Skip to content

Зачем InferDI

Ручное внедрение зависимостей через конструкторы хорошо подходит для начала: все зависимости видны, TypeScript проверяет каждый вызов new. InferDI полезен, когда граф вырастает и нужно также следить за временем жизни сервисов, скоупами, асинхронной готовностью, контрактами модулей и освобождением ресурсов.

Сборка проверяется как единый граф

Каждая регистрация возвращает новый тип контейнера. В нём записаны известные ключи, типы сервисов, время жизни, асинхронное состояние и отсутствующие входные данные скоупа. Следующая регистрация не сможет незаметно запросить неизвестный ключ, передать несовместимый аргумент, повторить имя или заставить singleton удерживать короткоживущее состояние.

ts
import { 
Container
} from '@inferdi/inferdi'
class
Database
{
find
(
id
: string) {
return {
id
}
} } class
UserService
{
constructor(readonly
database
:
Database
) {}
} const
app
= new
Container
()
.
registerClass
('database',
Database
, [])
.
registerClass
('users',
UserService
, ['database'])
const
users
=
app
.
get
('users')
const users: UserService

Это и означает «граф есть тип»: тип контейнера хранит сведения обо всех предыдущих регистрациях. Модули сохраняют те же требования, когда код сборки разнесён по файлам.

Что меняется по сравнению с ручной сборкой

При ручной сборке TypeScript уже проверяет new UserService(database). Но сам вызов не описывает время жизни регистраций, асинхронность зависимых сервисов, требования переиспользуемого модуля или владение закэшированными ресурсами. InferDI проверяет эти правила для всего графа и управляет жизненным циклом сервисов. Реализации по-прежнему выбираете вы.

Код сборки никуда не исчезает. И это полезно: на ревью видно, какая реализация выбрана и в каком порядке приходят зависимости.

Что делает код после компиляции

У ядра нет зависимостей времени выполнения, декораторов, рефлексии метаданных и получения сервисов через Proxy. Размер готового бандла строго ограничен: меньше 3 KiB после gzip. Чтение из кэша начинается с одного Map.get(); для явного undefined используется внутренний маркер, поэтому второй поиск не нужен.

После компиляции типы исчезают, но контейнер продолжает регистрировать сервисы, создавать объекты, вести кэши, сообщать об ошибках и освобождать свои ресурсы. Режим по умолчанию проверяет циклы и время жизни во время выполнения. { fast: true } включается явно: он отключает часть проверок и требует фиксированного графа.

Обычная бизнес-логика

Доменные сервисы получают зависимости через обычные аргументы конструктора или функции. Им не нужно импортировать InferDI или принимать контейнер. Контейнер используется в корне композиции и там, где приложение начинает и завершает работу со скоупами.

Модульный тест такого сервиса прост: создайте его с тестовой зависимостью. При замене InferDI потребуется переписать регистрации и интеграции с фреймворками. Если контейнер остался за пределами доменного слоя, бизнес-правила менять не придётся. Полный пример есть в разделе Корень композиции.

Явные скоупы и владение

Скоуп может соответствовать HTTP-запросу, заданию, операции отдельного клиента (tenant) или другой работе с определённым началом и концом. Контейнер освобождает принадлежащие ему закэшированные результаты классов и фабрик. Значениями из registerValue, .override() и входных данных скоупа, а также transient-результатами управляет вызывающий код. Родительские и дочерние контейнеры не освобождают друг друга автоматически.

Адаптеры связывают эти правила с React, Fastify, Hono, Koa, Express и Elysia, не добавляя логику фреймворков в ядро.

Границы гарантий

TypeScript использует структурную типизацию. Типы с одинаковой структурой взаимозаменяемы, если не различить их с помощью брендирования. any и приведения типов обходят проверки, а широкие типы ключей снижают точность графа. Динамическое получение зависимостей после await не участвует в синхронном отслеживании циклов по стеку вызовов.

InferDI не проектирует архитектуру, не чинит циклы, не исключает все утечки и не ускоряет любое приложение. Он проверяет объявленные связи и сохраняет реализацию небольшой. Теперь можно пройти Быстрый старт или посмотреть реальные ошибки на странице Типобезопасность.