@travetto/di
Advanced tools
Comparing version 2.0.0-alpha.3 to 2.0.0-alpha.4
{ | ||
"author": { | ||
"email": "travetto.framework@gmail.com", | ||
"name": "Travetto Framework" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"dependencies": { | ||
"@travetto/registry": "^2.0.0-alpha.3" | ||
}, | ||
"devDependencies": { | ||
"@travetto/config": "^2.0.0-alpha.2" | ||
}, | ||
"title": "Dependency Injection", | ||
"name": "@travetto/di", | ||
"displayName": "Dependency Injection", | ||
"version": "2.0.0-alpha.4", | ||
"description": "Dependency registration/management and injection support.", | ||
"homepage": "https://travetto.io", | ||
"keywords": [ | ||
@@ -26,10 +14,15 @@ "ast-transformations", | ||
], | ||
"homepage": "https://travetto.io", | ||
"license": "MIT", | ||
"main": "index.ts", | ||
"author": { | ||
"email": "travetto.framework@gmail.com", | ||
"name": "Travetto Framework" | ||
}, | ||
"files": [ | ||
"index.ts", | ||
"src", | ||
"support" | ||
"support", | ||
"test-support" | ||
], | ||
"name": "@travetto/di", | ||
"main": "index.ts", | ||
"repository": { | ||
@@ -39,4 +32,16 @@ "url": "https://github.com/travetto/travetto.git", | ||
}, | ||
"version": "2.0.0-alpha.3", | ||
"gitHead": "2f2ca49b7cb4cb89ba7f565b8cf2bd5d63fb0696" | ||
"dependencies": { | ||
"@travetto/transformer": "2.0.0-alpha.3", | ||
"@travetto/registry": "2.0.0-alpha.4" | ||
}, | ||
"devDependencies": { | ||
"@travetto/config": "2.0.0-alpha.3" | ||
}, | ||
"docDependencies": { | ||
"@travetto/model-mongo": "2.0.0-alpha.4", | ||
"@travetto/rest": "2.0.0-alpha.5" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
@@ -1,3 +0,3 @@ | ||
<!-- This file was generated by the framweork and should not be modified directly --> | ||
<!-- Please modify https://github.com/travetto/travetto/tree/master/module/di/doc.ts and execute "npm run docs" to rebuild --> | ||
<!-- This file was generated by @travetto/doc and should not be modified directly --> | ||
<!-- Please modify https://github.com/travetto/travetto/tree/master/module/di/doc.ts and execute "npx trv doc" to rebuild --> | ||
# Dependency Injection | ||
@@ -14,3 +14,3 @@ ## Dependency registration/management and injection support. | ||
## Declaration | ||
The [@Injectable](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L29) and [@InjectableFactory](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L71) decorators provide the registration of dependencies. Dependency declaration revolves around exposing `class`es and subtypes thereof to provide necessary functionality. Additionally, the framework will utilize dependencies to satisfy contracts with various implementations (e.g. [MongoModelService](https://github.com/travetto/travetto/tree/master/module/model-mongo/src/service.ts#L78) provides itself as an injectable candidate for [ModelCrudSupport](https://github.com/travetto/travetto/tree/master/module/model/src/service/crud.ts). | ||
The [@Injectable](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L30) and [@InjectableFactory](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L72) decorators provide the registration of dependencies. Dependency declaration revolves around exposing `class`es and subtypes thereof to provide necessary functionality. Additionally, the framework will utilize dependencies to satisfy contracts with various implementations (e.g. [MongoModelService](https://github.com/travetto/travetto/tree/master/module/model-mongo/src/service.ts#L45) provides itself as an injectable candidate for [ModelCrudSupport](https://github.com/travetto/travetto/tree/master/module/model/src/service/crud.ts). | ||
@@ -76,3 +76,3 @@ **Code: Example Injectable** | ||
In this scenario, `SpecificService` is a valid candidate for `BaseService` due to the abstract inheritance. Sometimes, you may want to provide a slight variation to a dependency without extending a class. To this end, the [@InjectableFactory](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L71) decorator denotes a `static` class method that produces an [@Injectable](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L29). | ||
In this scenario, `SpecificService` is a valid candidate for `BaseService` due to the abstract inheritance. Sometimes, you may want to provide a slight variation to a dependency without extending a class. To this end, the [@InjectableFactory](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L72) decorator denotes a `static` class method that produces an [@Injectable](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L30). | ||
@@ -98,11 +98,11 @@ **Code: Example InjectableFactory** | ||
**Note**: Other modules are able to provide aliases to [@Injectable](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L29) that also provide additional functionality. For example, the [@Config](https://github.com/travetto/travetto/tree/master/module/config/src/decorator.ts#L10) or the [@Controller](https://github.com/travetto/travetto/tree/master/module/rest/src/decorator/controller.ts#L9) decorator registers the associated class as an injectable element. | ||
**Note**: Other modules are able to provide aliases to [@Injectable](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L30) that also provide additional functionality. For example, the [@Config](https://github.com/travetto/travetto/tree/master/module/config/src/decorator.ts#L10) or the [@Controller](https://github.com/travetto/travetto/tree/master/module/rest/src/decorator/controller.ts#L9) decorator registers the associated class as an injectable element. | ||
## Injection | ||
Once all of your necessary dependencies are defined, now is the time to provide those [@Injectable](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L29) instances to your code. There are three primary methods for injection: | ||
Once all of your necessary dependencies are defined, now is the time to provide those [@Injectable](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L30) instances to your code. There are three primary methods for injection: | ||
The [@Inject](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L29) decorator, which denotes a desire to inject a value directly. These will be set post construction. | ||
The [@Inject](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L30) decorator, which denotes a desire to inject a value directly. These will be set post construction. | ||
**Code: Example Injectable with dependencies as [@Inject](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L29) fields** | ||
**Code: Example Injectable with dependencies as [@Inject](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L30) fields** | ||
```typescript | ||
@@ -123,3 +123,3 @@ import { Injectable, Inject } from '@travetto/di'; | ||
The [@Injectable](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L29) constructor params, which will be provided as the instance is being constructed. | ||
The [@Injectable](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L30) constructor params, which will be provided as the instance is being constructed. | ||
@@ -141,3 +141,3 @@ **Code: Example Injectable with dependencies in constructor** | ||
Via [@InjectableFactory](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L71) params, which are comparable to constructor params | ||
Via [@InjectableFactory](https://github.com/travetto/travetto/tree/master/module/di/src/decorator.ts#L72) params, which are comparable to constructor params | ||
@@ -218,2 +218,1 @@ **Code: Example InjectableFactory with parameters as dependencies** | ||
``` | ||
import { Class, ClassInstance } from '@travetto/base'; | ||
import { MethodDescriptor } from '@travetto/base/src/internal/types'; | ||
@@ -39,3 +40,3 @@ import { InjectableFactoryConfig, InjectableConfig, Dependency } from './types'; | ||
export type InjectConfig = { qualifier?: symbol, optional?: boolean }; | ||
export type InjectConfig = { qualifier?: symbol, optional?: boolean, resolution?: 'loose' | 'strict' }; | ||
@@ -73,3 +74,3 @@ export function InjectArgs(configs?: InjectConfig[][]) { | ||
export function InjectableFactory(first?: Partial<InjectableFactoryConfig> | symbol, ...args: (Partial<InjectableFactoryConfig> | undefined)[]) { | ||
return <T extends Class>(target: T, property: string | symbol, descriptor: TypedPropertyDescriptor<any>) => { | ||
return <T extends Class>(target: T, property: string | symbol, descriptor: MethodDescriptor) => { | ||
const config: InjectableFactoryConfig = collapseConfig(first, ...args); | ||
@@ -79,3 +80,3 @@ DependencyRegistry.registerFactory({ | ||
dependencies: config.dependencies?.map(x => Array.isArray(x) ? collapseConfig(...x) : collapseConfig(x)), | ||
fn: descriptor.value, | ||
fn: descriptor.value!, | ||
id: `${target.ᚕid}#${property.toString()}` | ||
@@ -82,0 +83,0 @@ }); |
@@ -10,2 +10,4 @@ import { Class, ClassInstance, ConcreteClass } from '@travetto/base'; | ||
type ClassId = string; | ||
type Resolution = 'strict' | 'loose'; | ||
type Resolved<T> = { config: InjectableConfig<T>, qualifier: symbol, id: string }; | ||
@@ -49,3 +51,3 @@ const PrimaryCandidateSym = Symbol.for('@trv:di/primary'); | ||
*/ | ||
protected resolveTarget<T>(target: ClassTarget<T>, qualifier?: symbol) { | ||
protected resolveTarget<T>(target: ClassTarget<T>, qualifier?: symbol, resolution?: Resolution): Resolved<T> { | ||
const qualifiers = this.targetToClass.get(target.ᚕid) ?? new Map<symbol, string>(); | ||
@@ -60,5 +62,7 @@ | ||
if (!qualifier) { | ||
// If primary found | ||
if (qualifiers.has(PrimaryCandidateSym)) { | ||
qualifier = PrimaryCandidateSym; | ||
} else { | ||
// If there is only one default symbol | ||
const filtered = resolved.filter(x => !!x).filter(x => this.defaultSymbols.has(x)); | ||
@@ -68,3 +72,9 @@ if (filtered.length === 1) { | ||
} else if (filtered.length > 1) { | ||
throw new InjectionError('Dependency has multiple candidates', target, filtered); | ||
// If dealing with sub types, prioritize exact matches | ||
const exact = this.getCandidateTypes(target as Class).filter(x => x.class === target); | ||
if (exact.length === 1) { | ||
qualifier = exact[0].qualifier; | ||
} else { | ||
throw new InjectionError('Dependency has multiple candidates', target, filtered); | ||
} | ||
} | ||
@@ -77,2 +87,6 @@ } | ||
} else if (!qualifiers.has(qualifier)) { | ||
if (!this.defaultSymbols.has(qualifier) && resolution === 'loose') { | ||
console.debug('Unable to find specific dependency, falling back to general instance', { qualifier, target: target.ᚕid }); | ||
return this.resolveTarget(target); | ||
} | ||
throw new InjectionError('Dependency not found', target, [qualifier]); | ||
@@ -102,6 +116,5 @@ } else { | ||
try { | ||
return await this.getInstance(x.target, x.qualifier); | ||
return await this.getInstance(x.target, x.qualifier, x.resolution); | ||
} catch (e) { | ||
if (x.optional && e instanceof InjectionError && e.category === 'notfound') { | ||
return undefined; | ||
@@ -121,3 +134,6 @@ } else { | ||
*/ | ||
protected async resolveFieldDependencies<T>(keys: string[], config: InjectableConfig<T>, instance: T) { | ||
protected async resolveFieldDependencies<T>(config: InjectableConfig<T>, instance: T) { | ||
const keys = Object.keys(config.dependencies.fields ?? {}) | ||
.filter(k => instance[k as keyof T] === undefined); // Filter out already set ones | ||
// And auto-wire | ||
@@ -146,8 +162,4 @@ if (keys.length) { | ||
// Compute fields to be auto-wired | ||
const fieldKeys = Object.keys(managed.dependencies.fields!) | ||
.filter(x => inst[x as keyof typeof inst] === undefined); // Only apply fields that have not been set | ||
// And auto-wire fields | ||
await this.resolveFieldDependencies(fieldKeys, managed, inst); | ||
await this.resolveFieldDependencies(managed, inst); | ||
@@ -159,4 +171,3 @@ // If factory with field properties on the sub class | ||
if (resolved) { | ||
const subKeys = Object.keys(resolved.dependencies.fields).filter(x => !managed.dependencies.fields[x]); | ||
await this.resolveFieldDependencies(subKeys, resolved, inst); | ||
await this.resolveFieldDependencies(resolved, inst); | ||
} | ||
@@ -209,2 +220,3 @@ } | ||
this.defaultSymbols.delete(qualifier); | ||
this.instances.get(classId)!.delete(qualifier); | ||
@@ -252,6 +264,6 @@ this.instancePromises.get(classId)!.delete(qualifier); | ||
*/ | ||
async getInstance<T>(target: ClassTarget<T>, qual?: symbol): Promise<T> { | ||
async getInstance<T>(target: ClassTarget<T>, qual?: symbol, resolution?: Resolution): Promise<T> { | ||
this.verifyInitialized(); | ||
const { id: classId, qualifier } = this.resolveTarget(target, qual); | ||
const { id: classId, qualifier } = this.resolveTarget(target, qual, resolution); | ||
if (!this.instances.has(classId) || !this.instances.get(classId)!.has(qualifier)) { | ||
@@ -437,2 +449,6 @@ await this.createInstance(target, qualifier); // Wait for proxy | ||
this.classToTarget.get(classId)!.set(Symbol.for(el.ᚕid), el.ᚕid); | ||
if (config.primary && (classId === targetId || config.factory)) { | ||
this.targetToClass.get(el.ᚕid)!.set(PrimaryCandidateSym, classId); | ||
} | ||
} | ||
@@ -505,4 +521,13 @@ | ||
} | ||
/** | ||
* Inject fields into instance | ||
*/ | ||
async injectFields<T extends { constructor: Class<T> }>(o: T, cls = o.constructor as Class<T>) { | ||
this.verifyInitialized(); | ||
// Compute fields to be auto-wired | ||
return await this.resolveFieldDependencies(this.get(cls), o); | ||
} | ||
} | ||
export const DependencyRegistry = new $DependencyRegistry(); |
@@ -27,2 +27,8 @@ import { Class } from '@travetto/base'; | ||
optional?: boolean; | ||
/** | ||
* Whether or not resolution of dependency should be flexible, | ||
* or should be strict. Default is strict. | ||
*/ | ||
resolution?: 'loose' | 'strict'; | ||
} | ||
@@ -29,0 +35,0 @@ |
@@ -1,8 +0,8 @@ | ||
import { CliUtil } from '@travetto/cli/src/util'; | ||
import { EnvInit } from '@travetto/base/bin/init'; | ||
export async function invoke(...[mod, cls, method, qualifier]: (string | undefined)[]) { | ||
CliUtil.initEnv({}); | ||
await (await import('@travetto/base')).PhaseManager.init(); | ||
EnvInit.init({}); | ||
await (await import('@travetto/base')).PhaseManager.run('init'); | ||
const inst = await (await import('../src/registry')).DependencyRegistry | ||
.getInstance(require(mod!)[cls!], qualifier ? Symbol.for(qualifier) : qualifier as undefined); | ||
.getInstance((await import(mod!))[cls!], qualifier ? Symbol.for(qualifier) : qualifier as undefined); | ||
return await (inst as Record<string, () => Promise<unknown>>)[method!](); | ||
@@ -9,0 +9,0 @@ } |
@@ -16,3 +16,3 @@ import { RetargettingProxy } from '@travetto/watch'; | ||
const Cls = class extends $DependencyRegistry { | ||
private proxies = new Map<string, Map<symbol | undefined, RetargettingProxy<unknown>>>(); | ||
#proxies = new Map<string, Map<symbol | undefined, RetargettingProxy<unknown>>>(); | ||
@@ -24,14 +24,14 @@ /** | ||
const { qualifier, id: classId } = this.resolveTarget(target, qual); | ||
let proxy: RetargettingProxy<T>; | ||
let proxy: RetargettingProxy<unknown>; | ||
if (!this.proxies.has(classId)) { | ||
this.proxies.set(classId, new Map()); | ||
if (!this.#proxies.has(classId)) { | ||
this.#proxies.set(classId, new Map()); | ||
} | ||
if (!this.proxies.get(classId)!.has(qualifier)) { | ||
proxy = new RetargettingProxy(instance); | ||
this.proxies.get(classId)!.set(qualifier, proxy); | ||
if (!this.#proxies.get(classId)!.has(qualifier)) { | ||
proxy = new RetargettingProxy<unknown>(instance); | ||
this.#proxies.get(classId)!.set(qualifier, proxy); | ||
console.debug('Registering proxy', { id: target.ᚕid, qualifier: qualifier.toString() }); | ||
} else { | ||
proxy = this.proxies.get(classId)!.get(qualifier)! as RetargettingProxy<T>; | ||
proxy = this.#proxies.get(classId)!.get(qualifier)! as RetargettingProxy<unknown>; | ||
proxy.setTarget(instance); | ||
@@ -43,3 +43,3 @@ console.debug('Updating target', { | ||
return proxy.get(); | ||
return proxy.get() as T; | ||
} | ||
@@ -69,4 +69,4 @@ | ||
!cls.ᚕabstract && | ||
this.proxies.has(classId) && | ||
this.proxies.get(classId)!.has(config.qualifier) | ||
this.#proxies.has(classId) && | ||
this.#proxies.get(classId)!.has(config.qualifier) | ||
) { | ||
@@ -83,3 +83,3 @@ console.debug('Reloading on next tick'); | ||
const classId = cls.ᚕid; | ||
const proxy = this.proxies.get(classId)!.get(qualifier); | ||
const proxy = this.#proxies.get(classId)!.get(qualifier); | ||
super.destroyInstance(cls, qualifier); | ||
@@ -93,3 +93,3 @@ if (proxy) { | ||
super.reset(); | ||
this.proxies.clear(); | ||
this.#proxies.clear(); | ||
} | ||
@@ -96,0 +96,0 @@ }; |
39605
12
818
2
213
+ Added@travetto/base@2.0.0-alpha.3(transitive)
+ Added@travetto/boot@2.0.0-alpha.3(transitive)
+ Added@travetto/compiler@2.0.0-alpha.3(transitive)
+ Added@travetto/registry@2.0.0-alpha.4(transitive)
+ Added@travetto/transformer@2.0.0-alpha.3(transitive)
+ Added@travetto/watch@2.0.0-alpha.3(transitive)
+ Added@types/node@12.20.55(transitive)
- Removed@travetto/base@2.2.4(transitive)
- Removed@travetto/boot@2.2.2(transitive)
- Removed@travetto/compiler@2.2.4(transitive)
- Removed@travetto/registry@2.2.4(transitive)
- Removed@travetto/transformer@2.2.4(transitive)
- Removed@travetto/watch@2.2.4(transitive)
- Removed@types/node@18.19.78(transitive)
- Removedundici-types@5.26.5(transitive)