@augment-vir/common
Version:
A collection of augments, helpers types, functions, and classes for any JavaScript environment.
78 lines (77 loc) • 2.73 kB
JavaScript
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
import { makeWritable } from '../type/writable.js';
import { getOrSetFromMap } from './get-or-set.js';
/**
* Map all ancestor constructors of an object to a set of objects.
*
* @category Internal
*/
export class ConstructorInstanceMap {
topMostConstructor;
/** A map of constructors to their added instances. */
map = new Map();
isDestroyed = false;
constructor(
/**
* The top most constructor to allow in the map. If this constructor is ever reached, the
* recursive mapping stops.
*/
topMostConstructor) {
this.topMostConstructor = topMostConstructor;
}
/**
* Add a new instance, mapping each of its ancestor constructors to it inside
* {@link ConstructorInstanceMap.map}.
*/
add(instance) {
if (this.isDestroyed) {
throw new Error('Cannot operate on destroyed ConstructorMap.');
}
this.traverseConstructors(instance, Object.getPrototypeOf(instance), 'add');
}
/** Gets all added instances of the given constructor. */
getInstances(constructor) {
if (this.isDestroyed) {
throw new Error('Cannot operate on destroyed ConstructorMap.');
}
return getOrSetFromMap(this.map, constructor, () => new Set());
}
/** Remove an instance, removing it from all mappings inside {@link ConstructorInstanceMap.map}. */
remove(instance) {
if (this.isDestroyed) {
throw new Error('Cannot operate on destroyed ConstructorMap.');
}
this.traverseConstructors(instance, Object.getPrototypeOf(instance), 'remove');
}
/** Recursively map all ancestor prototypes to the given instance. */
traverseConstructors(instance, prototype, operation) {
const constructor = prototype.constructor;
if (!constructor ||
constructor === Function ||
constructor === Object ||
constructor === this.topMostConstructor) {
/** Stop recursing into constructors. */
return;
}
if (operation === 'add') {
const set = getOrSetFromMap(this.map, constructor, () => new Set());
set.add(instance);
}
else {
const set = this.map.get(constructor);
if (set) {
set.delete(instance);
}
}
this.traverseConstructors(instance, Object.getPrototypeOf(prototype), operation);
}
/** Clean up the internal map. */
destroy() {
if (this.isDestroyed) {
return;
}
makeWritable(this).isDestroyed = true;
this.map.clear();
delete this.map;
}
}