import { Equater, MaybeAsyncEquater } from "./types.js"; export interface EqualityMap extends Iterable<[K, V]> { readonly size: number; get(key: K): V | undefined; set(key: K, value: V): V | undefined; contains(key: K): boolean; remove(key: K): V | undefined; clear(): void; keys(): IterableIterator; values(): IterableIterator; entries(): IterableIterator<[K, V]>; } class NativeEqualityMap implements EqualityMap { readonly #map = new Map(); get size() { return this.#map.size; } get(key: K) { return this.#map.get(key); } set(key: K, value: V) { const existing = this.get(key); this.#map.set(key, value); return existing; } contains(key: K) { return this.#map.has(key); } remove(key: K) { const existing = this.get(key); this.#map.delete(key); return existing; } clear() { this.#map.clear(); } keys() { return this.#map.keys(); } values() { return this.#map.values(); } entries() { return this.#map.entries(); } [Symbol.iterator]() { return this.#map[Symbol.iterator](); } } class CustomEqualityMap implements EqualityMap { readonly #list: [K, V][] = []; readonly #keyComparer: Equater; constructor(keyComparer: Equater) { this.#keyComparer = keyComparer; } get size() { return this.#list.length; } get(key: K) { for (const entry of this.#list) { if (this.#keyComparer(key, entry[0])) { return entry[1]; } } return undefined; } set(key: K, value: V) { for (const entry of this.#list) { if (this.#keyComparer(key, entry[0])) { const previous = entry[1]; entry[1] = value; return previous; } } this.#list.push([key, value]); return undefined; } contains(key: K) { for (const entry of this.#list) { if (this.#keyComparer(key, entry[0])) { return true; } } return false; } remove(key: K) { for (let i = 0; i < this.#list.length; i++) { if (this.#keyComparer(key, this.#list[i][0])) { const removed = this.#list.splice(i, 1); return removed[0][1]; } } return undefined; } clear() { this.#list.length = 0; } *keys() { for (const entry of this.#list) { yield entry[0]; } } *values() { for (const entry of this.#list) { yield entry[1]; } } entries() { return this[Symbol.iterator](); } *[Symbol.iterator]() { for (const entry of this.#list) { yield [entry[0], entry[1]] as [K, V]; // no entry mutation allowed! } } } export function createEqualityMap(keyComparer?: Equater): EqualityMap { return keyComparer ? new CustomEqualityMap(keyComparer) : new NativeEqualityMap(); } export interface AsyncEqualityMap extends Iterable<[K, V]> { get(key: K): Promise; set(key: K, value: V): Promise; contains(key: K): Promise; remove(key: K): Promise; clear(): void; } class NativeAsyncEqualityMap implements AsyncEqualityMap { readonly #map = new Map(); async get(key: K) { return this.#map.get(key); } async set(key: K, value: V) { const existing = await this.get(key); this.#map.set(key, value); return existing; } async contains(key: K) { return this.#map.has(key); } async remove(key: K) { const existing = await this.get(key); this.#map.delete(key); return existing; } clear() { this.#map.clear(); } [Symbol.iterator]() { return this.#map[Symbol.iterator](); } } class CustomAsyncEqualityMap implements AsyncEqualityMap { readonly #list: [K, V][] = []; readonly #keyComparer: MaybeAsyncEquater; constructor(keyComparer: MaybeAsyncEquater) { this.#keyComparer = keyComparer; } async get(key: K) { for (const entry of this.#list) { if (await this.#keyComparer(key, entry[0])) { return entry[1]; } } return undefined; } async set(key: K, value: V) { for (const entry of this.#list) { if (await this.#keyComparer(key, entry[0])) { const previous = entry[1]; entry[1] = value; return previous; } } this.#list.push([key, value]); return undefined; } async contains(key: K) { for (const entry of this.#list) { if (await this.#keyComparer(key, entry[0])) { return true; } } return false; } async remove(key: K) { for (let i = 0; i < this.#list.length; i++) { if (await this.#keyComparer(key, this.#list[i][0])) { const removed = this.#list.splice(i, 1); return removed[0][1]; } } return undefined; } clear() { this.#list.length = 0; } *[Symbol.iterator]() { for (const entry of this.#list) { yield [entry[0], entry[1]] as [K, V]; // no entry mutation allowed! } } } export function createAsyncEqualityMap(keyComparer?: MaybeAsyncEquater): AsyncEqualityMap { return keyComparer ? new CustomAsyncEqualityMap(keyComparer) : new NativeAsyncEqualityMap(); }