source: node_modules/immer/src/utils/common.ts@ ba17441

Last change on this file since ba17441 was a762898, checked in by istevanoska <ilinastevanoska@…>, 5 months ago

Added visualizations

  • Property mode set to 100644
File size: 6.7 KB
Line 
1import {
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
15export const getPrototypeOf = Object.getPrototypeOf
16
17/** Returns true if the given value is an Immer draft */
18/*#__PURE__*/
19export 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__*/
25export 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
37const objectCtorString = Object.prototype.constructor.toString()
38const cachedCtorStrings = new WeakMap()
39/*#__PURE__*/
40export 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__*/
62export function original<T>(value: T): T | undefined
63export 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 */
77export function each<T extends Objectish>(
78 obj: T,
79 iter: (key: string | number, value: any, source: T) => void,
80 strict?: boolean
81): void
82export 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__*/
96export 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__*/
110export 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__*/
117export 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__*/
123export 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__*/
132export 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__*/
142export function isMap(target: any): target is AnyMap {
143 return target instanceof Map
144}
145
146/*#__PURE__*/
147export function isSet(target: any): target is AnySet {
148 return target instanceof Set
149}
150/*#__PURE__*/
151export function latest(state: ImmerState): any {
152 return state.copy_ || state.base_
153}
154
155/*#__PURE__*/
156export 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 */
209export function freeze<T>(obj: T, deep?: boolean): T
210export 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
228function dontMutateFrozenCollections() {
229 die(2)
230}
231
232const dontMutateMethodOverride = {
233 value: dontMutateFrozenCollections
234}
235
236export 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}
Note: See TracBrowser for help on using the repository browser.