| 1 | // types only!
|
|---|
| 2 | import {
|
|---|
| 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 |
|
|---|
| 20 | export 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 | }
|
|---|