349 lines
8.0 KiB
TypeScript
349 lines
8.0 KiB
TypeScript
// types only!
|
|
import {
|
|
ImmerState,
|
|
AnyMap,
|
|
AnySet,
|
|
MapState,
|
|
SetState,
|
|
DRAFT_STATE,
|
|
getCurrentScope,
|
|
latest,
|
|
iteratorSymbol,
|
|
isDraftable,
|
|
createProxy,
|
|
loadPlugin,
|
|
markChanged,
|
|
ProxyType,
|
|
die,
|
|
each
|
|
} from "../internal"
|
|
|
|
export function enableMapSet() {
|
|
/* istanbul ignore next */
|
|
var extendStatics = function(d: any, b: any): any {
|
|
extendStatics =
|
|
Object.setPrototypeOf ||
|
|
({__proto__: []} instanceof Array &&
|
|
function(d, b) {
|
|
d.__proto__ = b
|
|
}) ||
|
|
function(d, b) {
|
|
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]
|
|
}
|
|
return extendStatics(d, b)
|
|
}
|
|
|
|
// Ugly hack to resolve #502 and inherit built in Map / Set
|
|
function __extends(d: any, b: any): any {
|
|
extendStatics(d, b)
|
|
function __(this: any): any {
|
|
this.constructor = d
|
|
}
|
|
d.prototype =
|
|
// @ts-ignore
|
|
((__.prototype = b.prototype), new __())
|
|
}
|
|
|
|
const DraftMap = (function(_super) {
|
|
__extends(DraftMap, _super)
|
|
// Create class manually, cause #502
|
|
function DraftMap(this: any, target: AnyMap, parent?: ImmerState): any {
|
|
this[DRAFT_STATE] = {
|
|
type_: ProxyType.Map,
|
|
parent_: parent,
|
|
scope_: parent ? parent.scope_ : getCurrentScope()!,
|
|
modified_: false,
|
|
finalized_: false,
|
|
copy_: undefined,
|
|
assigned_: undefined,
|
|
base_: target,
|
|
draft_: this as any,
|
|
isManual_: false,
|
|
revoked_: false
|
|
} as MapState
|
|
return this
|
|
}
|
|
const p = DraftMap.prototype
|
|
|
|
Object.defineProperty(p, "size", {
|
|
get: function() {
|
|
return latest(this[DRAFT_STATE]).size
|
|
}
|
|
// enumerable: false,
|
|
// configurable: true
|
|
})
|
|
|
|
p.has = function(key: any): boolean {
|
|
return latest(this[DRAFT_STATE]).has(key)
|
|
}
|
|
|
|
p.set = function(key: any, value: any) {
|
|
const state: MapState = this[DRAFT_STATE]
|
|
assertUnrevoked(state)
|
|
if (!latest(state).has(key) || latest(state).get(key) !== value) {
|
|
prepareMapCopy(state)
|
|
markChanged(state)
|
|
state.assigned_!.set(key, true)
|
|
state.copy_!.set(key, value)
|
|
state.assigned_!.set(key, true)
|
|
}
|
|
return this
|
|
}
|
|
|
|
p.delete = function(key: any): boolean {
|
|
if (!this.has(key)) {
|
|
return false
|
|
}
|
|
|
|
const state: MapState = this[DRAFT_STATE]
|
|
assertUnrevoked(state)
|
|
prepareMapCopy(state)
|
|
markChanged(state)
|
|
if (state.base_.has(key)) {
|
|
state.assigned_!.set(key, false)
|
|
} else {
|
|
state.assigned_!.delete(key)
|
|
}
|
|
state.copy_!.delete(key)
|
|
return true
|
|
}
|
|
|
|
p.clear = function() {
|
|
const state: MapState = this[DRAFT_STATE]
|
|
assertUnrevoked(state)
|
|
if (latest(state).size) {
|
|
prepareMapCopy(state)
|
|
markChanged(state)
|
|
state.assigned_ = new Map()
|
|
each(state.base_, key => {
|
|
state.assigned_!.set(key, false)
|
|
})
|
|
state.copy_!.clear()
|
|
}
|
|
}
|
|
|
|
p.forEach = function(
|
|
cb: (value: any, key: any, self: any) => void,
|
|
thisArg?: any
|
|
) {
|
|
const state: MapState = this[DRAFT_STATE]
|
|
latest(state).forEach((_value: any, key: any, _map: any) => {
|
|
cb.call(thisArg, this.get(key), key, this)
|
|
})
|
|
}
|
|
|
|
p.get = function(key: any): any {
|
|
const state: MapState = this[DRAFT_STATE]
|
|
assertUnrevoked(state)
|
|
const value = latest(state).get(key)
|
|
if (state.finalized_ || !isDraftable(value)) {
|
|
return value
|
|
}
|
|
if (value !== state.base_.get(key)) {
|
|
return value // either already drafted or reassigned
|
|
}
|
|
// despite what it looks, this creates a draft only once, see above condition
|
|
const draft = createProxy(state.scope_.immer_, value, state)
|
|
prepareMapCopy(state)
|
|
state.copy_!.set(key, draft)
|
|
return draft
|
|
}
|
|
|
|
p.keys = function(): IterableIterator<any> {
|
|
return latest(this[DRAFT_STATE]).keys()
|
|
}
|
|
|
|
p.values = function(): IterableIterator<any> {
|
|
const iterator = this.keys()
|
|
return {
|
|
[iteratorSymbol]: () => this.values(),
|
|
next: () => {
|
|
const r = iterator.next()
|
|
/* istanbul ignore next */
|
|
if (r.done) return r
|
|
const value = this.get(r.value)
|
|
return {
|
|
done: false,
|
|
value
|
|
}
|
|
}
|
|
} as any
|
|
}
|
|
|
|
p.entries = function(): IterableIterator<[any, any]> {
|
|
const iterator = this.keys()
|
|
return {
|
|
[iteratorSymbol]: () => this.entries(),
|
|
next: () => {
|
|
const r = iterator.next()
|
|
/* istanbul ignore next */
|
|
if (r.done) return r
|
|
const value = this.get(r.value)
|
|
return {
|
|
done: false,
|
|
value: [r.value, value]
|
|
}
|
|
}
|
|
} as any
|
|
}
|
|
|
|
p[iteratorSymbol] = function() {
|
|
return this.entries()
|
|
}
|
|
|
|
return DraftMap
|
|
})(Map)
|
|
|
|
function proxyMap_<T extends AnyMap>(target: T, parent?: ImmerState): T {
|
|
// @ts-ignore
|
|
return new DraftMap(target, parent)
|
|
}
|
|
|
|
function prepareMapCopy(state: MapState) {
|
|
if (!state.copy_) {
|
|
state.assigned_ = new Map()
|
|
state.copy_ = new Map(state.base_)
|
|
}
|
|
}
|
|
|
|
const DraftSet = (function(_super) {
|
|
__extends(DraftSet, _super)
|
|
// Create class manually, cause #502
|
|
function DraftSet(this: any, target: AnySet, parent?: ImmerState) {
|
|
this[DRAFT_STATE] = {
|
|
type_: ProxyType.Set,
|
|
parent_: parent,
|
|
scope_: parent ? parent.scope_ : getCurrentScope()!,
|
|
modified_: false,
|
|
finalized_: false,
|
|
copy_: undefined,
|
|
base_: target,
|
|
draft_: this,
|
|
drafts_: new Map(),
|
|
revoked_: false,
|
|
isManual_: false
|
|
} as SetState
|
|
return this
|
|
}
|
|
const p = DraftSet.prototype
|
|
|
|
Object.defineProperty(p, "size", {
|
|
get: function() {
|
|
return latest(this[DRAFT_STATE]).size
|
|
}
|
|
// enumerable: true,
|
|
})
|
|
|
|
p.has = function(value: any): boolean {
|
|
const state: SetState = this[DRAFT_STATE]
|
|
assertUnrevoked(state)
|
|
// bit of trickery here, to be able to recognize both the value, and the draft of its value
|
|
if (!state.copy_) {
|
|
return state.base_.has(value)
|
|
}
|
|
if (state.copy_.has(value)) return true
|
|
if (state.drafts_.has(value) && state.copy_.has(state.drafts_.get(value)))
|
|
return true
|
|
return false
|
|
}
|
|
|
|
p.add = function(value: any): any {
|
|
const state: SetState = this[DRAFT_STATE]
|
|
assertUnrevoked(state)
|
|
if (!this.has(value)) {
|
|
prepareSetCopy(state)
|
|
markChanged(state)
|
|
state.copy_!.add(value)
|
|
}
|
|
return this
|
|
}
|
|
|
|
p.delete = function(value: any): any {
|
|
if (!this.has(value)) {
|
|
return false
|
|
}
|
|
|
|
const state: SetState = this[DRAFT_STATE]
|
|
assertUnrevoked(state)
|
|
prepareSetCopy(state)
|
|
markChanged(state)
|
|
return (
|
|
state.copy_!.delete(value) ||
|
|
(state.drafts_.has(value)
|
|
? state.copy_!.delete(state.drafts_.get(value))
|
|
: /* istanbul ignore next */ false)
|
|
)
|
|
}
|
|
|
|
p.clear = function() {
|
|
const state: SetState = this[DRAFT_STATE]
|
|
assertUnrevoked(state)
|
|
if (latest(state).size) {
|
|
prepareSetCopy(state)
|
|
markChanged(state)
|
|
state.copy_!.clear()
|
|
}
|
|
}
|
|
|
|
p.values = function(): IterableIterator<any> {
|
|
const state: SetState = this[DRAFT_STATE]
|
|
assertUnrevoked(state)
|
|
prepareSetCopy(state)
|
|
return state.copy_!.values()
|
|
}
|
|
|
|
p.entries = function entries(): IterableIterator<[any, any]> {
|
|
const state: SetState = this[DRAFT_STATE]
|
|
assertUnrevoked(state)
|
|
prepareSetCopy(state)
|
|
return state.copy_!.entries()
|
|
}
|
|
|
|
p.keys = function(): IterableIterator<any> {
|
|
return this.values()
|
|
}
|
|
|
|
p[iteratorSymbol] = function() {
|
|
return this.values()
|
|
}
|
|
|
|
p.forEach = function forEach(cb: any, thisArg?: any) {
|
|
const iterator = this.values()
|
|
let result = iterator.next()
|
|
while (!result.done) {
|
|
cb.call(thisArg, result.value, result.value, this)
|
|
result = iterator.next()
|
|
}
|
|
}
|
|
|
|
return DraftSet
|
|
})(Set)
|
|
|
|
function proxySet_<T extends AnySet>(target: T, parent?: ImmerState): T {
|
|
// @ts-ignore
|
|
return new DraftSet(target, parent)
|
|
}
|
|
|
|
function prepareSetCopy(state: SetState) {
|
|
if (!state.copy_) {
|
|
// create drafts for all entries to preserve insertion order
|
|
state.copy_ = new Set()
|
|
state.base_.forEach(value => {
|
|
if (isDraftable(value)) {
|
|
const draft = createProxy(state.scope_.immer_, value, state)
|
|
state.drafts_.set(value, draft)
|
|
state.copy_!.add(draft)
|
|
} else {
|
|
state.copy_!.add(value)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
function assertUnrevoked(state: any /*ES5State | MapState | SetState*/) {
|
|
if (state.revoked_) die(3, JSON.stringify(latest(state)))
|
|
}
|
|
|
|
loadPlugin("MapSet", {proxyMap_, proxySet_})
|
|
}
|