/** * Stores a map of objects with weak references to the keys, allowing them to be garbage collected. Both keys and values * can be iterated over, unlike a WeakMap. */ export default class IterableWeakMap extends WeakMap { /** * @typedef {object} IterableWeakMapHeldValue * @property {Set>} set The set to be cleaned. * @property {WeakRef} ref The ref to remove. */ /** * @typedef {object} IterableWeakMapValue * @property {any} value The value. * @property {WeakRef} ref The weak ref of the key. */ /** * A set of weak refs to the map's keys, allowing enumeration. * @type {Set>} */ #refs = new Set(); /** * A FinalizationRegistry instance to clean up the ref set when objects are garbage collected. * @type {FinalizationRegistry} */ #finalizer = new FinalizationRegistry(IterableWeakMap.#cleanup); /** * @param {Iterable<[any, any]>} [entries] The initial entries. */ constructor(entries=[]) { super(); for ( const [key, value] of entries ) this.set(key, value); } /* -------------------------------------------- */ /** * Clean up the corresponding ref in the set when its value is garbage collected. * @param {IterableWeakMapHeldValue} heldValue The value held by the finalizer. */ static #cleanup({ set, ref }) { set.delete(ref); } /* -------------------------------------------- */ /** * Remove a key from the map. * @param {any} key The key to remove. * @returns {boolean} */ delete(key) { const entry = super.get(key); if ( !entry ) return false; super.delete(key); this.#refs.delete(entry.ref); this.#finalizer.unregister(key); return true; } /* -------------------------------------------- */ /** * Retrieve a value from the map. * @param {any} key The value's key. * @returns {any} */ get(key) { const entry = super.get(key); return entry && entry.value; } /* -------------------------------------------- */ /** * Place a value in the map. * @param {any} key The key. * @param {any} value The value. * @returns {IterableWeakMap} */ set(key, value) { const entry = super.get(key); if ( entry ) this.#refs.delete(entry.ref); const ref = new WeakRef(key); super.set(key, { value, ref }); this.#refs.add(ref); this.#finalizer.register(key, { ref, set: this.#refs }, key); return this; } /* -------------------------------------------- */ /** * Clear all values from the map. */ clear() { for ( const ref of this.#refs ) { const key = ref.deref(); if ( key ) this.delete(key); else this.#refs.delete(ref); } } /* -------------------------------------------- */ /** * Enumerate the entries. * @returns {Generator<[any, any], void, any>} */ *[Symbol.iterator]() { for ( const ref of this.#refs ) { const key = ref.deref(); if ( !key ) continue; const { value } = super.get(key); yield [key, value]; } } /* -------------------------------------------- */ /** * Enumerate the entries. * @returns {Generator<[any, any], void, any>} */ entries() { return this[Symbol.iterator](); } /* -------------------------------------------- */ /** * Enumerate the keys. * @returns {Generator} */ *keys() { for ( const [key] of this ) yield key; } /* -------------------------------------------- */ /** * Enumerate the values. * @returns {Generator} */ *values() { for ( const [, value] of this ) yield value; } }