npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@lexms/di-container

v1.1.0

Published

A lightweight dependency injection container for TypeScript

Readme

@lexms/di-container

A lightweight, TypeScript-first dependency injection container with support for both synchronous and asynchronous service resolution.

Features

  • 🚀 Lightweight: Minimal overhead with no external dependencies (except reflect-metadata for decorators)
  • 🔄 Sync & Async: Support for both synchronous and asynchronous service factories
  • 🛡️ Type Safe: Full TypeScript support with comprehensive type definitions
  • 🔁 Lifetime Management: Singleton and transient service lifetimes
  • 🎯 Flexible Tokens: Register services using classes, strings, or symbols
  • 🚫 Circular Dependency Detection: Automatic detection and prevention of circular dependencies
  • 🎨 Decorator Support: Optional decorator-based dependency injection
  • 🧹 Automatic Cleanup: Built-in disposal pattern for resource cleanup
  • 📊 Logging: Optional logging for debugging and monitoring
  • Performance Monitoring: Built-in performance tracking and metrics collection

Installation

pnpm add @lexms/di-container

For decorator support, also install:

pnpm add reflect-metadata

Quick Start

import { DIContainer, LifetimeScope } from '@lexms/di-container';

// Create a container
const container = new DIContainer();

// Define services
class DatabaseService {
  connect() {
    console.log('Connected to database');
  }
}

class UserService {
  constructor(private db: DatabaseService) {}
  
  getUsers() {
    this.db.connect();
    return ['user1', 'user2'];
  }
}

// Register services
container.registerSingleton(DatabaseService, () => new DatabaseService());
container.registerSingleton(UserService, () => 
  new UserService(container.resolve(DatabaseService))
);

// Resolve and use
const userService = container.resolve(UserService);
const users = userService.getUsers();

API Reference

DIContainer

Constructor

const container = new DIContainer(options?: DIContainerOptions);

Options:

  • enableLogging?: boolean - Enable debug logging (default: false)
  • logPrefix?: string - Custom log prefix (default: 'DIContainer')
  • enablePerformanceMonitoring?: boolean - Enable performance tracking (default: false)

Registration Methods

register<T>(token, factory, scope?)

Register a service with explicit lifetime scope.

container.register(MyService, () => new MyService(), LifetimeScope.SINGLETON);
registerSingleton<T>(token, factory)

Register a singleton service (same instance returned on every resolve).

container.registerSingleton(MyService, () => new MyService());
registerTransient<T>(token, factory)

Register a transient service (new instance returned on every resolve).

container.registerTransient(MyService, () => new MyService());
registerInstance<T>(token, instance)

Register an existing instance.

const config = { apiUrl: 'https://api.example.com' };
container.registerInstance('config', config);

Resolution Methods

resolve<T>(token)

Synchronously resolve a service.

const service = container.resolve(MyService);
resolveAsync<T>(token)

Asynchronously resolve a service (required for async factories).

const service = await container.resolveAsync(MyAsyncService);

Utility Methods

has(token): boolean

Check if a service is registered.

if (container.has(MyService)) {
  // Service is registered
}
getRegisteredTokens(): (string | symbol)[]

Get all registered service tokens.

const tokens = container.getRegisteredTokens();
clear(): void

Remove all service registrations.

container.clear();
dispose(): Promise<void>

Dispose the container and call dispose() on all disposable services.

await container.dispose();
getPerformanceStats(): ContainerPerformanceStats

Get comprehensive performance statistics for the container.

const stats = container.getPerformanceStats();
console.log(`Total resolutions: ${stats.totalResolutions}`);
console.log(`Average time: ${stats.averageResolutionTime}ms`);
getServiceMetrics(token?): ServicePerformanceMetrics[]

Get performance metrics for specific services or all services.

// Get metrics for all services
const allMetrics = container.getServiceMetrics();

// Get metrics for specific service
const serviceMetrics = container.getServiceMetrics(MyService);
resetPerformanceStats(): void

Reset all performance statistics.

container.resetPerformanceStats();

Advanced Usage

Async Services

class AsyncDatabaseService {
  async connect() {
    // Async connection logic
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log('Connected to database');
  }
}

// Register with async factory
container.registerSingleton(AsyncDatabaseService, async () => {
  const service = new AsyncDatabaseService();
  await service.connect();
  return service;
});

// Must use resolveAsync for async factories
const dbService = await container.resolveAsync(AsyncDatabaseService);

String and Symbol Tokens

// String tokens
container.registerInstance('apiUrl', 'https://api.example.com');
const apiUrl = container.resolve<string>('apiUrl');

// Symbol tokens
const DATABASE_CONFIG = Symbol('DatabaseConfig');
container.registerInstance(DATABASE_CONFIG, { host: 'localhost', port: 5432 });
const dbConfig = container.resolve<{ host: string; port: number }>(DATABASE_CONFIG);

Performance Monitoring

Enable performance monitoring to track service resolution times and identify bottlenecks.

const container = new DIContainer({ 
  enablePerformanceMonitoring: true,
  enableLogging: true 
});

class FastService {
  process() { return 'fast'; }
}

class SlowService {
  process() {
    // Simulate slow operation
    const start = Date.now();
    while (Date.now() - start < 100) {}
    return 'slow';
  }
}

container.registerSingleton(FastService, () => new FastService());
container.registerTransient(SlowService, () => new SlowService());

// Resolve services multiple times
container.resolve(FastService);
container.resolve(FastService); // Cached singleton
container.resolve(SlowService);
container.resolve(SlowService); // New instance

// Get performance statistics
const stats = container.getPerformanceStats();
console.log(`Total resolutions: ${stats.totalResolutions}`);
console.log(`Average resolution time: ${stats.averageResolutionTime}ms`);
console.log(`Slowest services:`, stats.slowestServices);

// Get service-specific metrics
const metrics = container.getServiceMetrics();
metrics.forEach(metric => {
  console.log(`${metric.token}: ${metric.averageTime}ms avg (${metric.totalResolutions} calls)`);
});

// Reset statistics
container.resetPerformanceStats();

Disposable Services

Services that implement a dispose() method will be automatically disposed when the container is disposed.

class ResourceService {
  private connection: any;
  
  constructor() {
    this.connection = createConnection();
  }
  
  async dispose() {
    await this.connection.close();
    console.log('Resources cleaned up');
  }
}

container.registerSingleton(ResourceService, () => new ResourceService());

// Later...
await container.dispose(); // Automatically calls ResourceService.dispose()

Decorator Support (Optional)

First, import reflect-metadata at the top of your main file:

import 'reflect-metadata';
import { Injectable, Inject, autoRegister } from '@lexms/di-container';

@Injectable
class DatabaseService {
  connect() {
    console.log('Connected to database');
  }
}

@Injectable
class UserService {
  constructor(
    private db: DatabaseService,
    @Inject('config') private config: any
  ) {}
}

// Auto-register decorated classes
autoRegister(DatabaseService);
autoRegister(UserService);

Error Handling

The container provides specific error types for different failure scenarios:

import { 
  ServiceNotFoundError, 
  CircularDependencyError, 
  DIContainerError 
} from '@lexms/di-container';

try {
  const service = container.resolve(UnregisteredService);
} catch (error) {
  if (error instanceof ServiceNotFoundError) {
    console.log('Service not found');
  } else if (error instanceof CircularDependencyError) {
    console.log('Circular dependency detected');
  }
}

Best Practices

1. Use Interface-based Design

interface IUserRepository {
  getUsers(): User[];
}

class DatabaseUserRepository implements IUserRepository {
  getUsers(): User[] {
    // Database implementation
    return [];
  }
}

class MockUserRepository implements IUserRepository {
  getUsers(): User[] {
    // Mock implementation
    return [{ id: 1, name: 'Test User' }];
  }
}

// Register based on environment
const repository = process.env.NODE_ENV === 'test' 
  ? new MockUserRepository()
  : new DatabaseUserRepository();
  
container.registerInstance('IUserRepository', repository);

2. Factory Functions for Complex Dependencies

container.registerSingleton(UserService, () => {
  const repository = container.resolve<IUserRepository>('IUserRepository');
  const logger = container.resolve<ILogger>('ILogger');
  const config = container.resolve<Config>('config');
  
  return new UserService(repository, logger, config);
});

3. Container Composition

class DatabaseModule {
  static register(container: DIContainer) {
    container.registerSingleton(DatabaseService, () => new DatabaseService());
    container.registerSingleton(UserRepository, () => 
      new UserRepository(container.resolve(DatabaseService))
    );
  }
}

class ServiceModule {
  static register(container: DIContainer) {
    container.registerSingleton(UserService, () =>
      new UserService(container.resolve(UserRepository))
    );
  }
}

// Register all modules
DatabaseModule.register(container);
ServiceModule.register(container);

Development

Scripts

# Install dependencies
pnpm install

# Build the project
pnpm run build

# Run tests
pnpm test

# Run tests in watch mode
pnpm run test:watch

# Run linting
pnpm run lint

# Fix linting issues
pnpm run lint:fix

# Clean build outputs
pnpm run clean

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.