| [a762898] | 1 | import {
|
|---|
| 2 | DRAFT_STATE,
|
|---|
| 3 | DRAFTABLE,
|
|---|
| 4 | Objectish,
|
|---|
| 5 | Drafted,
|
|---|
| 6 | AnyObject,
|
|---|
| 7 | AnyMap,
|
|---|
| 8 | AnySet,
|
|---|
| 9 | ImmerState,
|
|---|
| 10 | ArchType,
|
|---|
| 11 | die,
|
|---|
| 12 | StrictMode
|
|---|
| 13 | } from "../internal"
|
|---|
| 14 |
|
|---|
| 15 | export const getPrototypeOf = Object.getPrototypeOf
|
|---|
| 16 |
|
|---|
| 17 | /** Returns true if the given value is an Immer draft */
|
|---|
| 18 | /*#__PURE__*/
|
|---|
| 19 | export function isDraft(value: any): boolean {
|
|---|
| 20 | return !!value && !!value[DRAFT_STATE]
|
|---|
| 21 | }
|
|---|
| 22 |
|
|---|
| 23 | /** Returns true if the given value can be drafted by Immer */
|
|---|
| 24 | /*#__PURE__*/
|
|---|
| 25 | export function isDraftable(value: any): boolean {
|
|---|
| 26 | if (!value) return false
|
|---|
| 27 | return (
|
|---|
| 28 | isPlainObject(value) ||
|
|---|
| 29 | Array.isArray(value) ||
|
|---|
| 30 | !!value[DRAFTABLE] ||
|
|---|
| 31 | !!value.constructor?.[DRAFTABLE] ||
|
|---|
| 32 | isMap(value) ||
|
|---|
| 33 | isSet(value)
|
|---|
| 34 | )
|
|---|
| 35 | }
|
|---|
| 36 |
|
|---|
| 37 | const objectCtorString = Object.prototype.constructor.toString()
|
|---|
| 38 | const cachedCtorStrings = new WeakMap()
|
|---|
| 39 | /*#__PURE__*/
|
|---|
| 40 | export function isPlainObject(value: any): boolean {
|
|---|
| 41 | if (!value || typeof value !== "object") return false
|
|---|
| 42 | const proto = Object.getPrototypeOf(value)
|
|---|
| 43 | if (proto === null || proto === Object.prototype) return true
|
|---|
| 44 |
|
|---|
| 45 | const Ctor =
|
|---|
| 46 | Object.hasOwnProperty.call(proto, "constructor") && proto.constructor
|
|---|
| 47 | if (Ctor === Object) return true
|
|---|
| 48 |
|
|---|
| 49 | if (typeof Ctor !== "function") return false
|
|---|
| 50 |
|
|---|
| 51 | let ctorString = cachedCtorStrings.get(Ctor)
|
|---|
| 52 | if (ctorString === undefined) {
|
|---|
| 53 | ctorString = Function.toString.call(Ctor)
|
|---|
| 54 | cachedCtorStrings.set(Ctor, ctorString)
|
|---|
| 55 | }
|
|---|
| 56 |
|
|---|
| 57 | return ctorString === objectCtorString
|
|---|
| 58 | }
|
|---|
| 59 |
|
|---|
| 60 | /** Get the underlying object that is represented by the given draft */
|
|---|
| 61 | /*#__PURE__*/
|
|---|
| 62 | export function original<T>(value: T): T | undefined
|
|---|
| 63 | export function original(value: Drafted<any>): any {
|
|---|
| 64 | if (!isDraft(value)) die(15, value)
|
|---|
| 65 | return value[DRAFT_STATE].base_
|
|---|
| 66 | }
|
|---|
| 67 |
|
|---|
| 68 | /**
|
|---|
| 69 | * Each iterates a map, set or array.
|
|---|
| 70 | * Or, if any other kind of object, all of its own properties.
|
|---|
| 71 | *
|
|---|
| 72 | * @param obj The object to iterate over
|
|---|
| 73 | * @param iter The iterator function
|
|---|
| 74 | * @param strict When true (default), includes symbols and non-enumerable properties.
|
|---|
| 75 | * When false, uses looseiteration over only enumerable string properties.
|
|---|
| 76 | */
|
|---|
| 77 | export function each<T extends Objectish>(
|
|---|
| 78 | obj: T,
|
|---|
| 79 | iter: (key: string | number, value: any, source: T) => void,
|
|---|
| 80 | strict?: boolean
|
|---|
| 81 | ): void
|
|---|
| 82 | export function each(obj: any, iter: any, strict: boolean = true) {
|
|---|
| 83 | if (getArchtype(obj) === ArchType.Object) {
|
|---|
| 84 | // If strict, we do a full iteration including symbols and non-enumerable properties
|
|---|
| 85 | // Otherwise, we only iterate enumerable string properties for performance
|
|---|
| 86 | const keys = strict ? Reflect.ownKeys(obj) : Object.keys(obj)
|
|---|
| 87 | keys.forEach(key => {
|
|---|
| 88 | iter(key, obj[key], obj)
|
|---|
| 89 | })
|
|---|
| 90 | } else {
|
|---|
| 91 | obj.forEach((entry: any, index: any) => iter(index, entry, obj))
|
|---|
| 92 | }
|
|---|
| 93 | }
|
|---|
| 94 |
|
|---|
| 95 | /*#__PURE__*/
|
|---|
| 96 | export function getArchtype(thing: any): ArchType {
|
|---|
| 97 | const state: undefined | ImmerState = thing[DRAFT_STATE]
|
|---|
| 98 | return state
|
|---|
| 99 | ? state.type_
|
|---|
| 100 | : Array.isArray(thing)
|
|---|
| 101 | ? ArchType.Array
|
|---|
| 102 | : isMap(thing)
|
|---|
| 103 | ? ArchType.Map
|
|---|
| 104 | : isSet(thing)
|
|---|
| 105 | ? ArchType.Set
|
|---|
| 106 | : ArchType.Object
|
|---|
| 107 | }
|
|---|
| 108 |
|
|---|
| 109 | /*#__PURE__*/
|
|---|
| 110 | export function has(thing: any, prop: PropertyKey): boolean {
|
|---|
| 111 | return getArchtype(thing) === ArchType.Map
|
|---|
| 112 | ? thing.has(prop)
|
|---|
| 113 | : Object.prototype.hasOwnProperty.call(thing, prop)
|
|---|
| 114 | }
|
|---|
| 115 |
|
|---|
| 116 | /*#__PURE__*/
|
|---|
| 117 | export function get(thing: AnyMap | AnyObject, prop: PropertyKey): any {
|
|---|
| 118 | // @ts-ignore
|
|---|
| 119 | return getArchtype(thing) === ArchType.Map ? thing.get(prop) : thing[prop]
|
|---|
| 120 | }
|
|---|
| 121 |
|
|---|
| 122 | /*#__PURE__*/
|
|---|
| 123 | export function set(thing: any, propOrOldValue: PropertyKey, value: any) {
|
|---|
| 124 | const t = getArchtype(thing)
|
|---|
| 125 | if (t === ArchType.Map) thing.set(propOrOldValue, value)
|
|---|
| 126 | else if (t === ArchType.Set) {
|
|---|
| 127 | thing.add(value)
|
|---|
| 128 | } else thing[propOrOldValue] = value
|
|---|
| 129 | }
|
|---|
| 130 |
|
|---|
| 131 | /*#__PURE__*/
|
|---|
| 132 | export function is(x: any, y: any): boolean {
|
|---|
| 133 | // From: https://github.com/facebook/fbjs/blob/c69904a511b900266935168223063dd8772dfc40/packages/fbjs/src/core/shallowEqual.js
|
|---|
| 134 | if (x === y) {
|
|---|
| 135 | return x !== 0 || 1 / x === 1 / y
|
|---|
| 136 | } else {
|
|---|
| 137 | return x !== x && y !== y
|
|---|
| 138 | }
|
|---|
| 139 | }
|
|---|
| 140 |
|
|---|
| 141 | /*#__PURE__*/
|
|---|
| 142 | export function isMap(target: any): target is AnyMap {
|
|---|
| 143 | return target instanceof Map
|
|---|
| 144 | }
|
|---|
| 145 |
|
|---|
| 146 | /*#__PURE__*/
|
|---|
| 147 | export function isSet(target: any): target is AnySet {
|
|---|
| 148 | return target instanceof Set
|
|---|
| 149 | }
|
|---|
| 150 | /*#__PURE__*/
|
|---|
| 151 | export function latest(state: ImmerState): any {
|
|---|
| 152 | return state.copy_ || state.base_
|
|---|
| 153 | }
|
|---|
| 154 |
|
|---|
| 155 | /*#__PURE__*/
|
|---|
| 156 | export function shallowCopy(base: any, strict: StrictMode) {
|
|---|
| 157 | if (isMap(base)) {
|
|---|
| 158 | return new Map(base)
|
|---|
| 159 | }
|
|---|
| 160 | if (isSet(base)) {
|
|---|
| 161 | return new Set(base)
|
|---|
| 162 | }
|
|---|
| 163 | if (Array.isArray(base)) return Array.prototype.slice.call(base)
|
|---|
| 164 |
|
|---|
| 165 | const isPlain = isPlainObject(base)
|
|---|
| 166 |
|
|---|
| 167 | if (strict === true || (strict === "class_only" && !isPlain)) {
|
|---|
| 168 | // Perform a strict copy
|
|---|
| 169 | const descriptors = Object.getOwnPropertyDescriptors(base)
|
|---|
| 170 | delete descriptors[DRAFT_STATE as any]
|
|---|
| 171 | let keys = Reflect.ownKeys(descriptors)
|
|---|
| 172 | for (let i = 0; i < keys.length; i++) {
|
|---|
| 173 | const key: any = keys[i]
|
|---|
| 174 | const desc = descriptors[key]
|
|---|
| 175 | if (desc.writable === false) {
|
|---|
| 176 | desc.writable = true
|
|---|
| 177 | desc.configurable = true
|
|---|
| 178 | }
|
|---|
| 179 | // like object.assign, we will read any _own_, get/set accessors. This helps in dealing
|
|---|
| 180 | // with libraries that trap values, like mobx or vue
|
|---|
| 181 | // unlike object.assign, non-enumerables will be copied as well
|
|---|
| 182 | if (desc.get || desc.set)
|
|---|
| 183 | descriptors[key] = {
|
|---|
| 184 | configurable: true,
|
|---|
| 185 | writable: true, // could live with !!desc.set as well here...
|
|---|
| 186 | enumerable: desc.enumerable,
|
|---|
| 187 | value: base[key]
|
|---|
| 188 | }
|
|---|
| 189 | }
|
|---|
| 190 | return Object.create(getPrototypeOf(base), descriptors)
|
|---|
| 191 | } else {
|
|---|
| 192 | // perform a sloppy copy
|
|---|
| 193 | const proto = getPrototypeOf(base)
|
|---|
| 194 | if (proto !== null && isPlain) {
|
|---|
| 195 | return {...base} // assumption: better inner class optimization than the assign below
|
|---|
| 196 | }
|
|---|
| 197 | const obj = Object.create(proto)
|
|---|
| 198 | return Object.assign(obj, base)
|
|---|
| 199 | }
|
|---|
| 200 | }
|
|---|
| 201 |
|
|---|
| 202 | /**
|
|---|
| 203 | * Freezes draftable objects. Returns the original object.
|
|---|
| 204 | * By default freezes shallowly, but if the second argument is `true` it will freeze recursively.
|
|---|
| 205 | *
|
|---|
| 206 | * @param obj
|
|---|
| 207 | * @param deep
|
|---|
| 208 | */
|
|---|
| 209 | export function freeze<T>(obj: T, deep?: boolean): T
|
|---|
| 210 | export function freeze<T>(obj: any, deep: boolean = false): T {
|
|---|
| 211 | if (isFrozen(obj) || isDraft(obj) || !isDraftable(obj)) return obj
|
|---|
| 212 | if (getArchtype(obj) > 1 /* Map or Set */) {
|
|---|
| 213 | Object.defineProperties(obj, {
|
|---|
| 214 | set: dontMutateMethodOverride,
|
|---|
| 215 | add: dontMutateMethodOverride,
|
|---|
| 216 | clear: dontMutateMethodOverride,
|
|---|
| 217 | delete: dontMutateMethodOverride
|
|---|
| 218 | })
|
|---|
| 219 | }
|
|---|
| 220 | Object.freeze(obj)
|
|---|
| 221 | if (deep)
|
|---|
| 222 | // See #590, don't recurse into non-enumerable / Symbol properties when freezing
|
|---|
| 223 | // So use Object.values (only string-like, enumerables) instead of each()
|
|---|
| 224 | Object.values(obj).forEach(value => freeze(value, true))
|
|---|
| 225 | return obj
|
|---|
| 226 | }
|
|---|
| 227 |
|
|---|
| 228 | function dontMutateFrozenCollections() {
|
|---|
| 229 | die(2)
|
|---|
| 230 | }
|
|---|
| 231 |
|
|---|
| 232 | const dontMutateMethodOverride = {
|
|---|
| 233 | value: dontMutateFrozenCollections
|
|---|
| 234 | }
|
|---|
| 235 |
|
|---|
| 236 | export function isFrozen(obj: any): boolean {
|
|---|
| 237 | // Fast path: primitives and null/undefined are always "frozen"
|
|---|
| 238 | if (obj === null || typeof obj !== "object") return true
|
|---|
| 239 | return Object.isFrozen(obj)
|
|---|
| 240 | }
|
|---|