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

@davidcromianski-dev/ant-di

v2.0.1

Published

Simple JavaScript Dependency Injection Container inspired by PHP Pimple with advanced features like auto-wiring and service providers

Readme

Ant DI

npm version npm downloads npm bundle size License: MIT TypeScript

Simple JavaScript Dependency Injection Package

This package is a simple dependency injection container for JavaScript and TypeScript. It is inspired by the PHP Pimple package and provides advanced features like wiring and service providers.

ant-di

Features

  • Simple and lightweight - Easy to use dependency injection container
  • Auto-wiring - Manual dependency resolution for TypeScript classes with automatic singleton caching
  • Singleton behavior - Automatic instance caching ensures classes return the same instance
  • Factory support - Create new instances on each request
  • Protected callables - Store functions without executing them
  • Frozen keys - Prevent modification after first resolution
  • Service providers - Modular service registration
  • ValueObject management - Isolated dependency management with advanced features
  • Circular dependency detection - Built-in prevention of circular dependencies
  • Comprehensive error handling - Detailed error classes for better debugging
  • Comprehensive testing - Full test coverage with Poku
  • Rich examples - Multiple usage patterns and real-world scenarios

Installation

# NPM
npm install @davidcromianski-dev/ant-di
# PNPM
pnpm add @davidcromianski-dev/ant-di

Dependencies

Runtime Dependencies

This package has zero runtime dependencies, making it lightweight and avoiding dependency conflicts.

Development Dependencies

The following dependencies are used for development, testing, and building:

  • poku - Fast test runner for Node.js
  • ts-node - TypeScript execution engine for Node.js
  • tsx - TypeScript execution engine with esbuild
  • typescript - TypeScript compiler
  • vite - Build tool and dev server
  • vite-plugin-dts - TypeScript declaration file generation for Vite

Jest Compatibility

This package is fully compatible with Jest and other CommonJS-based testing frameworks. The build process generates both ESM and CommonJS outputs:

  • ESM: dist/index.es.js - For modern bundlers and ES modules
  • CommonJS: dist/index.cjs.js - For Jest, Node.js, and CommonJS environments

Using with Jest

To use this package with Jest, simply import from the CommonJS build:

// Jest test file
const { Container } = require('@davidcromianski-dev/ant-di');

describe('Container Tests', () => {
  it('should create a container instance', () => {
    const container = new Container();
    expect(container).toBeInstanceOf(Container);
  });
});

Quick Start

import { Container } from '@davidcromianski-dev/ant-di';

// Create container
const container = new Container();

// Register a service
container.set('database', () => new DatabaseConnection());

// Get the service
const db = container.get('database');

// Auto-wiring example
class UserService {
  constructor(private db: DatabaseConnection) {}
}

container.bind(UserService, [DatabaseConnection]);
const userService = container.get(UserService);

Container

The container is the main class of the package. It is used to store the services and parameters of the application.

Creating a Container

import { Container } from '@davidcromianski-dev/ant-di';

// Empty container
const container = new Container();

// Container with initial values
const container = new Container({
  'app.name': 'My Application',
  'app.version': '1.0.0',
});

Basic Operations

Registering Services

To register services in the container, you can use either the set method (recommended) or offsetSet method:

// Simple value
container.set('app.name', 'My Application');

// Factory function (traditional method)
container.set('database', () => new DatabaseConnection());

// Factory function (direct method)
container.set('database', () => new DatabaseConnection(), true);

// Class constructor
container.set('logger', Logger);

// Legacy method (still supported)
container.offsetSet('app.name', 'My Application');

[!NOTE] The set method is the recommended way to register services. The offsetSet method is maintained for backward compatibility.

The third parameter factory (default: false) allows you to directly register a function as a factory without calling container.factory() first. When factory=true, the function will be executed each time the service is requested.

Getting Services

To get services from the container, you can use the get method:

// Get by string key
const appName = container.get('app.name');

// Get by class constructor (auto-wiring)
const logger = container.get(Logger);

Container Management

The container provides methods for managing its lifecycle:

// Clear all registered services
container.clear();

// Dispose of the container (calls clear internally)
container.dispose();

[!TIP] Use clear() when you want to reset the container to its initial state, and dispose() when you're completely done with the container instance.

[!NOTE] If the service is a factory, it will be executed every time it is requested.

Version Compatibility

Version 3.0.0+ (Current)

The following methods are the recommended way to interact with the container:

// Service registration
container.set('key', value);
container.set('key', factory, true);

// Service retrieval
container.get('key');
container.get(Constructor);

// Service management
container.has('key');
container.unset('key');

Version < 3.0.0 (Legacy)

The following methods are deprecated but still supported for backward compatibility:

// Deprecated methods (still work, but not recommended)
container.offsetSet('key', value);
container.offsetGet('key');
container.offsetExists('key');
container.offsetUnset('key');

[!IMPORTANT] Migration Guide: All deprecated methods now internally call their modern equivalents:

  • offsetSet()set()
  • offsetGet()get()
  • offsetExists()has()
  • offsetUnset()unset()

Your existing code will continue to work, but consider migrating to the new methods for better maintainability.

[!CAUTION] If the service is not in the container, an exception will be thrown.

Checking Service Existence

To check if a service is registered in the container, you can use the has method:

const exists = container.has('service');

Removing Services

To remove services from the container, you can use the unset method:

container.unset('service');

Auto-wiring

Ant DI supports manual dependency injection for TypeScript classes. You can register dependencies manually.

Manual Dependency Registration

class UserService {
  constructor(
    private db: DatabaseConnection,
    private logger: Logger,
  ) {}
}

// Register dependencies manually
container.bind(UserService, [DatabaseConnection, Logger]);

// Get instance with auto-wired dependencies
const userService = container.get(UserService);

Dependency Binding by Name

For cases where you need to bind dependencies using class names as strings (useful for dynamic registration or avoiding circular import issues):

// Alternative binding method using class name
container.bind('UserService', [DatabaseConnection, Logger]);

// Both binding methods achieve the same result
const userService = container.get(UserService);

[!TIP] The bind() method accepts both constructor functions and class name strings, making it flexible for various use cases including dynamic class loading or avoiding potential circular import issues in complex applications.

Circular Dependency Detection

class ServiceA {
  constructor(public serviceB: ServiceB) {}
}

class ServiceB {
  constructor(public serviceA: ServiceA) {}
}

// This will throw a circular dependency error
container.bind(ServiceA, [ServiceB]);
container.bind(ServiceB, [ServiceA]);

Factory and Protection

Registering Factories

To register factories in the container, you can use multiple methods:

Method 1: Using the factory method (traditional)

const factory = (c: Container) => new Service();
container.factory(factory);
container.set('service', factory);

Method 2: Using set with factory parameter (recommended)

const factory = (c: Container) => new Service();
container.set('service', factory, true); // true = register as factory

[!TIP] All three methods are equivalent. The set method with factory=true is the recommended approach as it provides a more direct way to register factories without needing to call factory() first.

[!TIP] Useful for services that need to be created every time they are requested.

Protecting Services

To protect services in the container, you can use the protect method:

const callable = (c: Container) => new Service();
container.protect(callable);
container.set('service', callable);

[!TIP] By default, Ant DI assumes that any callable added to the container is a factory for a service, and it will invoke it when the key is accessed. The protect() method overrides this behavior, allowing you to store the callable itself.

Frozen Keys

Keys become frozen after first resolution of implicit factories:

// This creates an implicit factory
container.set('service', (c: Container) => new Service());

// First access - works fine
const service = container.get('service');

// Second access - throws error (key is frozen)
container.set('service', 'new value'); // Error!

Getting Raw Values

To get raw values from the container, you can use the raw method:

const rawValue = container.raw('service');

[!TIP] Useful when you need access to the underlying value (such as a closure or callable) itself, rather than the result of its execution.

Getting All Keys

To get all keys registered in the container, you can use the keys method:

const keys = container.keys();

Service Providers

Service providers allow you to organize the registration of services in the container.

import { Container, IServiceProvider } from '@davidcromianski-dev/ant-di';

class DatabaseServiceProvider implements IServiceProvider {
  register(container: Container) {
    container.set('db.host', 'localhost');
    container.set('db.port', 5432);

    const connectionFactory = (c: Container) => ({
      host: c.get('db.host'),
      port: c.get('db.port'),
      connect: () => `Connected to ${c.get('db.host')}:${c.get('db.port')}`,
    });

    container.factory(connectionFactory);
    container.set('db.connection', connectionFactory);
  }
}

// Register the service provider
container.register(new DatabaseServiceProvider());

// Use the services
const connection = container.get('db.connection');
console.log(connection.connect());

Singleton Behavior & Instance Caching

Ant DI automatically caches class instances to ensure singleton behavior:

class DatabaseService {
  constructor() {
    console.log('DatabaseService created');
  }
}

// Register the class
container.bind(DatabaseService, []);

// First access - creates instance
const db1 = container.get(DatabaseService); // "DatabaseService created"

// Second access - returns cached instance
const db2 = container.get(DatabaseService); // No log (cached)

console.log(db1 === db2); // true - same instance

[!TIP] Class instances are cached by their constructor function, ensuring that the same class always returns the same instance across the application.

ValueObject Management

Ant DI provides advanced ValueObject management for isolated dependency handling:

import { Container, ValueObject } from '@davidcromianski-dev/ant-di';

class DatabaseService {
  constructor(public config: any) {}
}

class UserService {
  constructor(public db: DatabaseService) {}
}

const container = new Container();

// Create ValueObject with dependencies
const dbValueObject = new ValueObject(
  container,
  'DatabaseService',
  DatabaseService,
);
dbValueObject.addDependency('dbConfig');

// Register configuration
container.set('dbConfig', { host: 'localhost', port: 5432 });

// Register service with ValueObject
container.set('DatabaseService', DatabaseService, false, ['dbConfig']);

// Get ValueObject from container
const valueObject = container.getValueObject('DatabaseService');

// ValueObject management methods
console.log('Has dependency:', valueObject?.hasDependency('dbConfig'));
console.log('Dependencies:', valueObject?.getDependencyKeys());
console.log('Is resolvable:', valueObject?.isResolvable());

// Create instance with dependency injection
const dbInstance = valueObject?.getValue();
console.log('Database instance:', dbInstance instanceof DatabaseService);

ValueObject Features

  • Dependency Management: Add, remove, and validate dependencies
  • Circular Dependency Detection: Automatic detection and prevention
  • Instance Creation: Singleton and factory patterns
  • Cloning: Create copies of ValueObjects
  • Validation: Check if dependencies are resolvable
// Advanced ValueObject usage
const valueObject = new ValueObject(container, 'UserService', UserService);

// Add multiple dependencies
valueObject
  .addDependency('DatabaseService')
  .addDependency('Logger')
  .asSingleton();

// Validate dependencies
if (valueObject.isResolvable()) {
  const instance = valueObject.getValue();
  console.log('Service created:', instance);
}

// Clone ValueObject
const cloned = valueObject.clone();
console.log('Cloned dependencies:', cloned.getDependencyKeys());

// Clear all dependencies
valueObject.clearDependencies();
console.log(
  'Dependencies cleared:',
  valueObject.getDependencyKeys().length === 0,
);

Proxy Access

The container supports proxy access for convenient property-style access:

// Set value
container.appName = 'My App';

// Get value
console.log(container.appName); // "My App"

Testing

The project uses Poku for testing. All tests are located in the tests/ directory.

Running Tests

# Run all tests
npm test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:coverage

Test Structure

Tests are organized into logical groups:

  • Basic Operations - Core container functionality
  • Factory Operations - Factory and protection features
  • Auto-wiring - Dependency injection and resolution
  • Frozen Keys - Key freezing behavior
  • Proxy Access - Proxy functionality
  • Constructor Initialization - Container initialization
  • Service Providers - Service provider registration

Example Test

import { describe, it, assert } from 'poku';
import { Container } from '../src';

describe('Container', () => {
  describe('Basic Operations', () => {
    it('should set and get a value', () => {
      const container = new Container();
      container.set('key', 'value');
      const value = container.get('key');
      assert.equal(value, 'value');
    });
  });
});

Examples

Comprehensive examples are available in the examples/ directory:

Basic Usage

npx ts-node examples/basic-usage.ts

Demonstrates simple value storage, factory functions, and basic container operations.

Dependency Injection

npx ts-node examples/dependency-injection.ts

Shows auto-wiring, manual dependency registration, and circular dependency detection.

Factories and Protection

npx ts-node examples/factories-and-protection.ts

Explains factory functions, protected callables, and frozen key behavior.

Service Providers

npx ts-node examples/service-providers.ts

Demonstrates modular service registration using service providers.

Advanced Patterns

npx ts-node examples/advanced-patterns.ts

Real-world scenarios including event systems and complex dependency graphs.

Run All Examples

npx ts-node examples/index.ts

API Reference

Container Class

Constructor

new Container(values?: Record<string, ValueOrFactoryOrCallable<any>>)

Methods

Core Container Operations
  • set<T>(key: string, value: ValueOrFactoryOrCallable<T>, factory?: boolean): void - Recommended method to register a value, factory, or callable in the container. The optional factory parameter (default: false) allows direct factory registration when set to true.
  • get<T>(key: string | Constructor<T>): T - Recommended method to retrieve a service by key or class constructor with auto-wiring
  • has(key: string): boolean - Recommended method to check if a key exists in the container
  • unset(key: string): void - Recommended method to remove a key from the container
Legacy Methods (Deprecated since 3.0.0)
  • offsetSet<T>(key: string, value: ValueOrFactoryOrCallable<T>, factory?: boolean): void - Deprecated. Use set() instead.
  • offsetGet<T>(key: string | Constructor<T>): T - Deprecated. Use get() instead.
  • offsetExists(key: string): boolean - Deprecated. Use has() instead.
  • offsetUnset(key: string): void - Deprecated. Use unset() instead.
Factory and Protection
  • factory<T>(factory: Factory<T>): Factory<T> - Mark a factory to always create new instances (prevents singleton caching)
  • protect(callable: Callable): Callable - Protect a callable from being treated as a factory
Utility Methods
  • raw<T>(key: string): T - Get the raw value without executing factories or callables
  • keys(): string[] - Get all registered keys in the container
  • clear(): void - Clear all registered services and reset the container to its initial state
  • dispose(): void - Dispose of the container and clean up resources
Service Providers
  • register(provider: IServiceProvider, values?: Record<string, ValueOrFactoryOrCallable<any>>): Container - Register a service provider with optional additional values
Dependency Injection
  • bind(target: Function | string, dependencies: any[]): void - Bind dependencies for a class constructor or class name string
  • getValueObject(key: string): ValueObjectInterface | undefined - Get a ValueObject for a given key if it exists
ValueObject Management
  • addDependency(dependency: DependencyKey): this - Add a dependency to the ValueObject
  • removeDependency(dependency: DependencyKey): this - Remove a specific dependency
  • hasDependency(dependency: DependencyKey): boolean - Check if a dependency exists
  • validateDependencies(): boolean - Validate all dependencies are available
  • resolveDependencies(): any[] - Resolve all dependencies with circular dependency detection
  • isResolvable(): boolean - Check if the ValueObject can be resolved
  • getDependencyKeys(): DependencyKey[] - Get all dependency keys
  • clearDependencies(): this - Clear all dependencies
  • clone(): ValueObjectInterface - Create a copy of the ValueObject

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Run the test suite
  6. Submit a pull request

License

This project is licensed under the MIT License. For more details, see the LICENSE file.