source: node_modules/immer/src/core/immerClass.ts

Last change on this file 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 IProduceWithPatches,
3 IProduce,
4 ImmerState,
5 Drafted,
6 isDraftable,
7 processResult,
8 Patch,
9 Objectish,
10 DRAFT_STATE,
11 Draft,
12 PatchListener,
13 isDraft,
14 isMap,
15 isSet,
16 createProxyProxy,
17 getPlugin,
18 die,
19 enterScope,
20 revokeScope,
21 leaveScope,
22 usePatchesInScope,
23 getCurrentScope,
24 NOTHING,
25 freeze,
26 current
27} from "../internal"
28
29interface ProducersFns {
30 produce: IProduce
31 produceWithPatches: IProduceWithPatches
32}
33
34export type StrictMode = boolean | "class_only"
35
36export class Immer implements ProducersFns {
37 autoFreeze_: boolean = true
38 useStrictShallowCopy_: StrictMode = false
39 useStrictIteration_: boolean = true
40
41 constructor(config?: {
42 autoFreeze?: boolean
43 useStrictShallowCopy?: StrictMode
44 useStrictIteration?: boolean
45 }) {
46 if (typeof config?.autoFreeze === "boolean")
47 this.setAutoFreeze(config!.autoFreeze)
48 if (typeof config?.useStrictShallowCopy === "boolean")
49 this.setUseStrictShallowCopy(config!.useStrictShallowCopy)
50 if (typeof config?.useStrictIteration === "boolean")
51 this.setUseStrictIteration(config!.useStrictIteration)
52 }
53
54 /**
55 * The `produce` function takes a value and a "recipe function" (whose
56 * return value often depends on the base state). The recipe function is
57 * free to mutate its first argument however it wants. All mutations are
58 * only ever applied to a __copy__ of the base state.
59 *
60 * Pass only a function to create a "curried producer" which relieves you
61 * from passing the recipe function every time.
62 *
63 * Only plain objects and arrays are made mutable. All other objects are
64 * considered uncopyable.
65 *
66 * Note: This function is __bound__ to its `Immer` instance.
67 *
68 * @param {any} base - the initial state
69 * @param {Function} recipe - function that receives a proxy of the base state as first argument and which can be freely modified
70 * @param {Function} patchListener - optional function that will be called with all the patches produced here
71 * @returns {any} a new state, or the initial state if nothing was modified
72 */
73 produce: IProduce = (base: any, recipe?: any, patchListener?: any) => {
74 // curried invocation
75 if (typeof base === "function" && typeof recipe !== "function") {
76 const defaultBase = recipe
77 recipe = base
78
79 const self = this
80 return function curriedProduce(
81 this: any,
82 base = defaultBase,
83 ...args: any[]
84 ) {
85 return self.produce(base, (draft: Drafted) => recipe.call(this, draft, ...args)) // prettier-ignore
86 }
87 }
88
89 if (typeof recipe !== "function") die(6)
90 if (patchListener !== undefined && typeof patchListener !== "function")
91 die(7)
92
93 let result
94
95 // Only plain objects, arrays, and "immerable classes" are drafted.
96 if (isDraftable(base)) {
97 const scope = enterScope(this)
98 const proxy = createProxy(base, undefined)
99 let hasError = true
100 try {
101 result = recipe(proxy)
102 hasError = false
103 } finally {
104 // finally instead of catch + rethrow better preserves original stack
105 if (hasError) revokeScope(scope)
106 else leaveScope(scope)
107 }
108 usePatchesInScope(scope, patchListener)
109 return processResult(result, scope)
110 } else if (!base || typeof base !== "object") {
111 result = recipe(base)
112 if (result === undefined) result = base
113 if (result === NOTHING) result = undefined
114 if (this.autoFreeze_) freeze(result, true)
115 if (patchListener) {
116 const p: Patch[] = []
117 const ip: Patch[] = []
118 getPlugin("Patches").generateReplacementPatches_(base, result, p, ip)
119 patchListener(p, ip)
120 }
121 return result
122 } else die(1, base)
123 }
124
125 produceWithPatches: IProduceWithPatches = (base: any, recipe?: any): any => {
126 // curried invocation
127 if (typeof base === "function") {
128 return (state: any, ...args: any[]) =>
129 this.produceWithPatches(state, (draft: any) => base(draft, ...args))
130 }
131
132 let patches: Patch[], inversePatches: Patch[]
133 const result = this.produce(base, recipe, (p: Patch[], ip: Patch[]) => {
134 patches = p
135 inversePatches = ip
136 })
137 return [result, patches!, inversePatches!]
138 }
139
140 createDraft<T extends Objectish>(base: T): Draft<T> {
141 if (!isDraftable(base)) die(8)
142 if (isDraft(base)) base = current(base)
143 const scope = enterScope(this)
144 const proxy = createProxy(base, undefined)
145 proxy[DRAFT_STATE].isManual_ = true
146 leaveScope(scope)
147 return proxy as any
148 }
149
150 finishDraft<D extends Draft<any>>(
151 draft: D,
152 patchListener?: PatchListener
153 ): D extends Draft<infer T> ? T : never {
154 const state: ImmerState = draft && (draft as any)[DRAFT_STATE]
155 if (!state || !state.isManual_) die(9)
156 const {scope_: scope} = state
157 usePatchesInScope(scope, patchListener)
158 return processResult(undefined, scope)
159 }
160
161 /**
162 * Pass true to automatically freeze all copies created by Immer.
163 *
164 * By default, auto-freezing is enabled.
165 */
166 setAutoFreeze(value: boolean) {
167 this.autoFreeze_ = value
168 }
169
170 /**
171 * Pass true to enable strict shallow copy.
172 *
173 * By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
174 */
175 setUseStrictShallowCopy(value: StrictMode) {
176 this.useStrictShallowCopy_ = value
177 }
178
179 /**
180 * Pass false to use faster iteration that skips non-enumerable properties
181 * but still handles symbols for compatibility.
182 *
183 * By default, strict iteration is enabled (includes all own properties).
184 */
185 setUseStrictIteration(value: boolean) {
186 this.useStrictIteration_ = value
187 }
188
189 shouldUseStrictIteration(): boolean {
190 return this.useStrictIteration_
191 }
192
193 applyPatches<T extends Objectish>(base: T, patches: readonly Patch[]): T {
194 // If a patch replaces the entire state, take that replacement as base
195 // before applying patches
196 let i: number
197 for (i = patches.length - 1; i >= 0; i--) {
198 const patch = patches[i]
199 if (patch.path.length === 0 && patch.op === "replace") {
200 base = patch.value
201 break
202 }
203 }
204 // If there was a patch that replaced the entire state, start from the
205 // patch after that.
206 if (i > -1) {
207 patches = patches.slice(i + 1)
208 }
209
210 const applyPatchesImpl = getPlugin("Patches").applyPatches_
211 if (isDraft(base)) {
212 // N.B: never hits if some patch a replacement, patches are never drafts
213 return applyPatchesImpl(base, patches)
214 }
215 // Otherwise, produce a copy of the base state.
216 return this.produce(base, (draft: Drafted) =>
217 applyPatchesImpl(draft, patches)
218 )
219 }
220}
221
222export function createProxy<T extends Objectish>(
223 value: T,
224 parent?: ImmerState
225): Drafted<T, ImmerState> {
226 // precondition: createProxy should be guarded by isDraftable, so we know we can safely draft
227 const draft: Drafted = isMap(value)
228 ? getPlugin("MapSet").proxyMap_(value, parent)
229 : isSet(value)
230 ? getPlugin("MapSet").proxySet_(value, parent)
231 : createProxyProxy(value, parent)
232
233 const scope = parent ? parent.scope_ : getCurrentScope()
234 scope.drafts_.push(draft)
235 return draft
236}
Note: See TracBrowser for help on using the repository browser.