Скоупы и освобождение ресурсов
Скоуп ограничивает время жизни сервисов одной операцией, например обработкой запроса. Дочерний скоуп наследует регистрации родителя, но кэширует собственные scoped-экземпляры и отвечает за освобождение их ресурсов. Скоупы разных запросов не делят эти экземпляры между собой. Завершив обработку запроса, приложение освобождает его скоуп.
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):
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() не запускает каскадную очистку уже созданных дочерних скоупов. Каждый скоуп нужно очищать на его собственной границе жизненного цикла.
Нативное управление ресурсами
Контейнер реализует оба символа очистки:
using syncScope = root.createScope()
await using asyncScope = root.createScope()Используйте await using или await container.dispose(), если принадлежащий контейнеру ресурс может очищаться асинхронно.
Порядок очистки
Принадлежащие контейнеру экземпляры освобождаются в обратном порядке создания. Контейнер проверяет:
Symbol.asyncDisposeSymbol.dispose.dispose()
Если несколько методов освобождения ресурсов завершаются ошибкой, InferDI собирает ошибки в AggregateError, чтобы один сбой очистки не мешал закрытию остальных ресурсов.
