1 | import {
|
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 |
|
29 | interface ProducersFns {
|
30 | produce: IProduce
|
31 | produceWithPatches: IProduceWithPatches
|
32 | }
|
33 |
|
34 | export type StrictMode = boolean | "class_only";
|
35 |
|
36 | export class Immer implements ProducersFns {
|
37 | autoFreeze_: boolean = true
|
38 | useStrictShallowCopy_: StrictMode = false
|
39 |
|
40 | constructor(config?: {
|
41 | autoFreeze?: boolean
|
42 | useStrictShallowCopy?: StrictMode
|
43 | }) {
|
44 | if (typeof config?.autoFreeze === "boolean")
|
45 | this.setAutoFreeze(config!.autoFreeze)
|
46 | if (typeof config?.useStrictShallowCopy === "boolean")
|
47 | this.setUseStrictShallowCopy(config!.useStrictShallowCopy)
|
48 | }
|
49 |
|
50 | |
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 | produce: IProduce = (base: any, recipe?: any, patchListener?: any) => {
|
70 |
|
71 | if (typeof base === "function" && typeof recipe !== "function") {
|
72 | const defaultBase = recipe
|
73 | recipe = base
|
74 |
|
75 | const self = this
|
76 | return function curriedProduce(
|
77 | this: any,
|
78 | base = defaultBase,
|
79 | ...args: any[]
|
80 | ) {
|
81 | return self.produce(base, (draft: Drafted) => recipe.call(this, draft, ...args))
|
82 | }
|
83 | }
|
84 |
|
85 | if (typeof recipe !== "function") die(6)
|
86 | if (patchListener !== undefined && typeof patchListener !== "function")
|
87 | die(7)
|
88 |
|
89 | let result
|
90 |
|
91 |
|
92 | if (isDraftable(base)) {
|
93 | const scope = enterScope(this)
|
94 | const proxy = createProxy(base, undefined)
|
95 | let hasError = true
|
96 | try {
|
97 | result = recipe(proxy)
|
98 | hasError = false
|
99 | } finally {
|
100 |
|
101 | if (hasError) revokeScope(scope)
|
102 | else leaveScope(scope)
|
103 | }
|
104 | usePatchesInScope(scope, patchListener)
|
105 | return processResult(result, scope)
|
106 | } else if (!base || typeof base !== "object") {
|
107 | result = recipe(base)
|
108 | if (result === undefined) result = base
|
109 | if (result === NOTHING) result = undefined
|
110 | if (this.autoFreeze_) freeze(result, true)
|
111 | if (patchListener) {
|
112 | const p: Patch[] = []
|
113 | const ip: Patch[] = []
|
114 | getPlugin("Patches").generateReplacementPatches_(base, result, p, ip)
|
115 | patchListener(p, ip)
|
116 | }
|
117 | return result
|
118 | } else die(1, base)
|
119 | }
|
120 |
|
121 | produceWithPatches: IProduceWithPatches = (base: any, recipe?: any): any => {
|
122 |
|
123 | if (typeof base === "function") {
|
124 | return (state: any, ...args: any[]) =>
|
125 | this.produceWithPatches(state, (draft: any) => base(draft, ...args))
|
126 | }
|
127 |
|
128 | let patches: Patch[], inversePatches: Patch[]
|
129 | const result = this.produce(base, recipe, (p: Patch[], ip: Patch[]) => {
|
130 | patches = p
|
131 | inversePatches = ip
|
132 | })
|
133 | return [result, patches!, inversePatches!]
|
134 | }
|
135 |
|
136 | createDraft<T extends Objectish>(base: T): Draft<T> {
|
137 | if (!isDraftable(base)) die(8)
|
138 | if (isDraft(base)) base = current(base)
|
139 | const scope = enterScope(this)
|
140 | const proxy = createProxy(base, undefined)
|
141 | proxy[DRAFT_STATE].isManual_ = true
|
142 | leaveScope(scope)
|
143 | return proxy as any
|
144 | }
|
145 |
|
146 | finishDraft<D extends Draft<any>>(
|
147 | draft: D,
|
148 | patchListener?: PatchListener
|
149 | ): D extends Draft<infer T> ? T : never {
|
150 | const state: ImmerState = draft && (draft as any)[DRAFT_STATE]
|
151 | if (!state || !state.isManual_) die(9)
|
152 | const {scope_: scope} = state
|
153 | usePatchesInScope(scope, patchListener)
|
154 | return processResult(undefined, scope)
|
155 | }
|
156 |
|
157 | |
158 |
|
159 |
|
160 |
|
161 |
|
162 | setAutoFreeze(value: boolean) {
|
163 | this.autoFreeze_ = value
|
164 | }
|
165 |
|
166 | |
167 |
|
168 |
|
169 |
|
170 |
|
171 | setUseStrictShallowCopy(value: StrictMode) {
|
172 | this.useStrictShallowCopy_ = value
|
173 | }
|
174 |
|
175 | applyPatches<T extends Objectish>(base: T, patches: readonly Patch[]): T {
|
176 |
|
177 |
|
178 | let i: number
|
179 | for (i = patches.length - 1; i >= 0; i--) {
|
180 | const patch = patches[i]
|
181 | if (patch.path.length === 0 && patch.op === "replace") {
|
182 | base = patch.value
|
183 | break
|
184 | }
|
185 | }
|
186 |
|
187 |
|
188 | if (i > -1) {
|
189 | patches = patches.slice(i + 1)
|
190 | }
|
191 |
|
192 | const applyPatchesImpl = getPlugin("Patches").applyPatches_
|
193 | if (isDraft(base)) {
|
194 |
|
195 | return applyPatchesImpl(base, patches)
|
196 | }
|
197 |
|
198 | return this.produce(base, (draft: Drafted) =>
|
199 | applyPatchesImpl(draft, patches)
|
200 | )
|
201 | }
|
202 | }
|
203 |
|
204 | export function createProxy<T extends Objectish>(
|
205 | value: T,
|
206 | parent?: ImmerState
|
207 | ): Drafted<T, ImmerState> {
|
208 |
|
209 | const draft: Drafted = isMap(value)
|
210 | ? getPlugin("MapSet").proxyMap_(value, parent)
|
211 | : isSet(value)
|
212 | ? getPlugin("MapSet").proxySet_(value, parent)
|
213 | : createProxyProxy(value, parent)
|
214 |
|
215 | const scope = parent ? parent.scope_ : getCurrentScope()
|
216 | scope.drafts_.push(draft)
|
217 | return draft
|
218 | }
|