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 |
|
18 |
|
19 | export function isDraft(value: any): boolean {
|
20 | return !!value && !!value[DRAFT_STATE]
|
21 | }
|
22 |
|
23 |
|
24 |
|
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 |
|
39 | export function isPlainObject(value: any): boolean {
|
40 | if (!value || typeof value !== "object") return false
|
41 | const proto = getPrototypeOf(value)
|
42 | if (proto === null) {
|
43 | return true
|
44 | }
|
45 | const Ctor =
|
46 | Object.hasOwnProperty.call(proto, "constructor") && proto.constructor
|
47 |
|
48 | if (Ctor === Object) return true
|
49 |
|
50 | return (
|
51 | typeof Ctor == "function" &&
|
52 | Function.toString.call(Ctor) === objectCtorString
|
53 | )
|
54 | }
|
55 |
|
56 |
|
57 |
|
58 | export function original<T>(value: T): T | undefined
|
59 | export function original(value: Drafted<any>): any {
|
60 | if (!isDraft(value)) die(15, value)
|
61 | return value[DRAFT_STATE].base_
|
62 | }
|
63 |
|
64 | /**
|
65 | * Each iterates a map, set or array.
|
66 | * Or, if any other kind of object, all of its own properties.
|
67 | * Regardless whether they are enumerable or symbols
|
68 | */
|
69 | export function each<T extends Objectish>(
|
70 | obj: T,
|
71 | iter: (key: string | number, value: any, source: T) => void
|
72 | ): void
|
73 | export function each(obj: any, iter: any) {
|
74 | if (getArchtype(obj) === ArchType.Object) {
|
75 | Reflect.ownKeys(obj).forEach(key => {
|
76 | iter(key, obj[key], obj)
|
77 | })
|
78 | } else {
|
79 | obj.forEach((entry: any, index: any) => iter(index, entry, obj))
|
80 | }
|
81 | }
|
82 |
|
83 |
|
84 | export function getArchtype(thing: any): ArchType {
|
85 | const state: undefined | ImmerState = thing[DRAFT_STATE]
|
86 | return state
|
87 | ? state.type_
|
88 | : Array.isArray(thing)
|
89 | ? ArchType.Array
|
90 | : isMap(thing)
|
91 | ? ArchType.Map
|
92 | : isSet(thing)
|
93 | ? ArchType.Set
|
94 | : ArchType.Object
|
95 | }
|
96 |
|
97 |
|
98 | export function has(thing: any, prop: PropertyKey): boolean {
|
99 | return getArchtype(thing) === ArchType.Map
|
100 | ? thing.has(prop)
|
101 | : Object.prototype.hasOwnProperty.call(thing, prop)
|
102 | }
|
103 |
|
104 |
|
105 | export function get(thing: AnyMap | AnyObject, prop: PropertyKey): any {
|
106 |
|
107 | return getArchtype(thing) === ArchType.Map ? thing.get(prop) : thing[prop]
|
108 | }
|
109 |
|
110 |
|
111 | export function set(thing: any, propOrOldValue: PropertyKey, value: any) {
|
112 | const t = getArchtype(thing)
|
113 | if (t === ArchType.Map) thing.set(propOrOldValue, value)
|
114 | else if (t === ArchType.Set) {
|
115 | thing.add(value)
|
116 | } else thing[propOrOldValue] = value
|
117 | }
|
118 |
|
119 |
|
120 | export function is(x: any, y: any): boolean {
|
121 |
|
122 | if (x === y) {
|
123 | return x !== 0 || 1 / x === 1 / y
|
124 | } else {
|
125 | return x !== x && y !== y
|
126 | }
|
127 | }
|
128 |
|
129 |
|
130 | export function isMap(target: any): target is AnyMap {
|
131 | return target instanceof Map
|
132 | }
|
133 |
|
134 |
|
135 | export function isSet(target: any): target is AnySet {
|
136 | return target instanceof Set
|
137 | }
|
138 |
|
139 | export function latest(state: ImmerState): any {
|
140 | return state.copy_ || state.base_
|
141 | }
|
142 |
|
143 |
|
144 | export function shallowCopy(base: any, strict: StrictMode) {
|
145 | if (isMap(base)) {
|
146 | return new Map(base)
|
147 | }
|
148 | if (isSet(base)) {
|
149 | return new Set(base)
|
150 | }
|
151 | if (Array.isArray(base)) return Array.prototype.slice.call(base)
|
152 |
|
153 | const isPlain = isPlainObject(base)
|
154 |
|
155 | if (strict === true || (strict === "class_only" && !isPlain)) {
|
156 |
|
157 | const descriptors = Object.getOwnPropertyDescriptors(base)
|
158 | delete descriptors[DRAFT_STATE as any]
|
159 | let keys = Reflect.ownKeys(descriptors)
|
160 | for (let i = 0; i < keys.length; i++) {
|
161 | const key: any = keys[i]
|
162 | const desc = descriptors[key]
|
163 | if (desc.writable === false) {
|
164 | desc.writable = true
|
165 | desc.configurable = true
|
166 | }
|
167 |
|
168 |
|
169 |
|
170 | if (desc.get || desc.set)
|
171 | descriptors[key] = {
|
172 | configurable: true,
|
173 | writable: true,
|
174 | enumerable: desc.enumerable,
|
175 | value: base[key]
|
176 | }
|
177 | }
|
178 | return Object.create(getPrototypeOf(base), descriptors)
|
179 | } else {
|
180 |
|
181 | const proto = getPrototypeOf(base)
|
182 | if (proto !== null && isPlain) {
|
183 | return {...base}
|
184 | }
|
185 | const obj = Object.create(proto)
|
186 | return Object.assign(obj, base)
|
187 | }
|
188 | }
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 | export function freeze<T>(obj: T, deep?: boolean): T
|
198 | export function freeze<T>(obj: any, deep: boolean = false): T {
|
199 | if (isFrozen(obj) || isDraft(obj) || !isDraftable(obj)) return obj
|
200 | if (getArchtype(obj) > 1 ) {
|
201 | obj.set = obj.add = obj.clear = obj.delete = dontMutateFrozenCollections as any
|
202 | }
|
203 | Object.freeze(obj)
|
204 | if (deep)
|
205 |
|
206 |
|
207 | Object.entries(obj).forEach(([key, value]) => freeze(value, true))
|
208 | return obj
|
209 | }
|
210 |
|
211 | function dontMutateFrozenCollections() {
|
212 | die(2)
|
213 | }
|
214 |
|
215 | export function isFrozen(obj: any): boolean {
|
216 | return Object.isFrozen(obj)
|
217 | }
|