UNPKG

@tldraw/store

Version:

tldraw infinite canvas SDK (store).

177 lines (152 loc) 4.32 kB
import { atom, Atom, transact, UNINITIALIZED } from '@tldraw/state' import { assert } from '@tldraw/utils' import { emptyMap, ImmutableMap } from './ImmutableMap' /** * A drop-in replacement for Map that stores values in atoms and can be used in reactive contexts. * @public */ export class AtomMap<K, V> implements Map<K, V> { private atoms: Atom<ImmutableMap<K, Atom<V | UNINITIALIZED>>> constructor( private readonly name: string, entries?: Iterable<readonly [K, V]> ) { let atoms = emptyMap<K, Atom<V>>() if (entries) { atoms = atoms.withMutations((atoms) => { for (const [k, v] of entries) { atoms.set(k, atom(`${name}:${String(k)}`, v)) } }) } this.atoms = atom(`${name}:atoms`, atoms) } /** @internal */ getAtom(key: K): Atom<V | UNINITIALIZED> | undefined { const valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key) if (!valueAtom) { // if the value is missing, we want to track whether it's in the present keys set this.atoms.get() return undefined } return valueAtom } get(key: K): V | undefined { const value = this.getAtom(key)?.get() assert(value !== UNINITIALIZED) return value } __unsafe__getWithoutCapture(key: K): V | undefined { const valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key) if (!valueAtom) return undefined const value = valueAtom.__unsafe__getWithoutCapture() assert(value !== UNINITIALIZED) return value } has(key: K): boolean { const valueAtom = this.getAtom(key) if (!valueAtom) { return false } return valueAtom.get() !== UNINITIALIZED } __unsafe__hasWithoutCapture(key: K): boolean { const valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key) if (!valueAtom) return false assert(valueAtom.__unsafe__getWithoutCapture() !== UNINITIALIZED) return true } set(key: K, value: V) { const existingAtom = this.atoms.__unsafe__getWithoutCapture().get(key) if (existingAtom) { existingAtom.set(value) } else { this.atoms.update((atoms) => { return atoms.set(key, atom(`${this.name}:${String(key)}`, value)) }) } return this } update(key: K, updater: (value: V) => V) { const valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key) if (!valueAtom) { throw new Error(`AtomMap: key ${key} not found`) } const value = valueAtom.__unsafe__getWithoutCapture() assert(value !== UNINITIALIZED) valueAtom.set(updater(value)) } delete(key: K) { const valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key) if (!valueAtom) { return false } transact(() => { valueAtom.set(UNINITIALIZED) this.atoms.update((atoms) => { return atoms.delete(key) }) }) return true } deleteMany(keys: Iterable<K>): [K, V][] { return transact(() => { const deleted: [K, V][] = [] const newAtoms = this.atoms.get().withMutations((atoms) => { for (const key of keys) { const valueAtom = atoms.get(key) if (!valueAtom) continue const oldValue = valueAtom.get() assert(oldValue !== UNINITIALIZED) deleted.push([key, oldValue]) atoms.delete(key) valueAtom.set(UNINITIALIZED) } }) if (deleted.length) { this.atoms.set(newAtoms) } return deleted }) } clear() { return transact(() => { for (const valueAtom of this.atoms.__unsafe__getWithoutCapture().values()) { valueAtom.set(UNINITIALIZED) } this.atoms.set(emptyMap()) }) } *entries(): Generator<[K, V], undefined, unknown> { for (const [key, valueAtom] of this.atoms.get()) { const value = valueAtom.get() assert(value !== UNINITIALIZED) yield [key, value] } } *keys(): Generator<K, undefined, unknown> { for (const key of this.atoms.get().keys()) { yield key } } *values(): Generator<V, undefined, unknown> { for (const valueAtom of this.atoms.get().values()) { const value = valueAtom.get() assert(value !== UNINITIALIZED) yield value } } // eslint-disable-next-line no-restricted-syntax get size() { return this.atoms.get().size } forEach(callbackfn: (value: V, key: K, map: AtomMap<K, V>) => void, thisArg?: any): void { for (const [key, value] of this.entries()) { callbackfn.call(thisArg, value, key, this) } } [Symbol.iterator]() { return this.entries() } [Symbol.toStringTag] = 'AtomMap' }