Skip to content

Скоупы и освобождение ресурсов

Скоуп ограничивает время жизни сервисов одной операцией, например обработкой запроса. Дочерний скоуп наследует регистрации родителя, но кэширует собственные scoped-экземпляры и отвечает за освобождение их ресурсов. Скоупы разных запросов не делят эти экземпляры между собой. Завершив обработку запроса, приложение освобождает его скоуп.

ts
const root = new Container()
  .declareScopeInputs<{ request: RequestContext }>()
  .registerClass('db', Db, [])
  .registerClass('handler', RequestHandler, ['request', 'db'], 'scoped')

async function handle(request: Request) {
  await using scope = root.createScope({ request })
  return scope.get('handler').run()
}

db - корневой singleton. Запрос передаётся как внешнее входное значение скоупа и остаётся во владении приложения, а handler создаётся и освобождается скоупом запроса.

scoped-регистрации принадлежат дочерним скоупам. При fast: false (по умолчанию) попытка получить такую регистрацию из корневого контейнера выбрасывает Scoped "key" cannot be resolved from the root container. Use createScope(). Получайте ключ из контейнера, который вернул createScope(). fast: true отключает эту проверку во время выполнения.

Входные данные скоупа

Входные данные скоупа (scope inputs) поступают извне при его создании: запрос, контекст авторизации, сведения о клиенте системы или данные задания. Объявите их один раз и передавайте нужную часть через createScope(inputs):

ts
const root = new Container()
  .declareScopeInputs<{request: RequestContext}>()
  .registerClass('service', RequestService, ['request'], 'scoped')

await using scope = root.createScope({request})
scope.get('service')

Тип контейнера хранит переданные входные данные и скрывает зависимые сервисы до их готовности. Именованные профили, вложенное уточнение, фабрики с объявленными зависимостями, переиспользуемые типы и правила валидации разобраны в разделе Входные данные скоупа.

Владение

Владелец определяется видом регистрации, а не только тем, какой код вызвал конструктор.

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

root.dispose() не запускает каскадную очистку уже созданных дочерних скоупов. Каждый скоуп нужно очищать на его собственной границе жизненного цикла.

Нативное управление ресурсами

Контейнер реализует оба символа очистки:

ts
using syncScope = root.createScope()
await using asyncScope = root.createScope()

Используйте await using или await container.dispose(), если принадлежащий контейнеру ресурс может очищаться асинхронно.

Порядок очистки

Принадлежащие контейнеру экземпляры освобождаются в обратном порядке создания. Контейнер проверяет:

  1. Symbol.asyncDispose
  2. Symbol.dispose
  3. .dispose()

Если несколько методов освобождения ресурсов завершаются ошибкой, InferDI собирает ошибки в AggregateError, чтобы один сбой очистки не мешал закрытию остальных ресурсов.