@di-framework/core

中文文档
6.1.0发布于 2026/10/12(MIT OR Apache-2.0)TypeScriptES Module零依赖构建来源已验证近 30 天下载 4535

Lightweight, zero-dependency TypeScript Dependency Injection framework using decorators. Works seamlessly with SWC and TypeScript's native decorator support.

原始使用文档
文档目录40 节
  1. Explicit application startup
  2. Installation
  3. Quick Start
  4. 1. Basic Service
  5. 2. Service with Dependencies
  6. 3. Resolve Services
  7. API Reference
  8. @Container(options?)
  9. @Component(target)
  10. @Telemetry(options?)
  11. @TelemetryListener()
  12. useContainer()
  13. container.register(serviceClass, options?)
  14. container.registerFactory(name, factory, options?)
  15. container.resolve(serviceClass)
  16. container.has(serviceClass)
  17. container.getServiceNames()
  18. container.on(event, listener)
  19. container.construct(serviceClass, overrides?)
  20. container.fork(options?)
  21. Advanced Examples
  22. Multiple Dependencies
  23. Transient (Non-Singleton) Services
  24. Lifecycle Methods
  25. Factory Functions
  26. How It Works
  27. Comparison with SAMPLE.ts
  28. Before (Manual - SAMPLE.ts)
  29. After (DI Framework)
  30. Error Handling
  31. Circular Dependencies
  32. Unregistered Services
  33. Best Practices
  34. Testing
  35. License
  36. Private Service-to-Service Bindings
  37. 1. Declaring an Exported Service
  38. 2. Injecting a Service Binding into a Caller
  39. 3. Rejection of Unbound Callers
  40. 4. Local Multi-Service Development and Mock Substitution

@di-framework/core

CI npm version license

A lightweight, type-safe Dependency Injection framework for TypeScript using decorators. This framework automatically manages service instantiation, dependency resolution, and lifecycle management.

Explicit application startup

Use @Configuration() and @Bean() for factory-based wiring. Dependencies are listed explicitly, so startup detects missing, duplicate, and cyclic beans before invoking a factory. Async factories are awaited while Container.resolve() remains synchronous.

import { ApplicationContext } from '@di-framework/core/application-context';
import { Bean, Configuration } from '@di-framework/core/decorators';

@Configuration()
class AppConfiguration {
  @Bean()
  port() { return 8080; } // token defaults to "port"

  @Bean('serverUrl', { dependencies: ['port'] })
  async serverUrl(port: number) { return `http://localhost:${port}`; }
}

const app = ApplicationContext.builder()
  .configuration(AppConfiguration)
  .bootstrap(HttpServer)
await app.start();
await app.stop();

Bootstrap components may define synchronous or asynchronous start() and stop() hooks. Successful components stop in reverse startup order. The legacy @Bootstrap() decorator still resolves at class-definition time, but is deprecated and will be removed in the next major release.

Installation

No external dependencies required! The framework works with SWC and TypeScript's native decorator support.

Just ensure you have:

  • TypeScript 5.0+
  • SWC or TypeScript compiler with experimentalDecorators and emitDecoratorMetadata enabled

The decorators are fully integrated with SWC's native support - no need for reflect-metadata or any other polyfill.

Quick Start

1. Basic Service

import { Container } from '@di-framework/core/decorators';

@Container()
export class DatabaseService {
  connect(): void {
    console.log('Connected to database');
  }
}

2. Service with Dependencies

import { Container, Component } from '@di-framework/core/decorators';
import { DatabaseService } from './services/DatabaseService';

@Container()
export class UserService {
  @Component(DatabaseService)
  private db!: DatabaseService;

  constructor() {}

  getUser(id: string) {
    return this.db.query(`SELECT * FROM users WHERE id = '${id}'`);
  }
}

Note: Property injection is used for all dependencies. This works seamlessly with SWC and TypeScript's native decorator support.

3. Resolve Services

import { useContainer } from '@di-framework/core/container';
import { UserService } from './services/UserService';

const container = useContainer();
const userService = container.resolve<UserService>(UserService);

// All dependencies are automatically injected!
userService.getUser('123');

API Reference

@Container(options?)

Marks a class as injectable and automatically registers it with the DI container.

Options:

  • singleton?: boolean (default: true) - Create a new instance each time or reuse the same instance
  • container?: DIContainer - Specify a custom container (defaults to global container)
    • Note: Import as import { Container as DIContainer } from '@di-framework/core/container' to avoid name collision with the @Container decorator.

Example:

@Container({ singleton: false })
export class RequestScopedService {
  // New instance created for each resolution
}

@Component(target)

Marks a constructor parameter or property for dependency injection.

Parameters:

  • target - The class to inject or a string identifier for factory-registered services

Example - Constructor Parameter:

@Container()
export class OrderService {
  constructor(@Component(DatabaseService) private db: DatabaseService) {}
}

Example - Property Injection:

@Container()
export class ReportService {
  @Component(DatabaseService)
  private db: DatabaseService;
}

@Telemetry(options?)

Marks a method for telemetry tracking. When called, it emits a telemetry event on the container. Works with both synchronous and asynchronous methods.

Options:

  • logging?: boolean (default: false) - If true, logs the method execution details (status and duration) to the console.

Example:

@Container()
export class ApiService {
  @Telemetry({ logging: true })
  async fetchData(id: string) {
    // ...
  }
}

@TelemetryListener()

Marks a method as a listener for telemetry events. The method will be automatically registered to the container's telemetry event when the service is instantiated.

Example:

@Container()
export class MonitoringService {
  @TelemetryListener()
  onTelemetry(event: any) {
    console.log(
      `Method ${event.className}.${event.methodName} took ${event.endTime - event.startTime}ms`,
    );
  }
}

useContainer()

Returns the global DI container instance.

import { useContainer } from '@di-framework/core/container';

const container = useContainer();

container.register(serviceClass, options?)

Manually register a service class.

container.register(UserService, { singleton: true });

container.registerFactory(name, factory, options?)

Register a service using a factory function.

container.registerFactory(
  'config',
  () => ({
    apiKey: process.env.API_KEY,
    dbUrl: process.env.DATABASE_URL,
  }),
  { singleton: true },
);

container.resolve(serviceClass)

Resolve and get an instance of a service.

const userService = container.resolve<UserService>(UserService);
// or by name
const config = container.resolve('config');

container.has(serviceClass)

Check if a service is registered.

if (container.has(UserService)) {
  const service = container.resolve(UserService);
}

container.getServiceNames()

Get all registered service names.

const names = container.getServiceNames();
console.log(names); // ['DatabaseService', 'UserService', ...]

container.on(event, listener)

Subscribe to DI container lifecycle events (observer pattern).

Events:

  • registered - fired when a class or factory is registered
  • resolved - fired whenever a service is resolved (cached or fresh)
  • constructed - fired when construct() creates a new instance
  • cleared - fired when the container is cleared

Example:

const unsubscribe = container.on('resolved', ({ key, fromCache }) => {
  console.log(`Resolved ${typeof key === 'string' ? key : key.name} (fromCache=${fromCache})`);
});

unsubscribe(); // stop listening

container.construct(serviceClass, overrides?)

Create a fresh instance without registering it, while still honoring dependency injection. Useful for constructor-pattern scenarios where you need to supply specific primitives/config values.

import { Component } from '@di-framework/core/decorators';
import { container } from '@di-framework/core/container';

class LoggerService {
  log(message: string) {
    console.log(message);
  }
}

class Greeter {
  constructor(
    @Component(LoggerService) private logger: LoggerService,
    private greeting: string,
  ) {}
}

const greeter = container.construct(Greeter, { 1: 'hello world' });

container.fork(options?)

Clone the container registrations (prototype pattern) into a new container. Pass { carrySingletons: true } to reuse existing singleton instances; default is to start with fresh instances.

const testContainer = container.fork({ carrySingletons: false });

Advanced Examples

Multiple Dependencies

@Container()
export class ApplicationContext {
  constructor(
    @Component(DatabaseService) private db: DatabaseService,
    @Component(LoggerService) private logger: LoggerService,
    @Component(AuthService) private auth: AuthService,
  ) {}

  async initialize() {
    this.logger.log('Initializing application...');
    await this.db.connect();
    this.auth.setup();
  }
}

Transient (Non-Singleton) Services

@Container({ singleton: false })
export class RequestContext {
  id = Math.random().toString();

  constructor(@Component(LoggerService) private logger: LoggerService) {
    this.logger.log(`Request context created: ${this.id}`);
  }
}

// Each resolve creates a new instance
const ctx1 = container.resolve(RequestContext); // new instance
const ctx2 = container.resolve(RequestContext); // different instance

Lifecycle Methods

Services can optionally implement lifecycle methods:

@Container()
export class DatabaseService {
  private connected = false;

  setEnv(env: Record<string, any>) {
    // Called to initialize environment-specific config
    console.log('DB URL:', env.DATABASE_URL);
  }

  setCtx(context: any) {
    // Called to set execution context
    console.log('Context:', context);
  }

  connect() {
    this.connected = true;
  }
}

// Calling lifecycle methods
const db = container.resolve(DatabaseService);
db.setEnv(process.env);
db.setCtx({ userId: '123' });
db.connect();

Factory Functions

container.registerFactory(
  'apiClient',
  () => {
    return new HttpClient({
      baseUrl: process.env.API_URL,
      timeout: 5000,
    });
  },
  { singleton: true },
);

// Use in services
@Container()
export class UserService {
  constructor(@Component('apiClient') private api: any) {}
}

How It Works

  1. Decoration: When you decorate a class with @Container(), the decorator registers it with the global container
  2. Registration: The class is stored in the container with metadata about its dependencies
  3. Resolution: When you call container.resolve(ServiceClass):
    • The container creates a new instance (or returns existing singleton)
    • It examines the constructor parameters and their types
    • It recursively resolves each dependency
    • Dependencies are injected into the constructor
    • The configured instance is returned
  4. Caching: Singleton instances are cached and reused

Comparison with SAMPLE.ts

Before (Manual - SAMPLE.ts)

const createServerContext = (env, ctx) => {
  if (!instanceState.member) {
    const contextInstance = Context.create({
      contactService: ContactService.create({}),
      assetService: AssetService.create({}),
      transactionService: TransactionService.create({}),
      // ... 20+ more services manually created and wired
      chatService: ChatService.create({
        openAIApiKey: env.OPENAI_API_KEY,
        // ... manual configuration
      }),
    });
    instanceState.member = contextInstance;
  }

  instanceState.member.setEnv(env);
  instanceState.member.setCtx(ctx);
  // ... manual dependency wiring
  instanceState.member.knowledgeService.setAttachmentService(
    instanceState.member.attachmentService,
  );
  return instanceState.member;
};

After (DI Framework)

@Container()
export class ApplicationContext {
  constructor(
    @Component(ContactService) private contactService: ContactService,
    @Component(AssetService) private assetService: AssetService,
    @Component(TransactionService)
    private transactionService: TransactionService,
    // ... all services automatically injected
    @Component(ChatService) private chatService: ChatService,
  ) {}

  setEnv(env: Record<string, any>) {
    // Services are already available via constructor injection
    this.chatService.initialize(env.OPENAI_API_KEY);
  }

  setCtx(ctx: any) {
    // All services have access to context
  }
}

// Usage
const container = useContainer();
const appContext = container.resolve(ApplicationContext);
appContext.setEnv(env);
appContext.setCtx(ctx);

Benefits:

  • No manual service instantiation
  • No manual dependency wiring
  • Automatic singleton management
  • Type-safe dependency resolution
  • Easier to test (mock services simply by registering test implementations)
  • Scales better as services grow

Error Handling

Circular Dependencies

// This will be detected and throw an error:
@Container()
class ServiceA {
  constructor(@Component(ServiceB) private b: ServiceB) {}
}

@Container()
class ServiceB {
  constructor(@Component(ServiceA) private a: ServiceA) {}
}

// Error: Circular dependency detected while resolving ServiceA

Unregistered Services

@Container()
class MyService {
  constructor(@Component(UnregisteredService) private s: UnregisteredService) {}
}

// Error: Service 'UnregisteredService' is not registered in the DI container

Best Practices

  1. Mark all services with @Container() - Makes dependency management explicit
  2. Use constructor injection - Preferred over property injection for mandatory dependencies
  3. Use property injection for optional dependencies - Keep it minimal
  4. No need to import reflect-metadata - This framework uses a lightweight metadata store
  5. Separate service interfaces from implementations - For easier testing
  6. Use singletons for stateless services - Most services should be singletons
  7. Use transient (non-singleton) for stateful services - Request/session scoped services

Testing

// Create a test container
import { Container as DIContainer } from '@di-framework/core/container';

const testContainer = new DIContainer();

// Register mock implementations
class MockDatabaseService {
  query() {
    return { mock: true };
  }
}

testContainer.register(MockDatabaseService);

// Register dependencies
testContainer.register(UserService);

// Test the service with mocked dependencies
const userService = testContainer.resolve(UserService);
expect(userService.getUser('1')).toEqual({ mock: true });

License

Licensed under either MIT or Apache-2.0, at your option.

Private Service-to-Service Bindings

Declare private service-to-service bindings so services can export callable contracts and authorized callers can invoke them through named dependencies via DI, without exposing public HTTP endpoints or network addresses.

1. Declaring an Exported Service

import { Container } from '@di-framework/core/decorators';
import { ExportService } from '@di-framework/core/service-bindings';

@Container()
@ExportService({
  name: 'inventory-service',
  operations: ['reserve', 'release'],
})
export class InventoryService {
  async reserve(items: any[]) {
    return { reservationId: 'res-123' };
  }

  async release(reservationId: string) {
    return { released: true };
  }
}

When operations is omitted, class registration discovers prototype methods without constructing the service. Declare arrow-function fields explicitly, for example @ExportService({ name: 'inventory-service', operations: ['read'] }) for read = () => .... The container resolves the service instance when an operation is invoked. Registering an existing instance also discovers its own function properties.

2. Injecting a Service Binding into a Caller

import { Container } from '@di-framework/core/decorators';
import { ServiceBinding } from '@di-framework/core/service-bindings';

@Container()
export class CheckoutService {
  constructor(
    @ServiceBinding('inventory', {
      caller: 'checkout-service',
      target: 'inventory-service',
    })
    private readonly inventory: InventoryContract,
  ) {}

  async checkout(items: any[]) {
    return await this.inventory.reserve(items);
  }
}

3. Rejection of Unbound Callers

Callers without an explicit authorization grant cannot invoke target services and are rejected with UnboundCallerError.

4. Local Multi-Service Development and Mock Substitution

Run multiple local services, monitor binding statuses, and substitute mocks for isolated testing:

import { LocalServiceDevManager } from '@di-framework/core/service-bindings';

const dev = new LocalServiceDevManager();
dev.registerService('inventory-service', new InventoryService());
dev.bind('checkout-service', 'inventory', 'inventory-service');

console.log(dev.formatStatusTable());

版本与发布记录

@di-framework/core 共发布 40 个版本,首次发布于 2026/08/03;过去 12 个月发布了 40 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
6.1.0latest2026/10/121 个月前
6.0.72026/10/121 个月前
6.0.62026/10/091 个月前
6.0.52026/10/071 个月前
6.0.42026/10/041 个月前
6.0.32026/10/021 个月前

关于 @di-framework/core 的常见问题

@di-framework/core 是什么?

Lightweight, zero-dependency TypeScript Dependency Injection framework using decorators. Works seamlessly with SWC and TypeScript's native decorator support.

如何安装 @di-framework/core?

在项目目录运行 npm install @di-framework/core,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:typescript(^5)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@di-framework/core 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@di-framework/core 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@di-framework/core 可以商用吗?

@di-framework/core 使用 (MIT OR Apache-2.0) 许可。可在 MIT、Apache-2.0 中任选其一遵守。选择 MIT 时,宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@di-framework/core 的下载量有多少?

根据 npm 官方下载统计,@di-framework/core 在 2026/10/12 – 2026/11/10 这 30 天内被下载 4,535 次,日均约 151 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@di-framework/core 还在维护吗?

最新版本 6.1.0 发布于 2026/10/12(1 个月前);至今共发布 40 个版本,其中过去 12 个月发布了 40 个;首次发布于 2026/08/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。