@travetto/di
Version:
Dependency registration/management and injection support.
108 lines (94 loc) • 4.01 kB
text/typescript
import { Class, describeFunction } from '@travetto/runtime';
import { RetargettingProxy } from '@travetto/registry';
import type { DependencyRegistry, ResolutionType, Resolved } from '../src/registry';
import type { ClassTarget, InjectableConfig } from '../src/types';
/**
* Wraps the Dependency Registry to support proxying instances
*/
class $DynamicDependencyRegistry {
#proxies = new Map<string, Map<symbol | undefined, RetargettingProxy<unknown>>>();
#registry: typeof DependencyRegistry;
#registryCreateInstance: <T>(target: ClassTarget<T>, qualifier: symbol) => Promise<T>;
#registryResolveTarget: <T>(target: ClassTarget<T>, qualifier?: symbol, resolution?: ResolutionType) => Resolved<T>;
#registryOnInstallFinalize: <T>(target: Class<T>) => InjectableConfig<T>;
#registryDestroyInstance: <T>(target: Class<T>, qualifier: symbol) => void;
/**
* Proxy the created instance
*/
proxyInstance<T>(target: ClassTarget<T>, qual: symbol | undefined, instance: T): T {
const { qualifier, id: classId } = this.#registryResolveTarget(target, qual);
let proxy: RetargettingProxy<unknown>;
if (!this.#proxies.has(classId)) {
this.#proxies.set(classId, new Map());
}
if (!this.#proxies.get(classId)!.has(qualifier)) {
proxy = new RetargettingProxy<T>(instance);
this.#proxies.get(classId)!.set(qualifier, proxy);
if (this.#registry.trace) {
console.debug('Registering proxy', { id: target.Ⲑid, qualifier: qualifier.toString() });
}
} else {
proxy = this.#proxies.get(classId)!.get(qualifier)!;
proxy.setTarget(instance);
if (this.#registry.trace) {
console.debug('Updating target', {
id: target.Ⲑid, qualifier: qualifier.toString(), instanceType: target.name
});
}
}
return proxy.get<T>();
}
/**
* Create instance and wrap in a proxy
*/
async createInstance<T>(target: ClassTarget<T>, qualifier: symbol): Promise<T> {
const instance = await this.#registryCreateInstance(target, qualifier);
const classId = this.#registryResolveTarget(target, qualifier).id;
// Reset as proxy instance
const proxied = this.proxyInstance<T>(target, qualifier, instance);
this.#registry['instances'].get(classId)!.set(qualifier, proxied);
return proxied;
}
/**
* Reload proxy if in watch mode
*/
onInstallFinalize<T>(cls: Class<T>): InjectableConfig<T> {
const config = this.#registryOnInstallFinalize(cls);
// If already loaded, reload
const classId = cls.Ⲑid;
if (
!describeFunction(cls)?.abstract &&
this.#proxies.has(classId) &&
this.#proxies.get(classId)!.has(config.qualifier)
) {
console.debug('Reloading on next tick');
// Timing matters due to create instance being asynchronous
process.nextTick(() => this.createInstance(config.target, config.qualifier));
}
return config;
}
destroyInstance(cls: Class, qualifier: symbol): void {
const classId = cls.Ⲑid;
const proxy = this.#proxies.get(classId)?.get(qualifier);
this.#registryDestroyInstance(cls, qualifier);
if (proxy) {
proxy.setTarget(null);
}
}
register(registry: typeof DependencyRegistry): void {
this.#registry = registry;
this.#registryCreateInstance = registry['createInstance'].bind(registry);
this.#registryResolveTarget = registry['resolveTarget'].bind(registry);
this.#registryOnInstallFinalize = registry['onInstallFinalize'].bind(registry);
this.#registryDestroyInstance = registry['destroyInstance'].bind(registry);
this.#registry['createInstance'] = this.createInstance.bind(this);
this.#registry['destroyInstance'] = this.destroyInstance.bind(this);
this.#registry['onInstallFinalize'] = this.onInstallFinalize.bind(this);
}
}
export const DependencyRegistration = {
init(registry: typeof DependencyRegistry): void {
const dynamic = new $DynamicDependencyRegistry();
dynamic.register(registry);
}
};