Входные данные скоупа
Данные скоупа передаёт код, который открывает скоуп: HTTP-запрос, авторизованный пользователь, tenant, payload задания или trace context. InferDI проводит эти требования через граф и не даёт разрешить сервис, пока нужные данные не предоставлены.
Объявление данных
declareScopeInputs() добавляет ключи только в граф типов. Метод не создаёт runtime-регистрации и значения.
interface RequestContext {
requestId: string
}
interface AuthContext {
userId: string
}
class PublicService {
constructor(readonly request: RequestContext) {}
}
class AccountService {
constructor(
readonly request: RequestContext,
readonly auth: AuthContext
) {}
}
const root = new Container()
.declareScopeInputs<{
request: RequestContext
auth: AuthContext
}>()
.registerClass('publicService', PublicService, ['request'], 'scoped')
.registerClass(
'accountService',
AccountService,
['request', 'auth'],
'scoped'
)Данные скоупа имеют время жизни scoped. Singleton не может зависеть от них, поэтому компилятор отклонит пропущенный или явно указанный singleton lifetime у этих сервисов.
Типизированные профили
createScope(inputs) принимает любую часть ещё не предоставленных данных. Тип возвращённого контейнера хранит набор готовых требований.
const publicScope = root.createScope({request})
publicScope.get('publicService')
// @ts-expect-error: auth has not been provided
publicScope.get('accountService')
const authenticatedScope = publicScope.createScope({auth})
authenticatedScope.get('accountService')Дайте профилям приложения имена с помощью обычных функций. TypeScript выведет точный набор готовых ключей без ручных аннотаций контейнера.
const openPublicScope = (request: RequestContext) =>
root.createScope({request})
const openAuthenticatedScope = (
request: RequestContext,
auth: AuthContext
) => root.createScope({request, auth})
type PublicScope = ReturnType<typeof openPublicScope>
type AuthenticatedScope = ReturnType<typeof openAuthenticatedScope>Открывайте профиль на границе запроса, сообщения или задания. Не добавляйте объекты фреймворка в корневой граф.
Распространение требований
InferDI проводит требования через классы, lazy companion, deps-aware sync-фабрики и декларативные async-фабрики.
const app = root
.registerFactory(
'requestId',
(c) => c.get('request').requestId,
['request'],
'scoped'
)
.registerAsyncFactory(
'session',
async (auth: AuthContext) => loadSession(auth.userId),
['auth'],
'scoped'
)Кортеж зависимостей в deps-aware overload метода registerFactory объявляет рёбра на уровне типов и ограничивает resolver доступными ключами. В runtime фабрика получает этот resolver. У registerAsyncFactory другой контракт: InferDI разрешает кортеж и передаёт в callback позиционные значения.
Из-за этого отличается и порядок аргументов:
registerFactory(key, factory, deps, lifetime)
registerAsyncFactory(key, factory, deps, lifetime)Владение вложенными скоупами
Дочерний скоуп наследует входные значения, но создаёт собственные scoped-инстансы. Входными значениями владеет приложение, контейнер их не освобождает.
await using publicScope = openPublicScope(request)
await using authenticatedScope = publicScope.createScope({auth})
await authenticatedScope.getAsync('session')JavaScript освобождает эти переменные в обратном порядке: сначала уточнённый дочерний скоуп, затем его родитель. Вызов root.dispose() не закрывает ни один из них.
Переиспользуемые контракты типов
ScopeInputMap описывает входы именованного Module. WithRequirements прикрепляет требования к выходному сервису модуля.
import {
type ScopeInputMap,
type Spec,
type WithRequirements
} from '@inferdi/inferdi'
type RequestInputs = ScopeInputMap<{
request: RequestContext
auth: AuthContext
}>
type RequestServices = {
accountService: WithRequirements<
Spec<AccountService, 'scoped'>,
'request' | 'auth'
>
}Generic helpers тоже должны сохранять информацию о готовности:
function resolveSync<
T extends DependenciesMap,
K extends Container.SyncReadyKeys<Container<T>>
>(container: Container<T>, key: K) {
return container.get(key)
}
function resolveAny<
T extends DependenciesMap,
K extends Container.ReadyKeys<Container<T>>
>(container: Container<T>, key: K) {
return container.getAsync(key)
}Контракт входных данных
- В объявлении допустимы только обязательные конечные string- и symbol-ключи. TypeScript отклоняет optional- и numeric-ключи,
__proto__, широкие index signatures и union-типы с разными наборами ключей. - Обязательное свойство со значением
undefinedсчитается предоставленным. InferDI проверяет наличие свойства, а не truthiness. createScope(inputs)делает неглубокий снимок собственных enumerable string- и symbol-свойств. При копировании выполняются getters и Proxy traps, поэтому передавайте обычный объект с данными.- Схема существует только в TypeScript. JavaScript,
anyили cast могут добавить неизвестный ключ либо перекрыть регистрацию в кеше дочернего контейнера. {fast: true}поддерживает уточнение inputs, но сохраняет правило фиксированного графа: завершите регистрацию до первого.get()или.createScope().
Правила владения описаны в разделе Скоупы и освобождение ресурсов, а async-сервисы с входными данными скоупа — в Асинхронных зависимостях.
