Biblioteca leve de Injeção de Dependências em TypeScript — baseada em decorators e um container instanciável.
Este README descreve a API pública, conceitos e comportamentos observados nos testes.
- Token: identificador usado para mapear dependências. Pode ser uma
stringou umaclasse. - Formas de criação:
useValue(valor),useFactory(função que retorna valor) euseClass(outra classe a ser instanciada pelo container). - Scope:
Scope.REQUEST(padrão) cria instância por resolução;Scope.SINGLETONcria uma vez e reutiliza.
- 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 (
- 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 nullScope.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.
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.
- 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 |