1.0.1 • Published 2 months ago

@stevanfreeborn/netdi v1.0.1

Weekly downloads
-
License
MIT
Repository
github
Last release
2 months ago

netdi

A dependency injection container for TypeScript projects that is inspired by the .NET DI container. It is designed to be simple, lightweight, and easy to use.

pull_request codecov publish semantic-release: angular NPM License NPM Version NPM Downloads

Features

  • ✅ .NET-style service registration with different lifetimes (singleton, scoped, transient)
  • ✅ Constructor injection with decorators
  • ✅ Type-safe dependency resolution
  • ✅ Factory method support for complex instantiation scenarios
  • ✅ Scoped service lifetimes for per-request contexts

Installation

# npm
npm install @stevanfreeborn/netdi

# yarn
yarn add @stevanfreeborn/netdi

# pnpm
pnpm add @stevanfreeborn/netdi

Requirements

  • Node.js >= 18.0.0
  • TypeScript with decorators and reflection metadata enabled

Add the following to your tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Basic Usage

Here's a simple example of how to use netdi:

import {
  ServiceCollection,
  createServiceIdentifier,
  injectable,
  inject,
} from '@stevanfreeborn/netdi';

// Define interfaces
interface IGreeter {
  greet(name: string): string;
}

interface IGreetingService {
  createGreeting(name: string): string;
}

// Create service identifiers
const greeterIdentifier = createServiceIdentifier<IGreeter>();
const greetingServiceIdentifier = createServiceIdentifier<IGreetingService>();

// Implement services
@injectable()
class GreetingService implements IGreetingService {
  createGreeting(name: string): string {
    return `Hello, ${name}!`;
  }
}

@injectable()
class Greeter implements IGreeter {
  constructor(@inject(greetingServiceIdentifier) private greetingService: IGreetingService) {}

  greet(name: string): string {
    return this.greetingService.createGreeting(name);
  }
}

// Set up dependency injection
const services = new ServiceCollection();

// Register services
services.addSingleton(greetingServiceIdentifier, GreetingService);
services.addScoped(greeterIdentifier, Greeter);

// Build service provider
const serviceProvider = services.build();

// Resolve and use a service
const greeter = serviceProvider.getService(greeterIdentifier);
console.log(greeter.greet('World')); // Outputs: Hello, World!

Service Lifetimes

netdi supports three service lifetimes:

Singleton

Singleton services are created once and shared by all consumers.

services.addSingleton(serviceIdentifier, Implementation);

Scoped

Scoped services are created once per scope. This is useful for services that should be shared within a request but not across requests.

services.addScoped(serviceIdentifier, Implementation);

// Create a scope
const scope = serviceProvider.createScope();
const scopedService = scope.serviceProvider.getService(serviceIdentifier);

// When done with the scope
scope.dispose();

Transient

Transient services are created each time they are requested.

services.addTransient(serviceIdentifier, Implementation);

Factory Registration

For complex service instantiation, you can use factory methods:

services.addSingleton(serviceIdentifier, provider => {
  // Use the provider to get dependencies
  const dependency = provider.getService(dependencyIdentifier);

  // Create and configure your service instance
  const instance = new MyService(dependency);
  instance.configure();

  return instance;
});

Creating Service Identifiers

Service identifiers help maintain type safety and prevent service conflicts:

// Create an identifier for a specific interface
const userServiceIdentifier = createServiceIdentifier<IUserService>();

// Then use it for registration and resolution
services.addSingleton(userServiceIdentifier, UserService);
const userService = serviceProvider.getService(userServiceIdentifier);

Decorators

@injectable()

Marks a class as injectable, allowing the container to create instances with dependencies:

@injectable()
class MyService {
  constructor() {}
}

@inject()

Specifies a dependency for a parameter:

class MyService {
  constructor(
    @inject(loggerIdentifier) private logger: ILogger,
    @inject(configIdentifier) private config: IConfig,
  ) {}
}

Advanced Topics

Service Disposal

Both service providers and scopes implement a dispose() method:

// Dispose the root provider
serviceProvider.dispose();

// Dispose a scope
const scope = serviceProvider.createScope();
// ... use scope
scope.dispose();

Type Safety

netdi is designed to be fully type-safe. The getService() method returns the exact type associated with the service identifier:

// TypeScript knows this is an IUserService
const userService = serviceProvider.getService(userServiceIdentifier);

License

MIT © Stevan Freeborn