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