@travetto/registry
Version:
Patterns and utilities for handling registration of metadata and functionality for run-time use
191 lines (166 loc) • 4.7 kB
text/typescript
import { Class } from '@travetto/runtime';
import { Registry } from '../registry';
import { ChangeEvent } from '../types';
function id(cls: string | Class): string {
return cls && typeof cls !== 'string' ? cls.Ⲑid : cls;
}
/**
* Metadata registry
*/
export abstract class MetadataRegistry<C extends { class: Class }, M = unknown, F = Function> extends Registry {
static id = id;
/**
* Classes pending removal
*/
expired = new Map<string, C>();
/**
* Classes pending creation
*/
pending = new Map<string, Partial<C>>();
/**
* Fields pending creation
*/
pendingFields = new Map<string, Map<F, Partial<M>>>();
/**
* Active items
*/
entries = new Map<string, C>();
/**
* Code to call when the installation is finalized
*/
abstract onInstallFinalize<T>(cls: Class<T>): C;
/**
* Code to call when uninstall is finalized
*/
onUninstallFinalize<T>(cls: Class<T>): void {
}
/**
* Create a pending class. Items are pending until the registry is activated
*/
abstract createPending(cls: Class): Partial<C>;
/**
* Is class found by id or by Class
*/
has(cls: string | Class): boolean {
return this.entries.has(id(cls));
}
/**
* Get class by id or by Class
*/
get(cls: string | Class): C {
return this.entries.get(id(cls))!;
}
/**
* Retrieve the class that is being removed
*/
getExpired(cls: string | Class): C {
return this.expired.get(id(cls))!;
}
/**
* Is there a class that is expiring
*/
hasExpired(cls: string | Class): boolean {
return this.expired.has(id(cls));
}
/**
* Is there a pending state for the class
*/
hasPending(cls: string | Class): boolean {
return this.pending.has(id(cls));
}
/**
* Get list of all classes that have been registered
*/
getClasses(): Class[] {
return Array.from(this.entries.values()).map(x => x.class);
}
/**
* Trigger initial install, moves pending to finalized (active)
*/
override initialInstall(): Class[] {
return Array.from(this.pending.values()).map(x => x.class).filter(x => !!x);
}
/**
* Create a pending field
*/
createPendingField(cls: Class, field: F): Partial<M> {
return {};
}
/**
* Find parent class for a given class object
*/
getParentClass(cls: Class): Class | null {
const parent: Class = Object.getPrototypeOf(cls);
return parent.name && parent !== Object ? parent : null;
}
/**
* Get or create a pending class
*/
getOrCreatePending(cls: Class): Partial<C> {
const cid = id(cls);
if (!this.pending.has(cid)) {
this.pending.set(cid, this.createPending(cls));
this.pendingFields.set(cid, new Map());
}
return this.pending.get(cid)!;
}
/**
* Get or create a pending field
*/
getOrCreatePendingField(cls: Class, field: F): Partial<M> {
this.getOrCreatePending(cls);
const classId = cls.Ⲑid;
if (!this.pendingFields.get(classId)!.has(field)) {
this.pendingFields.get(classId)!.set(field, this.createPendingField(cls, field));
}
return this.pendingFields.get(classId)!.get(field)!;
}
/**
* Register a pending class, with partial config to overlay
*/
register(cls: Class, pConfig: Partial<C> = {}): void {
const conf = this.getOrCreatePending(cls);
Object.assign(conf, pConfig);
}
/**
* Register a pending field, with partial config to overlay
*/
registerField(cls: Class, field: F, pConfig: Partial<M>): void {
const conf = this.getOrCreatePendingField(cls, field);
Object.assign(conf, pConfig);
}
/**
* On an install event, finalize
*/
onInstall(cls: Class, e: ChangeEvent<Class>): void {
const classId = cls.Ⲑid;
if (this.pending.has(classId) || this.pendingFields.has(classId)) {
if (this.trace) {
console.debug('Installing', { service: this.constructor.name, id: classId });
}
const result = this.onInstallFinalize(cls);
this.pendingFields.delete(classId);
this.pending.delete(classId);
this.entries.set(classId, result);
this.emit(e);
}
}
/**
* On an uninstall event, remove
*/
onUninstall(cls: Class, e: ChangeEvent<Class>): void {
const classId = cls.Ⲑid;
if (this.entries.has(classId)) {
if (this.trace) {
console.debug('Uninstalling', { service: this.constructor.name, id: classId });
}
this.expired.set(classId, this.entries.get(classId)!);
this.entries.delete(classId);
this.onUninstallFinalize(cls);
if (e.type === 'removing') {
this.emit(e);
}
process.nextTick(() => this.expired.delete(classId));
}
}
}