// types only! import { ImmerState, AnyMap, AnySet, MapState, SetState, DRAFT_STATE, getCurrentScope, latest, isDraftable, createProxy, loadPlugin, markChanged, die, ArchType, each } from "../internal" export function enableMapSet() { class DraftMap extends Map { [DRAFT_STATE]: MapState constructor(target: AnyMap, parent?: ImmerState) { super() this[DRAFT_STATE] = { type_: ArchType.Map, parent_: parent, scope_: parent ? parent.scope_ : getCurrentScope()!, modified_: false, finalized_: false, copy_: undefined, assigned_: undefined, base_: target, draft_: this as any, isManual_: false, revoked_: false } } get size(): number { return latest(this[DRAFT_STATE]).size } has(key: any): boolean { return latest(this[DRAFT_STATE]).has(key) } set(key: any, value: any) { const state: MapState = this[DRAFT_STATE] assertUnrevoked(state) if (!latest(state).has(key) || latest(state).get(key) !== value) { prepareMapCopy(state) markChanged(state) state.assigned_!.set(key, true) state.copy_!.set(key, value) state.assigned_!.set(key, true) } return this } delete(key: any): boolean { if (!this.has(key)) { return false } const state: MapState = this[DRAFT_STATE] assertUnrevoked(state) prepareMapCopy(state) markChanged(state) if (state.base_.has(key)) { state.assigned_!.set(key, false) } else { state.assigned_!.delete(key) } state.copy_!.delete(key) return true } clear() { const state: MapState = this[DRAFT_STATE] assertUnrevoked(state) if (latest(state).size) { prepareMapCopy(state) markChanged(state) state.assigned_ = new Map() each(state.base_, key => { state.assigned_!.set(key, false) }) state.copy_!.clear() } } forEach(cb: (value: any, key: any, self: any) => void, thisArg?: any) { const state: MapState = this[DRAFT_STATE] latest(state).forEach((_value: any, key: any, _map: any) => { cb.call(thisArg, this.get(key), key, this) }) } get(key: any): any { const state: MapState = this[DRAFT_STATE] assertUnrevoked(state) const value = latest(state).get(key) if (state.finalized_ || !isDraftable(value)) { return value } if (value !== state.base_.get(key)) { return value // either already drafted or reassigned } // despite what it looks, this creates a draft only once, see above condition const draft = createProxy(value, state) prepareMapCopy(state) state.copy_!.set(key, draft) return draft } keys(): IterableIterator { return latest(this[DRAFT_STATE]).keys() } values(): IterableIterator { const iterator = this.keys() return { [Symbol.iterator]: () => this.values(), next: () => { const r = iterator.next() /* istanbul ignore next */ if (r.done) return r const value = this.get(r.value) return { done: false, value } } } as any } entries(): IterableIterator<[any, any]> { const iterator = this.keys() return { [Symbol.iterator]: () => this.entries(), next: () => { const r = iterator.next() /* istanbul ignore next */ if (r.done) return r const value = this.get(r.value) return { done: false, value: [r.value, value] } } } as any } [Symbol.iterator]() { return this.entries() } } function proxyMap_(target: T, parent?: ImmerState): T { // @ts-ignore return new DraftMap(target, parent) } function prepareMapCopy(state: MapState) { if (!state.copy_) { state.assigned_ = new Map() state.copy_ = new Map(state.base_) } } class DraftSet extends Set { [DRAFT_STATE]: SetState constructor(target: AnySet, parent?: ImmerState) { super() this[DRAFT_STATE] = { type_: ArchType.Set, parent_: parent, scope_: parent ? parent.scope_ : getCurrentScope()!, modified_: false, finalized_: false, copy_: undefined, base_: target, draft_: this, drafts_: new Map(), revoked_: false, isManual_: false } } get size(): number { return latest(this[DRAFT_STATE]).size } has(value: any): boolean { const state: SetState = this[DRAFT_STATE] assertUnrevoked(state) // bit of trickery here, to be able to recognize both the value, and the draft of its value if (!state.copy_) { return state.base_.has(value) } if (state.copy_.has(value)) return true if (state.drafts_.has(value) && state.copy_.has(state.drafts_.get(value))) return true return false } add(value: any): any { const state: SetState = this[DRAFT_STATE] assertUnrevoked(state) if (!this.has(value)) { prepareSetCopy(state) markChanged(state) state.copy_!.add(value) } return this } delete(value: any): any { if (!this.has(value)) { return false } const state: SetState = this[DRAFT_STATE] assertUnrevoked(state) prepareSetCopy(state) markChanged(state) return ( state.copy_!.delete(value) || (state.drafts_.has(value) ? state.copy_!.delete(state.drafts_.get(value)) : /* istanbul ignore next */ false) ) } clear() { const state: SetState = this[DRAFT_STATE] assertUnrevoked(state) if (latest(state).size) { prepareSetCopy(state) markChanged(state) state.copy_!.clear() } } values(): IterableIterator { const state: SetState = this[DRAFT_STATE] assertUnrevoked(state) prepareSetCopy(state) return state.copy_!.values() } entries(): IterableIterator<[any, any]> { const state: SetState = this[DRAFT_STATE] assertUnrevoked(state) prepareSetCopy(state) return state.copy_!.entries() } keys(): IterableIterator { return this.values() } [Symbol.iterator]() { return this.values() } forEach(cb: any, thisArg?: any) { const iterator = this.values() let result = iterator.next() while (!result.done) { cb.call(thisArg, result.value, result.value, this) result = iterator.next() } } } function proxySet_(target: T, parent?: ImmerState): T { // @ts-ignore return new DraftSet(target, parent) } function prepareSetCopy(state: SetState) { if (!state.copy_) { // create drafts for all entries to preserve insertion order state.copy_ = new Set() state.base_.forEach(value => { if (isDraftable(value)) { const draft = createProxy(value, state) state.drafts_.set(value, draft) state.copy_!.add(draft) } else { state.copy_!.add(value) } }) } } function assertUnrevoked(state: any /*ES5State | MapState | SetState*/) { if (state.revoked_) die(3, JSON.stringify(latest(state))) } loadPlugin("MapSet", {proxyMap_, proxySet_}) }