UNPKG

6.84 kBPlain TextView Raw
1// types only!
2import {
3 ImmerState,
4 AnyMap,
5 AnySet,
6 MapState,
7 SetState,
8 DRAFT_STATE,
9 getCurrentScope,
10 latest,
11 isDraftable,
12 createProxy,
13 loadPlugin,
14 markChanged,
15 die,
16 ArchType,
17 each
18} from "../internal"
19
20export function enableMapSet() {
21 class DraftMap extends Map {
22 [DRAFT_STATE]: MapState
23
24 constructor(target: AnyMap, parent?: ImmerState) {
25 super()
26 this[DRAFT_STATE] = {
27 type_: ArchType.Map,
28 parent_: parent,
29 scope_: parent ? parent.scope_ : getCurrentScope()!,
30 modified_: false,
31 finalized_: false,
32 copy_: undefined,
33 assigned_: undefined,
34 base_: target,
35 draft_: this as any,
36 isManual_: false,
37 revoked_: false
38 }
39 }
40
41 get size(): number {
42 return latest(this[DRAFT_STATE]).size
43 }
44
45 has(key: any): boolean {
46 return latest(this[DRAFT_STATE]).has(key)
47 }
48
49 set(key: any, value: any) {
50 const state: MapState = this[DRAFT_STATE]
51 assertUnrevoked(state)
52 if (!latest(state).has(key) || latest(state).get(key) !== value) {
53 prepareMapCopy(state)
54 markChanged(state)
55 state.assigned_!.set(key, true)
56 state.copy_!.set(key, value)
57 state.assigned_!.set(key, true)
58 }
59 return this
60 }
61
62 delete(key: any): boolean {
63 if (!this.has(key)) {
64 return false
65 }
66
67 const state: MapState = this[DRAFT_STATE]
68 assertUnrevoked(state)
69 prepareMapCopy(state)
70 markChanged(state)
71 if (state.base_.has(key)) {
72 state.assigned_!.set(key, false)
73 } else {
74 state.assigned_!.delete(key)
75 }
76 state.copy_!.delete(key)
77 return true
78 }
79
80 clear() {
81 const state: MapState = this[DRAFT_STATE]
82 assertUnrevoked(state)
83 if (latest(state).size) {
84 prepareMapCopy(state)
85 markChanged(state)
86 state.assigned_ = new Map()
87 each(state.base_, key => {
88 state.assigned_!.set(key, false)
89 })
90 state.copy_!.clear()
91 }
92 }
93
94 forEach(cb: (value: any, key: any, self: any) => void, thisArg?: any) {
95 const state: MapState = this[DRAFT_STATE]
96 latest(state).forEach((_value: any, key: any, _map: any) => {
97 cb.call(thisArg, this.get(key), key, this)
98 })
99 }
100
101 get(key: any): any {
102 const state: MapState = this[DRAFT_STATE]
103 assertUnrevoked(state)
104 const value = latest(state).get(key)
105 if (state.finalized_ || !isDraftable(value)) {
106 return value
107 }
108 if (value !== state.base_.get(key)) {
109 return value // either already drafted or reassigned
110 }
111 // despite what it looks, this creates a draft only once, see above condition
112 const draft = createProxy(value, state)
113 prepareMapCopy(state)
114 state.copy_!.set(key, draft)
115 return draft
116 }
117
118 keys(): IterableIterator<any> {
119 return latest(this[DRAFT_STATE]).keys()
120 }
121
122 values(): IterableIterator<any> {
123 const iterator = this.keys()
124 return {
125 [Symbol.iterator]: () => this.values(),
126 next: () => {
127 const r = iterator.next()
128 /* istanbul ignore next */
129 if (r.done) return r
130 const value = this.get(r.value)
131 return {
132 done: false,
133 value
134 }
135 }
136 } as any
137 }
138
139 entries(): IterableIterator<[any, any]> {
140 const iterator = this.keys()
141 return {
142 [Symbol.iterator]: () => this.entries(),
143 next: () => {
144 const r = iterator.next()
145 /* istanbul ignore next */
146 if (r.done) return r
147 const value = this.get(r.value)
148 return {
149 done: false,
150 value: [r.value, value]
151 }
152 }
153 } as any
154 }
155
156 [Symbol.iterator]() {
157 return this.entries()
158 }
159 }
160
161 function proxyMap_<T extends AnyMap>(target: T, parent?: ImmerState): T {
162 // @ts-ignore
163 return new DraftMap(target, parent)
164 }
165
166 function prepareMapCopy(state: MapState) {
167 if (!state.copy_) {
168 state.assigned_ = new Map()
169 state.copy_ = new Map(state.base_)
170 }
171 }
172
173 class DraftSet extends Set {
174 [DRAFT_STATE]: SetState
175 constructor(target: AnySet, parent?: ImmerState) {
176 super()
177 this[DRAFT_STATE] = {
178 type_: ArchType.Set,
179 parent_: parent,
180 scope_: parent ? parent.scope_ : getCurrentScope()!,
181 modified_: false,
182 finalized_: false,
183 copy_: undefined,
184 base_: target,
185 draft_: this,
186 drafts_: new Map(),
187 revoked_: false,
188 isManual_: false
189 }
190 }
191
192 get size(): number {
193 return latest(this[DRAFT_STATE]).size
194 }
195
196 has(value: any): boolean {
197 const state: SetState = this[DRAFT_STATE]
198 assertUnrevoked(state)
199 // bit of trickery here, to be able to recognize both the value, and the draft of its value
200 if (!state.copy_) {
201 return state.base_.has(value)
202 }
203 if (state.copy_.has(value)) return true
204 if (state.drafts_.has(value) && state.copy_.has(state.drafts_.get(value)))
205 return true
206 return false
207 }
208
209 add(value: any): any {
210 const state: SetState = this[DRAFT_STATE]
211 assertUnrevoked(state)
212 if (!this.has(value)) {
213 prepareSetCopy(state)
214 markChanged(state)
215 state.copy_!.add(value)
216 }
217 return this
218 }
219
220 delete(value: any): any {
221 if (!this.has(value)) {
222 return false
223 }
224
225 const state: SetState = this[DRAFT_STATE]
226 assertUnrevoked(state)
227 prepareSetCopy(state)
228 markChanged(state)
229 return (
230 state.copy_!.delete(value) ||
231 (state.drafts_.has(value)
232 ? state.copy_!.delete(state.drafts_.get(value))
233 : /* istanbul ignore next */ false)
234 )
235 }
236
237 clear() {
238 const state: SetState = this[DRAFT_STATE]
239 assertUnrevoked(state)
240 if (latest(state).size) {
241 prepareSetCopy(state)
242 markChanged(state)
243 state.copy_!.clear()
244 }
245 }
246
247 values(): IterableIterator<any> {
248 const state: SetState = this[DRAFT_STATE]
249 assertUnrevoked(state)
250 prepareSetCopy(state)
251 return state.copy_!.values()
252 }
253
254 entries(): IterableIterator<[any, any]> {
255 const state: SetState = this[DRAFT_STATE]
256 assertUnrevoked(state)
257 prepareSetCopy(state)
258 return state.copy_!.entries()
259 }
260
261 keys(): IterableIterator<any> {
262 return this.values()
263 }
264
265 [Symbol.iterator]() {
266 return this.values()
267 }
268
269 forEach(cb: any, thisArg?: any) {
270 const iterator = this.values()
271 let result = iterator.next()
272 while (!result.done) {
273 cb.call(thisArg, result.value, result.value, this)
274 result = iterator.next()
275 }
276 }
277 }
278 function proxySet_<T extends AnySet>(target: T, parent?: ImmerState): T {
279 // @ts-ignore
280 return new DraftSet(target, parent)
281 }
282
283 function prepareSetCopy(state: SetState) {
284 if (!state.copy_) {
285 // create drafts for all entries to preserve insertion order
286 state.copy_ = new Set()
287 state.base_.forEach(value => {
288 if (isDraftable(value)) {
289 const draft = createProxy(value, state)
290 state.drafts_.set(value, draft)
291 state.copy_!.add(draft)
292 } else {
293 state.copy_!.add(value)
294 }
295 })
296 }
297 }
298
299 function assertUnrevoked(state: any /*ES5State | MapState | SetState*/) {
300 if (state.revoked_) die(3, JSON.stringify(latest(state)))
301 }
302
303 loadPlugin("MapSet", {proxyMap_, proxySet_})
304}