UNPKG

@augment-vir/common

Version:

A collection of augments, helpers types, functions, and classes for any JavaScript environment.

78 lines (77 loc) 2.73 kB
/* 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; } }