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