Lightweight dependency injection container for JavaScript/TypeScript
—
Comprehensive lifecycle management system with multiple scopes, registration options, disposable resource handling, and interception capabilities for advanced dependency behavior customization.
Enumeration defining different lifecycle behaviors for dependency instances.
/**
* Lifecycle enumeration defining dependency instance lifecycles
* Controls when and how instances are created and reused
*/
enum Lifecycle {
/** New instance created on every resolution */
Transient = 0,
/** Single instance shared across entire application */
Singleton = 1,
/** Single instance per resolution chain (same resolve() call) */
ResolutionScoped = 2,
/** Single instance per container (including child containers) */
ContainerScoped = 3
}Usage Examples:
// Transient - new instance every time
container.register("TransientService", TransientService, {
lifecycle: Lifecycle.Transient
});
// Singleton - shared across application
container.register("ConfigService", ConfigService, {
lifecycle: Lifecycle.Singleton
});
// Resolution scoped - shared within single resolve call
container.register("RequestContext", RequestContext, {
lifecycle: Lifecycle.ResolutionScoped
});
// Container scoped - shared within container instance
container.register("CacheService", CacheService, {
lifecycle: Lifecycle.ContainerScoped
});
// Default is Transient if not specified
container.register("DefaultService", DefaultService);Configuration object for specifying lifecycle and other registration behavior.
/**
* Options for dependency registration
* Currently supports lifecycle configuration
*/
interface RegistrationOptions {
lifecycle: Lifecycle;
}Usage Examples:
// Explicit lifecycle specification
const singletonOptions: RegistrationOptions = {
lifecycle: Lifecycle.Singleton
};
container.register("DatabaseService", DatabaseService, singletonOptions);
// Inline options
container.register("CacheService", CacheService, {
lifecycle: Lifecycle.ContainerScoped
});
// Factory with lifecycle
container.register("Logger", {
useFactory: (container) => new Logger(container.resolve("LogLevel"))
}, { lifecycle: Lifecycle.Singleton });Interface and utilities for managing resource disposal and cleanup.
/**
* Interface for disposable resources
* Enables automatic cleanup when container is disposed
*/
interface Disposable {
dispose(): Promise<void> | void;
}
/**
* Type guard to check if value implements Disposable
* @param value - Value to check
* @returns True if value has dispose method
*/
function isDisposable(value: any): value is Disposable;Usage Examples:
// Disposable service
class DatabaseService implements Disposable {
private connection: Connection;
constructor() {
this.connection = createConnection();
}
async dispose() {
await this.connection.close();
console.log("Database connection closed");
}
}
// File service with synchronous disposal
class FileService implements Disposable {
private fileHandle: FileHandle;
dispose() {
this.fileHandle.close();
console.log("File handle closed");
}
}
// Register disposable services
container.registerSingleton(DatabaseService);
container.registerSingleton(FileService);
// Check if disposable
const service = container.resolve(DatabaseService);
if (isDisposable(service)) {
console.log("Service is disposable");
}
// Cleanup all disposable resources
await container.dispose();Advanced interception system for customizing dependency resolution behavior with pre and post resolution hooks.
/**
* Options for resolution interceptors
* Controls when interceptors are executed
*/
interface InterceptorOptions {
frequency: Frequency;
}
/**
* Frequency enumeration for interceptor execution
* Controls how often interceptors run
*/
type Frequency = "Always" | "Once";
/**
* Callback executed before dependency resolution
* @param token - Token being resolved
* @param resolutionType - Type of resolution (Single or All)
*/
type PreResolutionInterceptorCallback<T> = (
token: InjectionToken<T>,
resolutionType: ResolutionType
) => void;
/**
* Callback executed after dependency resolution
* @param token - Token that was resolved
* @param result - Resolved instance(s)
* @param resolutionType - Type of resolution (Single or All)
*/
type PostResolutionInterceptorCallback<T> = (
token: InjectionToken<T>,
result: T | T[],
resolutionType: ResolutionType
) => void;
/**
* Resolution type enumeration
* Indicates whether single or multiple instances were resolved
*/
type ResolutionType = "Single" | "All";Usage Examples:
// Pre-resolution logging
container.beforeResolution("UserService", (token, resolutionType) => {
console.log(`About to resolve ${String(token)} as ${resolutionType}`);
performance.mark(`resolve-${String(token)}-start`);
});
// Post-resolution logging with performance measurement
container.afterResolution("UserService", (token, result, resolutionType) => {
performance.mark(`resolve-${String(token)}-end`);
performance.measure(
`resolve-${String(token)}`,
`resolve-${String(token)}-start`,
`resolve-${String(token)}-end`
);
console.log(`Resolved ${String(token)}:`, result);
});
// One-time initialization interceptor
container.afterResolution("DatabaseService", (token, result) => {
console.log("Database service initialized for first time");
(result as DatabaseService).runMigrations();
}, { frequency: "Once" });
// Always-running validation interceptor
container.afterResolution("ConfigService", (token, result) => {
const config = result as ConfigService;
if (!config.isValid()) {
throw new Error("Invalid configuration detected");
}
}, { frequency: "Always" });
// Multi-resolution interceptor
container.afterResolution("Plugin", (token, result, resolutionType) => {
if (resolutionType === "All") {
const plugins = result as Plugin[];
console.log(`Loaded ${plugins.length} plugins`);
}
});Container-level lifecycle management including reset, instance clearing, and disposal.
/**
* Clear all registrations from container
* Does not affect parent containers in hierarchy
*/
reset(): void;
/**
* Clear all cached instances but keep registrations
* Forces fresh instance creation on next resolve
*/
clearInstances(): void;
/**
* Dispose container and all registered disposable resources
* Calls dispose() on all instances implementing Disposable
* @returns Promise resolving when all disposals complete
*/
dispose(): Promise<void>;Usage Examples:
// Setup container with services
container.registerSingleton(DatabaseService);
container.registerSingleton(CacheService);
container.register("TempService", TempService);
// Clear cached instances (services will be recreated on next resolve)
container.clearInstances();
// Reset all registrations (complete cleanup)
container.reset();
// Proper application shutdown
class Application {
async shutdown() {
console.log("Shutting down application...");
// Dispose all resources
await container.dispose();
console.log("Application shutdown complete");
}
}
// Graceful shutdown handling
process.on('SIGTERM', async () => {
const app = container.resolve(Application);
await app.shutdown();
process.exit(0);
});Hierarchical container management with inheritance and scoped lifecycles.
/**
* Create child container inheriting from parent
* Child containers can override parent registrations
* Container-scoped instances are shared within child container
* @returns New child DependencyContainer instance
*/
createChildContainer(): DependencyContainer;Usage Examples:
// Parent container setup
container.register("GlobalService", GlobalService, { lifecycle: Lifecycle.Singleton });
container.register("SharedService", SharedService, { lifecycle: Lifecycle.ContainerScoped });
// Child container with overrides
const childContainer = container.createChildContainer();
childContainer.register("SharedService", ChildSpecificService); // Override parent
childContainer.register("ChildOnlyService", ChildOnlyService);
// Resolution behavior
const globalFromParent = container.resolve("GlobalService");
const globalFromChild = childContainer.resolve("GlobalService");
// Same instance (singleton spans containers)
const sharedFromParent = container.resolve("SharedService"); // Original SharedService
const sharedFromChild = childContainer.resolve("SharedService"); // ChildSpecificService
// Child cleanup
await childContainer.dispose(); // Only disposes child-specific resources// Lifecycle enumeration
enum Lifecycle {
Transient = 0,
Singleton = 1,
ResolutionScoped = 2,
ContainerScoped = 3
}
// Registration configuration
interface RegistrationOptions {
lifecycle: Lifecycle;
}
// Disposable interface
interface Disposable {
dispose(): Promise<void> | void;
}
// Interception types
interface InterceptorOptions {
frequency: Frequency;
}
type Frequency = "Always" | "Once";
type ResolutionType = "Single" | "All";
type PreResolutionInterceptorCallback<T> = (
token: InjectionToken<T>,
resolutionType: ResolutionType
) => void;
type PostResolutionInterceptorCallback<T> = (
token: InjectionToken<T>,
result: T | T[],
resolutionType: ResolutionType
) => void;Install with Tessl CLI
npx tessl i tessl/npm-tsyringe