UNPKG

immer

Version:

Create your next immutable state by mutating the current one

213 lines (192 loc) 5.46 kB
import { DRAFT_STATE, DRAFTABLE, hasSet, Objectish, Drafted, AnyObject, AnyArray, AnyMap, AnySet, ImmerState, ProxyType, Archtype, hasMap } from "./internal" /** Returns true if the given value is an Immer draft */ export function isDraft(value: any): boolean { return !!value && !!value[DRAFT_STATE] } /** Returns true if the given value can be drafted by Immer */ export function isDraftable(value: any): boolean { if (!value) return false return ( isPlainObject(value) || Array.isArray(value) || !!value[DRAFTABLE] || !!value.constructor[DRAFTABLE] || isMap(value) || isSet(value) ) } export function isPlainObject(value: any): boolean { if (!value || typeof value !== "object") return false const proto = Object.getPrototypeOf(value) return !proto || proto === Object.prototype } /** Get the underlying object that is represented by the given draft */ export function original<T>(value: T): T | undefined export function original(value: Drafted<any>): any { if (value && value[DRAFT_STATE]) { return value[DRAFT_STATE].base as any } // otherwise return undefined } export const ownKeys: (target: AnyObject) => PropertyKey[] = typeof Reflect !== "undefined" && Reflect.ownKeys ? Reflect.ownKeys : typeof Object.getOwnPropertySymbols !== "undefined" ? obj => Object.getOwnPropertyNames(obj).concat( Object.getOwnPropertySymbols(obj) as any ) : /* istanbul ignore next */ Object.getOwnPropertyNames export function each<T extends Objectish>( obj: T, iter: (key: string | number, value: any, source: T) => void ): void export function each(obj: any, iter: any) { if (getArchtype(obj) === Archtype.Object) { ownKeys(obj).forEach(key => iter(key, obj[key], obj)) } else { obj.forEach((entry: any, index: any) => iter(index, entry, obj)) } } export function isEnumerable(base: AnyObject, prop: PropertyKey): boolean { const desc = Object.getOwnPropertyDescriptor(base, prop) return desc && desc.enumerable ? true : false } export function getArchtype(thing: any): Archtype { /* istanbul ignore next */ if (!thing) die() if (thing[DRAFT_STATE]) { switch ((thing as Drafted)[DRAFT_STATE].type) { case ProxyType.ES5Object: case ProxyType.ProxyObject: return Archtype.Object case ProxyType.ES5Array: case ProxyType.ProxyArray: return Archtype.Array case ProxyType.Map: return Archtype.Map case ProxyType.Set: return Archtype.Set } } return Array.isArray(thing) ? Archtype.Array : isMap(thing) ? Archtype.Map : isSet(thing) ? Archtype.Set : Archtype.Object } export function has(thing: any, prop: PropertyKey): boolean { return getArchtype(thing) === Archtype.Map ? thing.has(prop) : Object.prototype.hasOwnProperty.call(thing, prop) } export function get(thing: AnyMap | AnyObject, prop: PropertyKey): any { // @ts-ignore return getArchtype(thing) === Archtype.Map ? thing.get(prop) : thing[prop] } export function set(thing: any, propOrOldValue: PropertyKey, value: any) { switch (getArchtype(thing)) { case Archtype.Map: thing.set(propOrOldValue, value) break case Archtype.Set: thing.delete(propOrOldValue) thing.add(value) break default: thing[propOrOldValue] = value } } export function is(x: any, y: any): boolean { // From: https://github.com/facebook/fbjs/blob/c69904a511b900266935168223063dd8772dfc40/packages/fbjs/src/core/shallowEqual.js if (x === y) { return x !== 0 || 1 / x === 1 / y } else { return x !== x && y !== y } } export function isMap(target: any): target is AnyMap { return hasMap && target instanceof Map } export function isSet(target: any): target is AnySet { return hasSet && target instanceof Set } export function latest(state: ImmerState): any { return state.copy || state.base } export function shallowCopy<T extends AnyObject | AnyArray>( base: T, invokeGetters?: boolean ): T export function shallowCopy(base: any, invokeGetters = false) { if (Array.isArray(base)) return base.slice() const clone = Object.create(Object.getPrototypeOf(base)) ownKeys(base).forEach(key => { if (key === DRAFT_STATE) { return // Never copy over draft state. } const desc = Object.getOwnPropertyDescriptor(base, key)! let {value} = desc if (desc.get) { if (!invokeGetters) { throw new Error("Immer drafts cannot have computed properties") } value = desc.get.call(base) } if (desc.enumerable) { clone[key] = value } else { Object.defineProperty(clone, key, { value, writable: true, configurable: true }) } }) return clone } export function freeze(obj: any, deep: boolean): void { if (!isDraftable(obj) || isDraft(obj) || Object.isFrozen(obj)) return const type = getArchtype(obj) if (type === Archtype.Set) { obj.add = obj.clear = obj.delete = dontMutateFrozenCollections as any } else if (type === Archtype.Map) { obj.set = obj.clear = obj.delete = dontMutateFrozenCollections as any } Object.freeze(obj) if (deep) each(obj, (_, value) => freeze(value, true)) } function dontMutateFrozenCollections() { throw new Error("This object has been frozen and should not be mutated") } export function createHiddenProperty( target: AnyObject, prop: PropertyKey, value: any ) { Object.defineProperty(target, prop, { value: value, enumerable: false, writable: true }) } /* istanbul ignore next */ export function die(): never { throw new Error("Illegal state, please file a bug") }