Releases: esliph/injection
Release list
Release v1.4.0
Suporte a dependências circulares
Novidades
forwardRef(() => Token)— adia a leitura da referência de classe para o momento do uso, resolvendo o caso em que um import circular entre módulos deixa o tokenundefinedna avaliação do decorator. Aceito em parâmetro de construtor, propriedade, parâmetro de método e também emtoken/useClassnoregister.
// b.service.ts
export class ServiceB {
@Inject(forwardRef(() => ServiceA)) a: ServiceA
}- Ciclos de resolução agora funcionam quando pelo menos um dos lados injeta por propriedade: o container expõe a instância parcial durante a construção (
a.b.a === a). Antes, qualquer ciclo estourava comRangeError: Maximum call stack size exceeded. CircularDependencyInjectionException(códigoCIRCULAR_DEPENDENCY) para o ciclo construtor ↔ construtor, com o caminho do ciclo na mensagem (ServiceA -> ServiceB -> ServiceA) em vez do estouro de pilha.- Novos exports:
forwardRef,resolveForwardRef,isForwardRef,ForwardReferencee o tipoDependencyTokenInput.
Correções de comportamento
- Import circular sem
forwardRefnão injeta maisnullsilenciosamente — passa a lançar exceção tipada apontando o parâmetro ou a propriedade. Parâmetro que nunca foi decorado continua recebendonull, como antes. - Token
undefinedem propriedade lançava umTypeErrorcru (Cannot read properties of undefined); agora lançaCircularDependencyInjectionException.getTokenName(undefined)deixou de estourar.
Nota de tipos
getInjectTokensParams e getInjectTokensProperties passam a devolver DependencyTokenInput (a metadata guarda o ForwardReference cru, sem desembrulhar). Quem precisar do token concreto aplica resolveForwardRef. É mudança apenas de tipo, sem alteração de runtime.
Release 1.3.0
Features:
- Adicionando suporte à nova proposta de decorators do TC39 stage 3.
- Adicionado nova funcionalidade para resolução de dependências em parâmetros de métodos por meio do novo método
invoke.
Changed:
- Alterado uso do decorator
@Injectem parâmetros: Para injetar dependência em parâmetro o decorator deve ser aplicado no método e deve ser informado explicitamente o indíce do parâmetro; se o parâmetro for no construtor, deve-se aplicar o decorator na classe - para mais consulte a documentação aqui.
Release 1.2.1
Changed:
- Adaptado
Decorator.Genericpara aceitar decorators em parâmetros tanto para Typescript quanto Babel, ambos na versão legacy. - Alterado para armazenar os metadados dos tokens (
@Inject) e da configuração da dependência (@Injectable) no Reflect Metadata.
Release 1.2.0
Features
- Adicionado novo decorator
@Injectablepara simplificação dos registros das dependências.
Release 1.1.1
Features:
- Adaptando registro dos Tokens nas classes (via
@Inject) para gravar em repositorio interno de metadados ao invés de usarreflect-metadata.
Release 1.1.0
Decorator @Inject(token)
- Use em parâmetros do construtor ou em propriedades para declarar dependências que o container deve fornecer.
- Exemplos:
class A {}
class B {
constructor(@Inject('TOKEN') public t: number) {}
}
class C {
@Inject(A) service!: A
}- A biblioteca também expõe
getInjectTokens(target)que retorna os tokens registrados para parâmetros do construtor e propriedades:
const { constructorParams, properties } = getInjectTokens(MyClass)
// constructorParams: array com tokens ou `undefined` para posições não anotadas
// properties: objeto { propName: token }Tipos principais
DependencyToken=string | ClassConstructorDependencyCreation={ useClass?: ClassConstructor, useValue?: any, useFactory?: () => any }Dependency=DependencyCreation & { token: DependencyToken, scope: Scope }DependencyRegister=DependencyCreation & { token, scope? } | ClassConstructor(aceita também diretamente uma classe como atalho)
Observações:
- Ao registrar uma
ClassConstructordireto no array, o container a transforma em{ token: ClassConstructor, useClass: ClassConstructor, scope: Scope.REQUEST }.
DependencyRepository (armazenamento)
- Métodos públicos:
register(dependency: Dependency)— registra a dependênciaget(token: DependencyToken): Dependency | null— obtém dependência ounullquando não registradahas(token: DependencyToken): boolean— verifica existência
Use quando precisar de um repositório customizado (o container aceita um repositório via construtor).
DependencyContainer (API principal)
new DependencyContainer(repository?: DependencyRepository)register(dependencies: DependencyRegister[])— registra múltiplas dependênciasresolve<T = any>(token: DependencyToken): T— resolve e retorna a instância/valor correspondentehasDependency(token: DependencyToken): boolean— delega para o repositóriogetDependency(token: DependencyToken): Dependency | null— delega para o repositório interno
Comportamento importante de register e resolve:
registertransforma cada entrada em umDependencycomscopedefaultScope.REQUESTquando omitido.- Valida o token (apenas
stringouclasssão válidos). - Verifica se já existe dependência registrada com o mesmo token (lança
TokenAlreadyRegisteredInjectionException). - Valida que exatamente uma forma de criação foi definida:
useValue|useFactory|useClass. Caso contrário, lança exceções específicas. resolveaceita umtokenstring ou umaclass:- Se o token está registrado, resolve pelo tipo registrado (
useValue|useFactory|useClass). - Se o token não está registrado e for uma
class, tenta instanciar a classe (resolvendo recursivamente tokens anotados). - Se o token não estiver registrado e não for uma
class, lançaClassConstructorInvalidInjectionException.
- Se o token está registrado, resolve pelo tipo registrado (
Resolução de classes com @Inject
- Para cada posição indexada do construtor, se o token estiver
undefined(posição sem decorator) o container injetanullnessa posição. - Para parâmetros e propriedades anotadas com token, o container chama
resolveToken(token)para obter o valor.
Exemplo: posições sem token viram null:
class X {
constructor(@Inject('A') a: any, b: any, @Inject('C') c: any) {}
}
// constructorParams -> ['A', undefined, 'C']
// Ao instanciar, b será passado como nullEscopos
Scope.REQUEST(padrão): cria o valor/instância a cadaresolve.Scope.SINGLETON: armazena o resultado da primeira resolução e retorna a mesma instância nas próximas.
Validações e Exceções
Todas as exceções estendem InjectionException e têm o campo code com um valor de InjectionErrorCode.
Principais exceções e quando são lançadas:
| Exceção | Código | Causa |
|---|---|---|
InvalidTokenInjectionException |
TOKEN_INVALID |
quando se tenta usar como token algo que não é string nem class |
TokenAlreadyRegisteredInjectionException |
TOKEN_ALREADY_REGISTERED |
ao registrar um token já existente |
CreationMethodMissingInjectionException |
CREATION_METHOD_MISSING |
ao registrar sem useClass, useFactory ou useValue |
CreationMultipleMethodInjectionException |
CREATION_MULTIPLE_METHOD |
ao informar mais de um método de criação simultaneamente |
CreationMethodUseClassInjectionException |
CREATION_METHOD_USE_CLASS_INVALID |
useClass não é uma classe válida |
CreationMethodUseFactoryInjectionException |
CREATION_METHOD_USE_FACTORY_INVALID |
useFactory não é função |
TokenNotRegisteredInjectionException |
TOKEN_NOT_REGISTERED |
ao tentar resolver um token que não foi registrado |
ClassConstructorInvalidInjectionException |
CLASS_CONSTRUCTOR_INVALID |
ao chamar resolve passando algo que não é uma classe e que não está registrado |
Os códigos enumerados estão em InjectionErrorCode e ajudam a tratar erros sem depender da string da mensagem.
Exemplos práticos
- Registrar um valor simples:
container.register([{ token: 'CONFIG_PORT', useValue: 3000 }])
class App {
constructor(@Inject('CONFIG_PORT') public port: number) {}
}
const app = container.resolve(App)
// app.port === 3000- Registrar com
useFactory:
container.register([{ token: 'RAND', useFactory: () => Math.random() }])
class R {
constructor(@Inject('RAND') public r: number) {}
}
const a = container.resolve(R)
const b = container.resolve(R)
// a.r e b.r serão diferentes (REQUEST scope)- Registrar com
useClass/ alinhamento via classes:
class ServiceA {}
class ServiceB {
constructor(@Inject(ServiceA) public a: ServiceA) {}
}
// shorthand: registra ServiceA e ServiceB como useClass dos próprios tokens
container.register([ServiceA, ServiceB])
class App {
constructor(@Inject(ServiceB) public b: ServiceB) {}
}
const app = container.resolve(App)
// app.b instanceof ServiceB
// app.b.a instanceof ServiceA- Singleton:
class DB {
static created = 0
constructor() {
DB.created++
}
}
container.register([{ token: DB, useClass: DB, scope: Scope.SINGLETON }])
container.resolve(DB)
container.resolve(DB)
// DB.created === 1Erros comuns e como reproduzi-los (para tratamento)
| Exceção | Simulação |
|---|---|
InvalidTokenInjectionException |
usar 10, {}, [], () => {} como token |
TokenAlreadyRegisteredInjectionException |
registrar duas vezes com token: 'X' |
CreationMethodMissingInjectionException |
registrar { token: 'X' } sem use* |
CreationMultipleMethodInjectionException |
registrar { token: 'X', useClass: A, useValue: 1 } |
CreationMethodUseClassInjectionException |
useClass: 10 |
CreationMethodUseFactoryInjectionException |
useFactory: true |
TokenNotRegisteredInjectionException |
classe com @Inject('MISSING') quando 'MISSING' não foi registrado |
ClassConstructorInvalidInjectionException |
container.resolve('MISSING') quando 'MISSING' não é um token registrado |