@tonytoolkit/dependency-injection (0.0.1)

Published 2026-05-27 17:54:49 +03:00 by anton

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.jsonpublishConfig.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 — мост @InjectDI

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

Keywords

dependency-injection di container inject
Details
npm
2026-05-27 17:54:49 +03:00
1
Anton Lapshin
PERSONAL
28 KiB
Assets (1)
Versions (2) View all
0.0.2 2026-05-28
0.0.1 2026-05-27