@tonytoolkit/dependency-injection (0.0.1)
Installation
@tonytoolkit:registry=npm install @tonytoolkit/dependency-injection@0.0.1"@tonytoolkit/dependency-injection": "0.0.1"About this package
@tonytoolkit/dependency-injection
@tonytoolkit/dependency-injection — DI-контейнер с декларативным API (DI.instance, DI.cfg, DI.from, …), property injection (@Inject) и фабриками поверх контейнера.
Установка
Пакет лежит в npm registry Forgejo пользователя anton. Один раз настройте scope и (при необходимости) токен:
npm config set @tonytoolkit:registry https://git.serverbox.dev/api/packages/anton/npm/
# для приватного инстанса: Personal Access Token с правами на пакеты
# npm config set //git.serverbox.dev/api/packages/anton/npm/:_authToken=YOUR_TOKEN
npm install @tonytoolkit/dependency-injection
Публикация (локально)
После авторизации тем же PAT:
npm publish
Реестр задаётся в package.json → publishConfig.registry.
Публикация из CI
После пуша git-тега вида v0.0.1 workflow CI сначала гоняет тесты, затем выполняет npm publish (нужен секрет NPM_PUBLISH_TOKEN в настройках репозитория на Forgejo).
Импорт
import {DI} from '@tonytoolkit/dependency-injection/di/DI';
import {Inject} from '@tonytoolkit/dependency-injection/inject/Inject';
import {DIInjectRegistry} from '@tonytoolkit/dependency-injection/inject/registry/DIInjectRegistry';
import {DIFactory} from '@tonytoolkit/dependency-injection/factory/DIFactory';
import {ObjectFactory} from '@tonytoolkit/dependency-injection/di/ObjectFactory';
import {GetterToValue} from '@tonytoolkit/dependency-injection/adapters/GetterToValue';
Ключевые сущности
DI — контейнер
Центральный объект. Принимает map Record<string, InstanceBuilderDI> и создаёт инстансы через create(key).
| Статический API | Назначение |
|---|---|
DI.instance(Class, ...args) |
Создать binding на класс/функцию |
DI.singleton(Class, ...args) |
Singleton binding |
DI.cfg('Key') |
Ссылка на другой binding из конфига |
DI.ctx('Key') |
Ссылка на binding из runtime-контекста |
DI.from('Key', overrides) |
Клон binding + merge аргументов |
DI.fromCtx('Key') |
Клон из контекста |
DI.fromBuilder('Key') |
Клон из builders map |
DI.closure(fn, ...args) |
Lazy-результат функции при resolve |
DI.raw(value) |
Лiteral без дальнейшей обработки |
DI.self() |
Передать сам контейнер |
| Метод экземпляра | Назначение |
|---|---|
create(key, ctx?) |
Создать инстанс по ключу |
createFromBuilder(builder, ctx, key) |
Resolve готового builder |
getConfig(key) |
Получить builder по ключу |
configure / extend |
Заменить или дополнить конфиг |
InstanceBuilderDI — fluent builder binding
Внутренний объект, который описывает как создавать инстанс: ctor, args, singleton, ref, mergeArgs, lazy, closure. В конфигах tropic-ville напрямую не используется — только через DI.* static API.
Inject — property injection
Декоратор для полей класса. Резолвит зависимость через глобальный registry:
@Inject('EventEmitter')
private _eventEmitter: EventEmitter;
Если ключ не указан, берётся имя property без _.
DIInjectRegistry — мост @Inject → DI
const di = new DI(AppConfig);
Inject.setRegistry(new DIInjectRegistry(di));
После этого все @Inject в Cocos-компонентах резолвятся через di.create(key).
DIFactory / ObjectFactory — runtime factories
DIFactory— создаёт инстансы по ключу с optional runtime options (используется дляActionFactoryв tropic-ville).ObjectFactory— sub-factory с собственным config map и ctx (MetaGame, TiledWorld, TaskLoader).
IGetter / KeyValueGetter
Интерфейс lazy getter-а. KeyValueGetter — map key → builder/value для DIFactory.builders.
Adapters
| Adapter | Назначение |
|---|---|
GetterToValue |
DI.instance(GetterToValue, getter) — resolve getter при создании |
DIGetter |
IGetter, который лениво вызывает di.create(key) |
Как это устроено в tropic-ville
flowchart TD
subgraph configs [Declarative configs]
AppConfig
AnalyticsConfig
ActionDiConfig
PlatformDi["build-platforms/*.di.ts"]
end
AppConfig --> DI_ctor["new DI(AppConfig)"]
DI_ctor --> Create["di.create(key)"]
DI_ctor --> InjectReg["Inject.setRegistry(DIInjectRegistry)"]
InjectReg --> Components["@Inject on scene components"]
Create --> Services["Services, actions, getters"]
Components --> Services
Bootstrap (StartScene)
const di = new DI(AppConfig);
Inject.setRegistry(new DIInjectRegistry(di));
this._presenterManager = di.create('StartScenePresenterManager');
MainSceneComponent, MergeSceneComponent и другие сцены не создают свой DI — они используют @Inject против registry, установленного при старте.
Типичные паттерны конфигурации
// Singleton-сервис
'EventEmitter': DI.singleton(EventEmitter),
// Класс с зависимостями
'ActionManager': DI.instance(ActionManager, {
actionFactory: DI.cfg('ActionFactory'),
}),
// Клон binding + override
'StartAction': DI.from('ProxyAction', {action: Actions.Start}),
// Getter → value при resolve
'Locale': DI.instance(GetterToValue, DI.cfg('LocaleGetter')),
// Runtime factory
'ActionFactory': DI.instance(DIFactory, {
di: DI.self(),
builders: DI.instance(KeyValueGetter, ActionConfig),
}),
Scene analytics activation (declarative)
'AnalyticsActivatedEventOptions': DI.instance(ValueGetter, [{
activator: DI.instance(EventActivator, {
eventEmitter: DI.cfg('EventEmitter'),
events: SceneAnalyticsActivationEvents.Enter,
}),
listenEvents: DI.instance(SceneAnalyticsListenEventsGetter),
}]),
SceneAnalyticsListenEventsGetter резолвит Start/Main/MergeAnalyticsEventOptions лениво через Inject.getRegistry() — только когда сцена активируется.
Быстрый старт
import {DI} from '@tonytoolkit/dependency-injection/di/DI';
import {Inject} from '@tonytoolkit/dependency-injection/inject/Inject';
import {DIInjectRegistry} from '@tonytoolkit/dependency-injection/inject/registry/DIInjectRegistry';
class Logger {
log(message: string) {
console.log(message);
}
}
class Greeter {
constructor(private readonly _logger: Logger) {}
greet(name: string) {
this._logger.log(`Hello, ${name}`);
}
}
class App {
@Inject('Greeter')
private _greeter!: Greeter;
run() {
this._greeter.greet('world');
}
}
const di = new DI({
Logger: DI.singleton(Logger),
Greeter: DI.instance(Greeter, DI.cfg('Logger')),
});
Inject.setRegistry(new DIInjectRegistry(di));
const app = di.create('App'); // если App зарегистрирован
// или new App() после setRegistry — @Inject сработает на getter
Merge args (DI.from)
const di = new DI({
Base: DI.instance(MyClass, {a: 1, b: 2}),
Clone: DI.instance(MyClass, DI.from('Base', {b: 99})),
});
const clone = di.create('Clone');
// { a: 1, b: 99 }
Структура пакета
dependency-injection/
├── di/ # DI container core
├── inject/ # @Inject decorator + DIInjectRegistry
├── factory/ # DIFactory, EntryFactory
├── getters/ # IGetter, KeyValueGetter
├── adapters/ # GetterToValue, DIGetter
└── tests/
Миграция с @tapclap/utils-js
Replace imports:
- import {DI} from '@tapclap/utils-js/di/DI';
+ import {DI} from '@tonytoolkit/dependency-injection/di/DI';
Аналогично для inject/*, factory/*, getters/*.
Поведение и API идентичны текущей реализации в utils-js v1.25.x.
Разработка
npm install
npm run build
npm test
npm run lint
Лицензия
UNLICENSED — внутренний пакет TapClap.
Dependencies
Development dependencies
| ID | Version |
|---|---|
| @types/jest | ^29.2.2 |
| @types/node | ^14.11.2 |
| jest | ^29.2.2 |
| ts-jest | ^29.0.3 |
| ts-node | ^10.9.1 |
| typescript | ^5.7.3 |