import { entries, get, has, isObservableObject, keys, remove, runInAction, set, values } from "mobx" import { failure } from "../error/failure" import { isPlainObject } from "../plainTypes/checks" class PlainObjectMap implements Map { constructor(private readonly data: Record) {} clear(): void { for (const key of Object.keys(this.data)) { delete this.data[key] } } delete(key: string): boolean { if (this.has(key)) { delete this.data[key] return true } return false } forEach(callbackfn: (value: V, key: string, map: Map) => void, thisArg?: any): void { for (const key of Object.keys(this.data)) { callbackfn.call(thisArg, this.data[key], key, this) } } get(key: string): V | undefined { return this.data[key] } has(key: string): boolean { return Object.hasOwn(this.data, key) } set(key: string, value: V): this { this.data[key] = value return this } get size(): number { return Object.keys(this.data).length } *entries(): ReturnType["entries"]> { yield* Object.entries(this.data) } *keys(): ReturnType["keys"]> { yield* Object.keys(this.data) } *values(): ReturnType["values"]> { yield* Object.values(this.data) } [Symbol.iterator](): ReturnType[typeof Symbol.iterator]> { return this.entries() } readonly [Symbol.toStringTag] = "PlainObjectMap" } class ObservableObjectMap implements Map { constructor(private readonly data: Record) {} clear(): void { runInAction(() => { for (const key of keys(this.data)) { remove(this.data, key as string) } }) } delete(key: string): boolean { return runInAction(() => { if (this.has(key)) { remove(this.data, key) return true } return false }) } forEach(callbackfn: (value: V, key: string, map: Map) => void, thisArg?: any): void { for (const key of keys(this.data)) { const value = this.get(key as string)! callbackfn.call(thisArg, value, key as string, this) } } get(key: string): V | undefined { return get(this.data, key) } has(key: string): boolean { return has(this.data, key) } set(key: string, value: V): this { runInAction(() => { set(this.data, key, value) }) return this } get size(): number { return keys(this.data).length } *entries(): ReturnType["entries"]> { yield* entries(this.data) } *keys(): ReturnType["keys"]> { yield* keys(this.data) as ReadonlyArray } *values(): ReturnType["values"]> { yield* values(this.data) } [Symbol.iterator](): ReturnType[typeof Symbol.iterator]> { return this.entries() } readonly [Symbol.toStringTag] = "ObservableObjectMap" } const mapCache = new WeakMap, Map>() /** * Returns a reactive Map-like view of the given object. * * The input must be a plain object or an observable object. * * @template V The type of the values in the object. * @param data The plain or observable object to wrap as a Map. * @returns A Map-like view of the object. */ export function asMap(data: Record): Map { if (!(isPlainObject(data) || isObservableObject(data))) { throw failure("asMap expects an object") } let map = mapCache.get(data) if (!map) { if (isObservableObject(data)) { map = new ObservableObjectMap(data) } else { map = new PlainObjectMap(data) } mapCache.set(data, map) } return map }