Entradas de scope
Las entradas de scope son valores que entrega el código que abre un scope: una petición HTTP, un usuario autenticado, un tenant, el payload de un trabajo o el contexto de una traza. InferDI propaga esos requisitos por el grafo e impide resolver un servicio antes de que sus entradas estén disponibles.
Declarar las entradas
declareScopeInputs() añade claves al grafo de tipos. No crea registros ni valores en 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'
)Las entradas tienen lifetime scoped. Un singleton no puede depender de ellas, así que el compilador rechaza omitir el lifetime o declarar singleton en los servicios del ejemplo.
Abrir perfiles tipados
createScope(inputs) acepta cualquier subconjunto de las entradas pendientes. El tipo del contenedor devuelto registra qué requisitos están listos.
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')Usa funciones normales para dar nombre a los perfiles de la aplicación. Sus tipos de retorno conservan el conjunto exacto de claves listas sin anotaciones manuales del contenedor.
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>Abre el perfil en el límite de una petición, mensaje o trabajo. El grafo raíz puede mantenerse independiente de los objetos del framework.
Los requisitos se propagan por el grafo
InferDI propaga los requisitos por clases, companions lazy, factorías síncronas con dependencias declaradas y factorías async declarativas.
const app = root
.registerFactory(
'requestId',
(c) => c.get('request').requestId,
['request'],
'scoped'
)
.registerAsyncFactory(
'session',
async (auth: AuthContext) => loadSession(auth.userId),
['auth'],
'scoped'
)La tupla del overload de registerFactory con dependencias declara aristas a nivel de tipos y limita el callback a un resolver con esas claves. InferDI pasa ese resolver al callback en runtime. registerAsyncFactory usa otro contrato: resuelve la tupla y pasa valores posicionales al callback.
El orden de argumentos también cambia:
registerFactory(key, factory, deps, lifetime)
registerAsyncFactory(key, factory, deps, lifetime)Propiedad de scopes anidados
Un hijo hereda los valores de entrada y crea sus propias instancias scoped. La aplicación conserva la propiedad de las entradas; el contenedor no las libera.
await using publicScope = openPublicScope(request)
await using authenticatedScope = publicScope.createScope({auth})
await authenticatedScope.getAsync('session')JavaScript libera estas declaraciones en orden inverso, primero el hijo refinado y después su padre. root.dispose() no cierra ninguno de los dos scopes.
Contratos de tipos reutilizables
ScopeInputMap describe las entradas de un Module con nombre. WithRequirements añade requisitos de entrada a la salida del módulo.
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'
>
}Los helpers genéricos también deben conservar el estado de preparación:
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)
}Contrato de entrada
- Las declaraciones requieren claves string o symbol finitas y obligatorias. Se rechazan claves opcionales o numéricas,
__proto__, index signatures amplias y unions con conjuntos de claves distintos. - Una propiedad obligatoria con valor
undefinedcuenta como proporcionada. InferDI comprueba la presencia de la propiedad, no su truthiness. createScope(inputs)toma una copia superficial de las propiedades string y symbol propias y enumerables. Los getters y Proxy traps se ejecutan durante la copia; pasa un objeto de datos normal.- El schema de entradas solo existe en TypeScript. JavaScript,
anyo un cast pueden añadir claves desconocidas o tapar un registro en la caché del hijo. {fast: true}admite el refinamiento de entradas, pero conserva su regla de grafo fijo: termina los registros antes del primer.get()o.createScope().
Consulta Scopes y liberación de recursos para las reglas de propiedad y Dependencias asíncronas para servicios async que dependen de entradas de scope.
