Migración
InferDI registra los cambios incompatibles por versión major. La fuente de verdad sigue siendo packages/inferdi/MIGRATION.md, pero aquí se resume la ruta de migración actual.
Migración a 5.0
La release inicial de v5 solo afectó a los adaptadores. El incremento de versión mantiene todos los paquetes publicados en lockstep y alinea los adaptadores de frameworks alrededor de un único contrato de limpieza. Las builds posteriores de v5 también aplican la propiedad del scope hijo y endurecen el contrato de modo rápido descrito a continuación.
Los contratos de los adaptadores ahora comparten estas reglas:
createScope,setupScope,disposeScope,autoDisposeyonDisposeErrorusan el mismo vocabulario.MaybePromise,InferdiScope,InferdiRooteInferdiScopeOfse exportan en todos los adaptadores.- Si
setupScopefalla, el adaptador expone únicamente el error de setup original. - Los fallos de limpieza durante la liberación del setup van a
onDisposeErroro al sink del adaptador. - Una petición fallida libera su scope incluso después de
skipInferdiDispose, salvo por la limitación documentada de Express. - Los hooks de limpieza ven el slot público del scope mientras se ejecutan.
La resolución scoped requiere un scope hijo
Con el valor predeterminado strict: true, resolver una clave scoped desde la raíz ahora lanza Scoped "key" cannot be resolved from the root container. Use createScope(). Crea un contenedor hijo con const scope = root.createScope(), llama a scope.get(scopedKey) y libera el hijo en su límite de ciclo de vida. El modo rápido omite este guard en runtime, pero los servicios scoped deben seguir resolviéndose desde scopes hijos.
Contrato de grafo inmutable del modo rápido
new Container({ strict: false }) ahora lee el registro raíz inmutable directamente desde los scopes, evita recorrer los padres y refleja los singletons delegados en la caché del scope. Los scopes strict recorren su cadena exacta de padres en cada fallo local en lugar de conservar instantáneas de búsqueda, por lo que las mutaciones siguen visibles sin contabilidad de invalidación ni metadatos por scope. La deduplicación por identidad de instancias owned se ejecuta durante el disposal en ambos modos. Registra cada clave de runtime una sola vez mediante una cadena fluent lineal, completa el registro antes de la primera resolución o creación de scope, mantén inmutable el árbol activado y elimina los scopes hijos antes que sus ancestros. Usa strict: true para hot reload o cualquier árbol que cambie después de activarse.
Notas de los adaptadores
| Paquete | Notas de migración |
|---|---|
@inferdi/fastify | Renombra logDisposeError a onDisposeError; InferdiScope.dispose() puede devolver void o Promise<void>; se añadieron disposeScope, autoDispose, skipInferdiDispose e InferdiScopeOf. |
@inferdi/hono | Los fallos de limpieza después de next() se registran o se envían a onDisposeError; ya no reemplazan una respuesta exitosa. La liberación del setup ya no lanza AggregateError. |
@inferdi/express | onDisposeError es ahora un sink por error para la liberación del setup y la finalización de la respuesta. Express no puede forzar la liberación de un scope omitido ante un error de ruta manejado. |
@inferdi/koa | La liberación del setup expone únicamente el error de setup. Un error aguas abajo libera incluso después de skipInferdiDispose(ctx). |
@inferdi/elysia | La liberación del setup expone únicamente el error de setup. Un fallo de limpieza va a onDisposeError o a console.error. |
Migración a 4.0
v4 endurece la semántica de tiempo de vida de Lazy<T>. Un companion lazy gestionado ahora preserva el tiempo de vida del objetivo. Un singleton solo puede inyectar Lazy<singleton>.
Cambios principales:
AllowedDeps<T, 'singleton'>ya no acepta unLazy<V>arbitrario.LazySpec<V, TargetKind>pasó a ser un tipo público para formas explícitas de contenedores y módulos.- La exención lazy en runtime solo se aplica cuando el tipo del objetivo es
singleton. - Un singleton que inyectaba
Lazy<scoped>oLazy<transient>debe cambiar o el tiempo de vida del objetivo o el del consumidor.
Correcciones comunes:
// v3
.registerClass('req', RequestContext, [], 'scoped', 'reqLazy')
.registerClass('app', AppService, ['reqLazy'], 'singleton')
// v4: make the consumer scoped
.registerClass('req', RequestContext, [], 'scoped', 'reqLazy')
.registerClass('app', AppService, ['reqLazy'], 'scoped')// v3
type Deps = SpecMap<{ clock: Clock }> & {
clockLazy: Spec<Lazy<Clock>, 'transient'>
}
// v4
type Deps = SpecMap<{ clock: Clock }> & {
clockLazy: LazySpec<Clock, 'singleton'>
}Migración a 3.0
v3 traslada la seguridad de los tiempos de vida al sistema de tipos. El comportamiento en runtime se mantiene compatible, y los guards estrictos de runtime siguen siendo defensa en profundidad.
Cambios principales:
- Las entradas de
DependenciesMappasaron a serSpec<V, Kind>en lugar de tipos de servicio simples. RegistrationKind,Spec<V, K>ySpecMap<M, K>pasaron a ser exportaciones públicas.registerFactoryestrecha su parámetrocpara las factorías singleton.registerClassfiltradepspara los registros singleton.override(key, value)preserva el tipo de tiempo de vida original.new Container({ strict: false })puede deshabilitar los guards de ciclo y tiempo de vida en runtime tras una auditoría del grafo.
Correcciones comunes:
// v2
const c = new Container() as Container<{ a: A; b: B }>
// v3
const c = new Container() as Container<SpecMap<{ a: A; b: B }>>// v2
const mod: Module<{ cfg: Config }, { db: Db }> = (c) => ...
// v3
const mod: Module<
SpecMap<{ cfg: Config }>,
SpecMap<{ db: Db }>
> = (c) => ...Migración a 2.0
v2 tiene dos cambios incompatibles mecánicos.
Se eliminó container.cradle
Usa .get(key):
// 1.x
const { db, logger } = container.cradle
// 2.x
const db = container.get('db')
const logger = container.get('logger')registerClass(..., lazy: true) pasó a ser lazyKey
Pasa la clave del companion:
// 1.x
.registerClass('clock', Clock, [], 'transient', true)
// 2.x
.registerClass('clock', Clock, [], 'transient', 'clockLazy')v2 también añadió claves de tipo string o symbol a todos los métodos de registro y mejoró los diagnósticos de ancestro liberado.
Lockstep de versiones
Todos los paquetes publicados de InferDI comparten la misma versión:
Al actualizar los adaptadores, mantén el paquete del adaptador y @inferdi/inferdi en versiones major coincidentes.
Lista de verificación de actualización
- Lee las notas de migración de cada versión major que atravieses.
- Actualiza
@inferdi/inferdiy todos los adaptadores instalados juntos. - Ejecuta las pruebas de tipos o
tsc --noEmitpara detectar cambios en la forma del grafo. - Ejecuta las pruebas de runtime en modo estricto.
- Revisa la propiedad del scope de petición si usas
skipInferdiDispose,autoDispose: falseo undisposeScopepersonalizado.
Fronteras estables
El paquete core sigue siendo libre de decoradores y sin dependencias. El comportamiento del ciclo de vida de los frameworks vive en los paquetes de adaptadores, no en @inferdi/inferdi.
