@n2js/container
v0.1.0
Published
TypeScript-first module runtime and dependency injection container with scoped resolution and typed module features.
Maintainers
Readme
@n2js/container
简体中文 | English
@n2js/container 是一个 TypeScript-first 模块运行时与依赖注入容器。它的重点不只是实例化 class,而是把 Provider、模块可见性、request scope、只读 Definition、模块 Feature 和生命周期放进同一张类型安全的模块图里。适合在服务端、CLI、worker 或测试工具中独立组织复杂依赖。
当前包提供 ESM-only 构建产物,运行入口为
./dist/index.js,类型声明入口为./dist/index.d.ts。
与普通 DI 容器的不同
- 模块图优先: 依赖不只是全局注册表,Provider 属于明确的模块,跨模块访问需要
exported()或reexport。 - 类型优先:
createToken()、createMultiToken()和 class token 都保留 TypeScript 推断,减少手写类型断言。 - 声明与运行时分离: Definition 用于收集命令、schema、任务配置等只读元数据,不会混进运行时依赖解析。
- Feature 可扩展:
defineModuleFeature()可以把只读 artifact 挂到模块实例上,让模块既能解析依赖,也能携带派生能力。 - 作用域明确:
singleton、transient、request三种 scope 都是显式行为,request scope 需要主动创建和释放。 - 运行环境中立: 不绑定 HTTP、CLI 或 worker,你可以把它用在任意需要模块化依赖组织的 TypeScript 项目里。
安装
npm install @n2js/container也可以使用 Bun:
bun add @n2js/container快速开始
import {
Container,
createToken,
defineModule,
provide,
} from '@n2js/container';
const appConfigToken = createToken<{ readonly region: string }>('app.config');
class UserService {
getName(): string {
return 'Ada';
}
}
const AppModule = defineModule({
displayName: 'AppModule',
providers: [
UserService,
provide(appConfigToken).useValue({ region: 'cn' }).register(),
],
});
const container = new Container(AppModule);
const userService = await container.root.resolve(UserService);
const appConfig = await container.root.resolve(appConfigToken);
console.log(userService?.getName());
console.log(appConfig?.region);能力概览
- 使用
defineModule()描述模块的 Provider、imports、features 和 lifecycle。 - 使用
createToken()、createMultiToken()或 class token 解析运行时依赖。 - 使用
provide()注册 class、value、factory 或只读 Definition。 - 支持
singleton、transient、request三种 Provider scope。 - 支持构造函数注入、属性注入、可选依赖和
CURRENT_MODULE_REF。 - 使用
defineModuleFeature()为模块实例附加类型安全的只读 artifact。 - 使用
container.init()和container.dispose()管理模块生命周期。 - 使用
forwardRef()延迟引用模块或 token,解决声明顺序问题。
模块与可见性
模块通过 imports 组合。use() 表示普通导入,mount() 表示带路径的导入,并会把路径记录到模块快照中。
默认情况下,子模块 Provider 不会自动暴露给父模块。需要向上暴露时,可以在 Provider 上调用 exported(),或在导入关系上声明 reexport。
import {
Container,
createToken,
defineModule,
mount,
provide,
use,
} from '@n2js/container';
const sharedToken = createToken<string>('shared');
const SharedModule = defineModule({
displayName: 'SharedModule',
providers: [provide(sharedToken).useValue('shared').exported()],
});
const FeatureModule = defineModule({
displayName: 'FeatureModule',
imports: [use(SharedModule, { reexport: true })],
});
const RootModule = defineModule({
displayName: 'RootModule',
imports: [mount('/feature', FeatureModule)],
});
const container = new Container(RootModule);
await container.root.resolve(sharedToken);
container.findMount('/feature');
container.snapshot();Provider Scope
| Scope | 行为 |
| --- | --- |
| singleton | 默认 scope,同一个 Container runtime 内复用实例。 |
| transient | 每次解析创建新实例。 |
| request | 在同一个 request scope 内复用,不同 scope 之间隔离。 |
import {
Container,
createToken,
defineModule,
provide,
} from '@n2js/container';
const requestIdToken = createToken<string>('request.id');
class RequestService {
static readonly inject = [requestIdToken] as const;
constructor(readonly requestId: string) {}
}
const RequestModule = defineModule({
displayName: 'RequestModule',
providers: [
provide(requestIdToken)
.useFactory(() => crypto.randomUUID(), [], { scope: 'request' })
.register(),
provide(RequestService)
.useClass(RequestService, { scope: 'request' })
.register(),
],
});
const container = new Container(RequestModule);
const scope = container.createRequestScope();
await container.root.resolveWithScope(RequestService, scope);
scope.dispose();注入装饰器
可以使用 @Injectable()、@Inject() 和 @Optional() 描述构造函数或属性注入。若使用属性默认类型推断,请确保运行环境已经配置 decorator metadata。
import {
Container,
Inject,
Injectable,
Optional,
createToken,
defineModule,
provide,
} from '@n2js/container';
const nameToken = createToken<string>('name');
const missingToken = createToken<string>('missing');
@Injectable()
class GreetingService {
constructor(
@Inject(nameToken) readonly name: string,
@Optional() @Inject(missingToken) readonly fallback: string | undefined,
) {}
}
const GreetingModule = defineModule({
displayName: 'GreetingModule',
providers: [
provide(nameToken).useValue('Ada').register(),
GreetingService,
],
});
const container = new Container(GreetingModule);
await container.root.resolve(GreetingService);Definition 与 Feature
Definition 是启动期收集的只读声明,不参与运行时 resolve()。它适合保存命令、schema、任务配置或其它声明式元数据。
import {
Container,
createDefinitionToken,
defineModule,
provide,
} from '@n2js/container';
const commandToken = createDefinitionToken<{
readonly name: string;
readonly description: string;
}>('commands');
const CommandModule = defineModule({
displayName: 'CommandModule',
providers: [
provide(commandToken)
.useDefinition({ name: 'sync', description: 'Sync records' })
.register(),
],
});
const container = new Container(CommandModule);
const commands = container.definitions.entries('commands');defineModuleFeature() 可以把派生出的只读 artifact 挂到模块实例上,并保留精确类型。
import {
Container,
defineModule,
defineModuleFeature,
} from '@n2js/container';
const configFeature = defineModuleFeature({
name: 'config',
create() {
return {
config: Object.freeze({
region: 'cn',
}),
};
},
});
const ConfigModule = defineModule({
displayName: 'ConfigModule',
features: [configFeature],
});
const container = new Container(ConfigModule);
container.root.config.region;生命周期
container.init() 会按依赖优先顺序执行 onModuleInit,container.dispose() 会按反向顺序执行 onModuleDispose。重复调用会复用同一次状态,销毁失败会抛出 ContainerError。
import { Container, defineModule } from '@n2js/container';
const AppModule = defineModule({
displayName: 'LifecycleModule',
lifecycle: {
async onModuleInit() {
await Promise.resolve();
},
async onModuleDispose() {
await Promise.resolve();
},
},
});
const container = new Container(AppModule);
await container.init();
await container.dispose();公共 API
| API | 说明 |
| --- | --- |
| Container | 创建模块 runtime,提供 root、select()、resolve()、snapshot()、init() 和 dispose()。 |
| defineModule() | 定义模块 token,描述 Provider、imports、features 和 lifecycle。 |
| mount() | 按路径挂载模块,并保留 mount path 快照。 |
| use() | 导入模块,不产生新的 path 段。 |
| provide() | 创建 class、value、factory 或 definition provider。 |
| createToken() | 创建单值运行时 token。 |
| createMultiToken() | 创建多值运行时 token,解析结果为冻结数组。 |
| createDefinitionToken() | 创建 Definition token,仅用于声明收集。 |
| defineModuleFeature() | 定义模块 artifact 扩展。 |
| forwardRef() | 延迟解析模块或 runtime token。 |
| Injectable() | 标记 class 的默认 Provider 选项。 |
| Inject() | 标记构造函数参数或属性注入 token。 |
| Optional() | 标记依赖缺失时返回 undefined。 |
| CURRENT_MODULE_REF | 内置 token,用于在 Provider 中注入当前模块引用。 |
| ContainerError | 容器错误类型,带稳定错误码和结构化 details。 |
