source: frontend/node_modules/immer/src/core/proxy.ts

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 7.5 KB
Line 
1import {
2 each,
3 has,
4 is,
5 isDraftable,
6 shallowCopy,
7 latest,
8 ImmerBaseState,
9 ImmerState,
10 Drafted,
11 AnyObject,
12 AnyArray,
13 Objectish,
14 getCurrentScope,
15 DRAFT_STATE,
16 die,
17 createProxy,
18 ProxyType
19} from "../internal"
20
21interface ProxyBaseState extends ImmerBaseState {
22 assigned_: {
23 [property: string]: boolean
24 }
25 parent_?: ImmerState
26 revoke_(): void
27}
28
29export interface ProxyObjectState extends ProxyBaseState {
30 type_: ProxyType.ProxyObject
31 base_: any
32 copy_: any
33 draft_: Drafted<AnyObject, ProxyObjectState>
34}
35
36export interface ProxyArrayState extends ProxyBaseState {
37 type_: ProxyType.ProxyArray
38 base_: AnyArray
39 copy_: AnyArray | null
40 draft_: Drafted<AnyArray, ProxyArrayState>
41}
42
43type ProxyState = ProxyObjectState | ProxyArrayState
44
45/**
46 * Returns a new draft of the `base` object.
47 *
48 * The second argument is the parent draft-state (used internally).
49 */
50export function createProxyProxy<T extends Objectish>(
51 base: T,
52 parent?: ImmerState
53): Drafted<T, ProxyState> {
54 const isArray = Array.isArray(base)
55 const state: ProxyState = {
56 type_: isArray ? ProxyType.ProxyArray : (ProxyType.ProxyObject as any),
57 // Track which produce call this is associated with.
58 scope_: parent ? parent.scope_ : getCurrentScope()!,
59 // True for both shallow and deep changes.
60 modified_: false,
61 // Used during finalization.
62 finalized_: false,
63 // Track which properties have been assigned (true) or deleted (false).
64 assigned_: {},
65 // The parent draft state.
66 parent_: parent,
67 // The base state.
68 base_: base,
69 // The base proxy.
70 draft_: null as any, // set below
71 // The base copy with any updated values.
72 copy_: null,
73 // Called by the `produce` function.
74 revoke_: null as any,
75 isManual_: false
76 }
77
78 // the traps must target something, a bit like the 'real' base.
79 // but also, we need to be able to determine from the target what the relevant state is
80 // (to avoid creating traps per instance to capture the state in closure,
81 // and to avoid creating weird hidden properties as well)
82 // So the trick is to use 'state' as the actual 'target'! (and make sure we intercept everything)
83 // Note that in the case of an array, we put the state in an array to have better Reflect defaults ootb
84 let target: T = state as any
85 let traps: ProxyHandler<object | Array<any>> = objectTraps
86 if (isArray) {
87 target = [state] as any
88 traps = arrayTraps
89 }
90
91 const {revoke, proxy} = Proxy.revocable(target, traps)
92 state.draft_ = proxy as any
93 state.revoke_ = revoke
94 return proxy as any
95}
96
97/**
98 * Object drafts
99 */
100export const objectTraps: ProxyHandler<ProxyState> = {
101 get(state, prop) {
102 if (prop === DRAFT_STATE) return state
103
104 const source = latest(state)
105 if (!has(source, prop)) {
106 // non-existing or non-own property...
107 return readPropFromProto(state, source, prop)
108 }
109 const value = source[prop]
110 if (state.finalized_ || !isDraftable(value)) {
111 return value
112 }
113 // Check for existing draft in modified state.
114 // Assigned values are never drafted. This catches any drafts we created, too.
115 if (value === peek(state.base_, prop)) {
116 prepareCopy(state)
117 return (state.copy_![prop as any] = createProxy(
118 state.scope_.immer_,
119 value,
120 state
121 ))
122 }
123 return value
124 },
125 has(state, prop) {
126 return prop in latest(state)
127 },
128 ownKeys(state) {
129 return Reflect.ownKeys(latest(state))
130 },
131 set(
132 state: ProxyObjectState,
133 prop: string /* strictly not, but helps TS */,
134 value
135 ) {
136 const desc = getDescriptorFromProto(latest(state), prop)
137 if (desc?.set) {
138 // special case: if this write is captured by a setter, we have
139 // to trigger it with the correct context
140 desc.set.call(state.draft_, value)
141 return true
142 }
143 if (!state.modified_) {
144 // the last check is because we need to be able to distinguish setting a non-existing to undefined (which is a change)
145 // from setting an existing property with value undefined to undefined (which is not a change)
146 const current = peek(latest(state), prop)
147 // special case, if we assigning the original value to a draft, we can ignore the assignment
148 const currentState: ProxyObjectState = current?.[DRAFT_STATE]
149 if (currentState && currentState.base_ === value) {
150 state.copy_![prop] = value
151 state.assigned_[prop] = false
152 return true
153 }
154 if (is(value, current) && (value !== undefined || has(state.base_, prop)))
155 return true
156 prepareCopy(state)
157 markChanged(state)
158 }
159
160 if (
161 (state.copy_![prop] === value &&
162 // special case: handle new props with value 'undefined'
163 (value !== undefined || prop in state.copy_)) ||
164 // special case: NaN
165 (Number.isNaN(value) && Number.isNaN(state.copy_![prop]))
166 )
167 return true
168
169 // @ts-ignore
170 state.copy_![prop] = value
171 state.assigned_[prop] = true
172 return true
173 },
174 deleteProperty(state, prop: string) {
175 // The `undefined` check is a fast path for pre-existing keys.
176 if (peek(state.base_, prop) !== undefined || prop in state.base_) {
177 state.assigned_[prop] = false
178 prepareCopy(state)
179 markChanged(state)
180 } else {
181 // if an originally not assigned property was deleted
182 delete state.assigned_[prop]
183 }
184 // @ts-ignore
185 if (state.copy_) delete state.copy_[prop]
186 return true
187 },
188 // Note: We never coerce `desc.value` into an Immer draft, because we can't make
189 // the same guarantee in ES5 mode.
190 getOwnPropertyDescriptor(state, prop) {
191 const owner = latest(state)
192 const desc = Reflect.getOwnPropertyDescriptor(owner, prop)
193 if (!desc) return desc
194 return {
195 writable: true,
196 configurable: state.type_ !== ProxyType.ProxyArray || prop !== "length",
197 enumerable: desc.enumerable,
198 value: owner[prop]
199 }
200 },
201 defineProperty() {
202 die(11)
203 },
204 getPrototypeOf(state) {
205 return Object.getPrototypeOf(state.base_)
206 },
207 setPrototypeOf() {
208 die(12)
209 }
210}
211
212/**
213 * Array drafts
214 */
215
216const arrayTraps: ProxyHandler<[ProxyArrayState]> = {}
217each(objectTraps, (key, fn) => {
218 // @ts-ignore
219 arrayTraps[key] = function() {
220 arguments[0] = arguments[0][0]
221 return fn.apply(this, arguments)
222 }
223})
224arrayTraps.deleteProperty = function(state, prop) {
225 if (__DEV__ && isNaN(parseInt(prop as any))) die(13)
226 // @ts-ignore
227 return arrayTraps.set!.call(this, state, prop, undefined)
228}
229arrayTraps.set = function(state, prop, value) {
230 if (__DEV__ && prop !== "length" && isNaN(parseInt(prop as any))) die(14)
231 return objectTraps.set!.call(this, state[0], prop, value, state[0])
232}
233
234// Access a property without creating an Immer draft.
235function peek(draft: Drafted, prop: PropertyKey) {
236 const state = draft[DRAFT_STATE]
237 const source = state ? latest(state) : draft
238 return source[prop]
239}
240
241function readPropFromProto(state: ImmerState, source: any, prop: PropertyKey) {
242 const desc = getDescriptorFromProto(source, prop)
243 return desc
244 ? `value` in desc
245 ? desc.value
246 : // This is a very special case, if the prop is a getter defined by the
247 // prototype, we should invoke it with the draft as context!
248 desc.get?.call(state.draft_)
249 : undefined
250}
251
252function getDescriptorFromProto(
253 source: any,
254 prop: PropertyKey
255): PropertyDescriptor | undefined {
256 // 'in' checks proto!
257 if (!(prop in source)) return undefined
258 let proto = Object.getPrototypeOf(source)
259 while (proto) {
260 const desc = Object.getOwnPropertyDescriptor(proto, prop)
261 if (desc) return desc
262 proto = Object.getPrototypeOf(proto)
263 }
264 return undefined
265}
266
267export function markChanged(state: ImmerState) {
268 if (!state.modified_) {
269 state.modified_ = true
270 if (state.parent_) {
271 markChanged(state.parent_)
272 }
273 }
274}
275
276export function prepareCopy(state: {base_: any; copy_: any}) {
277 if (!state.copy_) {
278 state.copy_ = shallowCopy(state.base_)
279 }
280}
Note: See TracBrowser for help on using the repository browser.