/** * @module * * MeekValueMap data collection. */ /** * Private data. * * @template K Key type. * @template V Value type. */ interface Pri { /** * Finalization registry. */ readonly fr: FinalizationRegistry; /** * Map of keys to weak references to values. */ kwv: Map>; } const pri = new WeakMap(); const unregister = ( fr: FinalizationRegistry, wr?: WeakRef, ): unknown => wr && fr.unregister(wr); /** * Like WeakValueMap. * * @template K Key type. * @template V Value type. */ export class MeekValueMap { /** * Type string. */ declare public readonly [Symbol.toStringTag]: string; /** * Create a new MeekValueMap. * * @param iterable Initial pairs. */ constructor(iterable?: Iterable | null) { const kwv = new Map>(); const fr = new FinalizationRegistry(kwv.delete.bind(kwv)); for (const [key, value] of iterable ?? []) { unregister(fr, kwv.get(key)); const ref = new WeakRef(value); fr.register(value, key, ref); kwv.set(key, ref); } pri.set(this, { fr, kwv }); } /** * Iterator for key-value pairs in this map. * * @returns Key-value iterator. */ public *[Symbol.iterator](): MapIterator<[K, V]> { for (const [key, ref] of (pri.get(this) as Pri).kwv) { const value = ref.deref(); if (value) { yield [key, value]; } } } /** * Clear this map. */ public clear(): void { const { fr, kwv } = pri.get(this) as Pri; for (const [key, ref] of kwv) { fr.unregister(ref); kwv.delete(key); } } /** * Delete a key from this map. * * @param key Key to delete. * @returns Whether the key was deleted. */ public delete(key: K): boolean { const { fr, kwv } = pri.get(this) as Pri; const ref = kwv.get(key); if (ref) { fr.unregister(ref); return kwv.delete(key); } return false; } /** * Iterator for key-value pairs in this map. * * @returns Key-value iterator. */ public *entries(): MapIterator<[K, V]> { for (const [key, ref] of (pri.get(this) as Pri).kwv) { const value = ref.deref(); if (value) { yield [key, value]; } } } /** * Call a function for each pair in this map. * * @param callbackfn Callback function. * @param thisArg This argument. */ public forEach( callbackfn: (value: V, key: K, map: MeekValueMap) => void, thisArg?: any, ): void { for (const [key, ref] of (pri.get(this) as Pri).kwv) { const value = ref.deref(); if (value) { callbackfn.call(thisArg, value, key, this); } } } /** * Get the value for a key from this map. * * @param key Key to get. * @returns Value for the key. */ public get(key: K): V | undefined { return (pri.get(this) as Pri).kwv.get(key)?.deref(); } /** * Get the value for a key from this map or insert the default value. * * @param key Key to get. * @param defaultValue Default value. * @returns Value for the key. */ public getOrInsert(key: K, defaultValue: V): V { const { fr, kwv } = pri.get(this) as Pri; const old = kwv.get(key); if (old) { const r = old.deref(); if (r) { return r; } } const ref = new WeakRef(defaultValue); unregister(fr, old); fr.register(defaultValue, key, ref); kwv.set(key, ref); return defaultValue; } /** * Get the value for a key from this map or insert computed default value. * * @param key Key to get. * @param callback Compute the default value. * @returns Value for the key. */ public getOrInsertComputed(key: K, callback: (key: K) => V): V { const { fr, kwv } = pri.get(this) as Pri; const old = kwv.get(key); if (old) { const r = old.deref(); if (r) { return r; } } const value = callback(key); const ref = new WeakRef(value); unregister(fr, old); fr.register(value, key, ref); kwv.set(key, ref); return value; } /** * Has a key in this map. * * @param key Key to check. * @returns Whether the key is in this map. */ public has(key: K): boolean { return !!(pri.get(this) as Pri).kwv.get(key)?.deref(); } /** * Iterator for keys in this map. * * @returns Key iterator. */ public *keys(): MapIterator { for (const [key, ref] of (pri.get(this) as Pri).kwv) { if (ref.deref()) { yield key; } } } /** * Set a value for a key in this map. * * @param key Key to set. * @param value Value to set. * @returns This map. */ public set(key: K, value: V): this { const { fr, kwv } = pri.get(this) as Pri; const ref = new WeakRef(value); unregister(fr, kwv.get(key)); fr.register(value, key, ref); kwv.set(key, ref); return this; } /** * The number of keys in this map. * Can be greater than number of active keys. */ public get size(): number { return (pri.get(this) as Pri).kwv.size; } /** * Iterator for values in this map. * * @returns Value iterator. */ public *values(): MapIterator { for (const [, ref] of (pri.get(this) as Pri).kwv) { const value = ref.deref(); if (value) { yield value; } } } static { Object.defineProperty(this.prototype, Symbol.toStringTag, { value: 'MeekValueMap', configurable: true, enumerable: false, writable: false, }); } } /** * Readonly MeekValueMap. * * @template K Key type. * @template V Value type. */ export type ReadonlyMeekValueMap = Omit< MeekValueMap, 'clear' | 'delete' | 'getOrInsert' | 'getOrInsertComputed' | 'set' >;