UNPKG

@travetto/registry

Version:

Patterns and utilities for handling registration of metadata and functionality for run-time use

191 lines (166 loc) 4.7 kB
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)); } } }