Dependency Injection for TypeScript
  • TypeScript 100%
Find a file
JPerera b037e7041a
All checks were successful
CI / test (push) Successful in 28s
Initial commit: di4ts dependency injection library
2026-03-01 13:13:02 +01:00
.forgejo/workflows Initial commit: di4ts dependency injection library 2026-03-01 13:13:02 +01:00
src Initial commit: di4ts dependency injection library 2026-03-01 13:13:02 +01:00
tests Initial commit: di4ts dependency injection library 2026-03-01 13:13:02 +01:00
.gitignore Initial commit: di4ts dependency injection library 2026-03-01 13:13:02 +01:00
bun.lock Initial commit: di4ts dependency injection library 2026-03-01 13:13:02 +01:00
bunfig.toml Initial commit: di4ts dependency injection library 2026-03-01 13:13:02 +01:00
LICENSE Initial commit 2026-03-01 12:48:29 +01:00
package.json Initial commit: di4ts dependency injection library 2026-03-01 13:13:02 +01:00
README.md Initial commit: di4ts dependency injection library 2026-03-01 13:13:02 +01:00
tsconfig.json Initial commit: di4ts dependency injection library 2026-03-01 13:13:02 +01:00

di4ts

A lightweight, Bun-native dependency injection library for TypeScript.

Features

  • Simple API: Both decorator and fluent APIs available
  • Singleton Lifecycle: All services are singletons by default
  • Async Support: First-class async initialization support
  • Circular Dependency Handling: Automatic proxy-based circular dependency resolution
  • Type-Safe: Full TypeScript support with type inference
  • Zero Config: Works out of the box with Bun

Installation

bun add di4ts

Quick Start

Using Decorators

import 'reflect-metadata';
import { Container, injectable, inject, createToken } from 'di4ts';

// Create tokens for interfaces
const ILogger = createToken<Logger>('ILogger');

@injectable()
class Logger {
  log(msg: string) {
    console.log(msg);
  }
}

@injectable()
class UserService {
  constructor(@inject(ILogger) private logger: Logger) {}

  getUser(id: string) {
    this.logger.log(`Getting user ${id}`);
    return { id, name: 'John' };
  }
}

// Configure container
const container = new Container();
container.bind(ILogger).to(Logger);

// Resolve dependencies
const userService = container.get(UserService);
userService.getUser('123');

Using Fluent API

import 'reflect-metadata';
import { Container, createToken } from 'di4ts';

interface IDatabase {
  query(sql: string): Promise<any[]>;
}

const IDatabase = createToken<IDatabase>('IDatabase');

const container = new Container();

// Bind to class
container.bind(IDatabase).to(Database);

// Bind to value
container.bind(IConfig).toValue({ connectionString: '...' });

// Bind to factory
container.bind(IDatabase).toFactory((c) => {
  const config = c.get(IConfig);
  return new Database(config.connectionString);
});

// Resolve
const db = container.get(IDatabase);

API Reference

Container

The main DI container that manages service registrations and resolution.

const container = new Container({
  parent: parentContainer, // Optional parent for hierarchical DI
  detectCircularDependencies: true, // Enable circular dependency detection
});

Methods

Method Description
bind<T>(token) Create a binding builder for a token
get<T>(token) Resolve a dependency synchronously
getAsync<T>(token) Resolve a dependency asynchronously
resolve<T>(Class) Auto-resolve a class without registration
resolveAsync<T>(Class) Auto-resolve a class asynchronously
has<T>(token) Check if a token is registered
init() Initialize all async dependencies
createChild() Create a child container
clear() Clear all registrations

Binding Builder

Fluent API for configuring bindings.

container.bind(IToken)
  .to(Class)              // Bind to class constructor
  .toValue(instance)      // Bind to existing value
  .toFactory(fn)          // Bind to factory function
  .toAsyncFactory(fn)     // Bind to async factory

Decorators

Decorator Description
@injectable() Marks a class as injectable
@singleton() Alias for @injectable()
@inject(token) Specifies injection token for parameter

Token Creation

// Create a type-safe token
const ILogger = createToken<Logger>('ILogger');

// Tokens can be strings, symbols, or classes
const token1 = 'my-service';
const token2 = Symbol('my-service');
const token3 = MyClass;

Async Initialization

const container = new Container();

// Register async factory
container.bind(IDatabase).toAsyncFactory(async () => {
  const db = new Database();
  await db.connect();
  return db;
});

// Initialize all async dependencies
await container.init();

// Now sync access works
const db = container.get(IDatabase);

Or use getAsync directly:

const db = await container.getAsync(IDatabase);

Circular Dependencies

di4ts handles circular dependencies automatically using proxies:

@injectable()
class ServiceA {
  constructor(@inject(IServiceB) public b: IServiceB) {}
}

@injectable()
class ServiceB {
  constructor(@inject(IServiceA) public a: IServiceA) {}
}

container.bind(IServiceA).to(ServiceA);
container.bind(IServiceB).to(ServiceB);

const a = container.get(IServiceA);
// a.b is a proxy that resolves to ServiceB
// a.b.a is a proxy that resolves back to ServiceA

Hierarchical DI

Create child containers that inherit from parent containers:

const parent = new Container();
parent.bind(IService).to(Service);

const child = parent.createChild();
child.bind(IService).to(AlternativeService); // Override parent binding

// Child can access parent bindings
// But parent cannot access child bindings

Error Handling

import {
  DependencyNotFoundError,
  CircularDependencyError,
  AsyncDependencyError,
  NotInjectableError,
  ResolutionError,
} from 'di4ts';

try {
  container.get(IUnknownToken);
} catch (e) {
  if (e instanceof DependencyNotFoundError) {
    console.log('Token not found:', e.token);
  }
}

Requirements

  • Bun 1.0+
  • TypeScript 5.0+
  • reflect-metadata (automatically imported)

License

MIT