immer
Version:
Create your next immutable state by mutating the current one
1,764 lines (1,449 loc) • 53.2 kB
JavaScript
// Should be no imports here!
var _a; // SOme things that should be evaluated before all else...
var hasSymbol = typeof Symbol !== "undefined";
var hasMap = typeof Map !== "undefined";
var hasSet = typeof Set !== "undefined";
/**
* The sentinel value returned by producers to replace the draft with undefined.
*/
var NOTHING = hasSymbol ? Symbol("immer-nothing") : (_a = {}, _a["immer-nothing"] = true, _a);
/**
* To let Immer treat your class instances as plain immutable objects
* (albeit with a custom prototype), you must define either an instance property
* or a static property on each of your custom classes.
*
* Otherwise, your class instance will never be drafted, which means it won't be
* safe to mutate in a produce callback.
*/
var DRAFTABLE = hasSymbol ? Symbol("immer-draftable") : "__$immer_draftable";
var DRAFT_STATE = hasSymbol ? Symbol("immer-state") : "__$immer_state";
var iteratorSymbol = hasSymbol ? Symbol.iterator : "@@iterator";
/* istanbul ignore next */
var extendStatics = function (d, b) {
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, b) {
extendStatics(d, b);
function __() {
this.constructor = d;
}
d.prototype = ( // @ts-ignore
__.prototype = b.prototype, new __());
}
var Archtype;
(function (Archtype) {
Archtype[Archtype["Object"] = 0] = "Object";
Archtype[Archtype["Array"] = 1] = "Array";
Archtype[Archtype["Map"] = 2] = "Map";
Archtype[Archtype["Set"] = 3] = "Set";
})(Archtype || (Archtype = {}));
var ProxyType;
(function (ProxyType) {
ProxyType[ProxyType["ProxyObject"] = 0] = "ProxyObject";
ProxyType[ProxyType["ProxyArray"] = 1] = "ProxyArray";
ProxyType[ProxyType["ES5Object"] = 2] = "ES5Object";
ProxyType[ProxyType["ES5Array"] = 3] = "ES5Array";
ProxyType[ProxyType["Map"] = 4] = "Map";
ProxyType[ProxyType["Set"] = 5] = "Set";
})(ProxyType || (ProxyType = {}));
/** Returns true if the given value is an Immer draft */
function isDraft(value) {
return !!value && !!value[DRAFT_STATE];
}
/** Returns true if the given value can be drafted by Immer */
function isDraftable(value) {
if (!value) { return false; }
return isPlainObject(value) || Array.isArray(value) || !!value[DRAFTABLE] || !!value.constructor[DRAFTABLE] || isMap(value) || isSet(value);
}
function isPlainObject(value) {
if (!value || typeof value !== "object") { return false; }
var proto = Object.getPrototypeOf(value);
return !proto || proto === Object.prototype;
}
function original(value) {
if (value && value[DRAFT_STATE]) {
return value[DRAFT_STATE].base;
} // otherwise return undefined
}
var ownKeys = typeof Reflect !== "undefined" && Reflect.ownKeys ? Reflect.ownKeys : typeof Object.getOwnPropertySymbols !== "undefined" ? function (obj) {
return Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj));
} :
/* istanbul ignore next */
Object.getOwnPropertyNames;
function each(obj, iter) {
if (getArchtype(obj) === Archtype.Object) {
ownKeys(obj).forEach(function (key) {
return iter(key, obj[key], obj);
});
} else {
obj.forEach(function (entry, index) {
return iter(index, entry, obj);
});
}
}
function isEnumerable(base, prop) {
var desc = Object.getOwnPropertyDescriptor(base, prop);
return desc && desc.enumerable ? true : false;
}
function getArchtype(thing) {
/* istanbul ignore next */
if (!thing) { die(); }
if (thing[DRAFT_STATE]) {
switch (thing[DRAFT_STATE].type) {
case ProxyType.ES5Object:
case ProxyType.ProxyObject:
return Archtype.Object;
case ProxyType.ES5Array:
case ProxyType.ProxyArray:
return Archtype.Array;
case ProxyType.Map:
return Archtype.Map;
case ProxyType.Set:
return Archtype.Set;
}
}
return Array.isArray(thing) ? Archtype.Array : isMap(thing) ? Archtype.Map : isSet(thing) ? Archtype.Set : Archtype.Object;
}
function has(thing, prop) {
return getArchtype(thing) === Archtype.Map ? thing.has(prop) : Object.prototype.hasOwnProperty.call(thing, prop);
}
function get(thing, prop) {
// @ts-ignore
return getArchtype(thing) === Archtype.Map ? thing.get(prop) : thing[prop];
}
function set(thing, propOrOldValue, value) {
switch (getArchtype(thing)) {
case Archtype.Map:
thing.set(propOrOldValue, value);
break;
case Archtype.Set:
thing.delete(propOrOldValue);
thing.add(value);
break;
default:
thing[propOrOldValue] = value;
}
}
function is(x, y) {
// From: https://github.com/facebook/fbjs/blob/c69904a511b900266935168223063dd8772dfc40/packages/fbjs/src/core/shallowEqual.js
if (x === y) {
return x !== 0 || 1 / x === 1 / y;
} else {
return x !== x && y !== y;
}
}
function isMap(target) {
return hasMap && target instanceof Map;
}
function isSet(target) {
return hasSet && target instanceof Set;
}
function latest(state) {
return state.copy || state.base;
}
function shallowCopy(base, invokeGetters) {
if (invokeGetters === void 0) {
invokeGetters = false;
}
if (Array.isArray(base)) { return base.slice(); }
var clone = Object.create(Object.getPrototypeOf(base));
ownKeys(base).forEach(function (key) {
if (key === DRAFT_STATE) {
return; // Never copy over draft state.
}
var desc = Object.getOwnPropertyDescriptor(base, key);
var value = desc.value;
if (desc.get) {
if (!invokeGetters) {
throw new Error("Immer drafts cannot have computed properties");
}
value = desc.get.call(base);
}
if (desc.enumerable) {
clone[key] = value;
} else {
Object.defineProperty(clone, key, {
value: value,
writable: true,
configurable: true
});
}
});
return clone;
}
function freeze(obj, deep) {
if (!isDraftable(obj) || isDraft(obj) || Object.isFrozen(obj)) { return; }
var type = getArchtype(obj);
if (type === Archtype.Set) {
obj.add = obj.clear = obj.delete = dontMutateFrozenCollections;
} else if (type === Archtype.Map) {
obj.set = obj.clear = obj.delete = dontMutateFrozenCollections;
}
Object.freeze(obj);
if (deep) { each(obj, function (_, value) {
return freeze(value, true);
}); }
}
function dontMutateFrozenCollections() {
throw new Error("This object has been frozen and should not be mutated");
}
function createHiddenProperty(target, prop, value) {
Object.defineProperty(target, prop, {
value: value,
enumerable: false,
writable: true
});
}
/* istanbul ignore next */
function die() {
throw new Error("Illegal state, please file a bug");
}
/** Each scope represents a `produce` call. */
var ImmerScope =
/** @class */
function () {
function ImmerScope(parent, immer) {
this.drafts = [];
this.parent = parent;
this.immer = immer; // Whenever the modified draft contains a draft from another scope, we
// need to prevent auto-freezing so the unowned draft can be finalized.
this.canAutoFreeze = true;
}
ImmerScope.prototype.usePatches = function (patchListener) {
if (patchListener) {
this.patches = [];
this.inversePatches = [];
this.patchListener = patchListener;
}
};
ImmerScope.prototype.revoke = function () {
this.leave();
this.drafts.forEach(revoke); // @ts-ignore
this.drafts = null;
};
ImmerScope.prototype.leave = function () {
if (this === ImmerScope.current) {
ImmerScope.current = this.parent;
}
};
ImmerScope.enter = function (immer) {
var scope = new ImmerScope(ImmerScope.current, immer);
ImmerScope.current = scope;
return scope;
};
return ImmerScope;
}();
function revoke(draft) {
var state = draft[DRAFT_STATE];
if (state.type === ProxyType.ProxyObject || state.type === ProxyType.ProxyArray) { state.revoke(); }else { state.revoked = true; }
}
function processResult(immer, result, scope) {
var baseDraft = scope.drafts[0];
var isReplaced = result !== undefined && result !== baseDraft;
immer.willFinalize(scope, result, isReplaced);
if (isReplaced) {
if (baseDraft[DRAFT_STATE].modified) {
scope.revoke();
throw new Error("An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."); // prettier-ignore
}
if (isDraftable(result)) {
// Finalize the result in case it contains (or is) a subset of the draft.
result = finalize(immer, result, scope);
maybeFreeze(immer, result);
}
if (scope.patches) {
scope.patches.push({
op: "replace",
path: [],
value: result
});
scope.inversePatches.push({
op: "replace",
path: [],
value: baseDraft[DRAFT_STATE].base
});
}
} else {
// Finalize the base draft.
result = finalize(immer, baseDraft, scope, []);
}
scope.revoke();
if (scope.patches) {
scope.patchListener(scope.patches, scope.inversePatches);
}
return result !== NOTHING ? result : undefined;
}
function finalize(immer, draft, scope, path) {
var state = draft[DRAFT_STATE];
if (!state) {
if (Object.isFrozen(draft)) { return draft; }
return finalizeTree(immer, draft, scope);
} // Never finalize drafts owned by another scope.
if (state.scope !== scope) {
return draft;
}
if (!state.modified) {
maybeFreeze(immer, state.base, true);
return state.base;
}
if (!state.finalized) {
state.finalized = true;
finalizeTree(immer, state.draft, scope, path); // We cannot really delete anything inside of a Set. We can only replace the whole Set.
if (immer.onDelete && state.type !== ProxyType.Set) {
// The `assigned` object is unreliable with ES5 drafts.
if (immer.useProxies) {
var assigned = state.assigned;
each(assigned, function (prop, exists) {
if (!exists) { immer.onDelete(state, prop); }
});
} else {
var base = state.base,
copy_1 = state.copy;
each(base, function (prop) {
if (!has(copy_1, prop)) { immer.onDelete(state, prop); }
});
}
}
if (immer.onCopy) {
immer.onCopy(state);
} // At this point, all descendants of `state.copy` have been finalized,
// so we can be sure that `scope.canAutoFreeze` is accurate.
if (immer.autoFreeze && scope.canAutoFreeze) {
freeze(state.copy, false);
}
if (path && scope.patches) {
generatePatches(state, path, scope.patches, scope.inversePatches);
}
}
return state.copy;
}
function finalizeTree(immer, root, scope, rootPath) {
var state = root[DRAFT_STATE];
if (state) {
if (state.type === ProxyType.ES5Object || state.type === ProxyType.ES5Array) {
// Create the final copy, with added keys and without deleted keys.
state.copy = shallowCopy(state.draft, true);
}
root = state.copy;
}
each(root, function (key, value) {
return finalizeProperty(immer, scope, root, state, root, key, value, rootPath);
});
return root;
}
function finalizeProperty(immer, scope, root, rootState, parentValue, prop, childValue, rootPath) {
if (childValue === parentValue) {
throw Error("Immer forbids circular references");
} // In the `finalizeTree` method, only the `root` object may be a draft.
var isDraftProp = !!rootState && parentValue === root;
var isSetMember = isSet(parentValue);
if (isDraft(childValue)) {
var path = rootPath && isDraftProp && !isSetMember && // Set objects are atomic since they have no keys.
!has(rootState.assigned, prop) // Skip deep patches for assigned keys.
? rootPath.concat(prop) : undefined; // Drafts owned by `scope` are finalized here.
childValue = finalize(immer, childValue, scope, path);
set(parentValue, prop, childValue); // Drafts from another scope must prevent auto-freezing.
if (isDraft(childValue)) {
scope.canAutoFreeze = false;
}
} // Unchanged draft properties are ignored.
else if (isDraftProp && is(childValue, get(rootState.base, prop))) {
return;
} // Search new objects for unfinalized drafts. Frozen objects should never contain drafts.
// TODO: the recursion over here looks weird, shouldn't non-draft stuff have it's own recursion?
// especially the passing on of root and rootState doesn't make sense...
else if (isDraftable(childValue) && !Object.isFrozen(childValue)) {
each(childValue, function (key, grandChild) {
return finalizeProperty(immer, scope, root, rootState, childValue, key, grandChild, rootPath);
});
maybeFreeze(immer, childValue);
}
if (isDraftProp && immer.onAssign && !isSetMember) {
immer.onAssign(rootState, prop, childValue);
}
}
function maybeFreeze(immer, value, deep) {
if (deep === void 0) {
deep = false;
}
if (immer.autoFreeze && !isDraft(value)) {
freeze(value, deep);
}
}
/**
* Returns a new draft of the `base` object.
*
* The second argument is the parent draft-state (used internally).
*/
function createProxy(base, parent) {
var isArray = Array.isArray(base);
var state = {
type: isArray ? ProxyType.ProxyArray : ProxyType.ProxyObject,
// Track which produce call this is associated with.
scope: parent ? parent.scope : ImmerScope.current,
// True for both shallow and deep changes.
modified: false,
// Used during finalization.
finalized: false,
// Track which properties have been assigned (true) or deleted (false).
assigned: {},
// The parent draft state.
parent: parent,
// The base state.
base: base,
// The base proxy.
draft: null,
// Any property proxies.
drafts: {},
// The base copy with any updated values.
copy: null,
// Called by the `produce` function.
revoke: null,
isManual: false
}; // the traps must target something, a bit like the 'real' base.
// but also, we need to be able to determine from the target what the relevant state is
// (to avoid creating traps per instance to capture the state in closure,
// and to avoid creating weird hidden properties as well)
// So the trick is to use 'state' as the actual 'target'! (and make sure we intercept everything)
// Note that in the case of an array, we put the state in an array to have better Reflect defaults ootb
var target = state;
var traps = objectTraps;
if (isArray) {
target = [state];
traps = arrayTraps;
} // TODO: optimization: might be faster, cheaper if we created a non-revocable proxy
// and administrate revoking ourselves
var _a = Proxy.revocable(target, traps),
revoke = _a.revoke,
proxy = _a.proxy;
state.draft = proxy;
state.revoke = revoke;
return proxy;
}
/**
* Object drafts
*/
var objectTraps = {
get: function (state, prop) {
if (prop === DRAFT_STATE) { return state; }
var drafts = state.drafts; // Check for existing draft in unmodified state.
if (!state.modified && has(drafts, prop)) {
return drafts[prop];
}
var value = latest(state)[prop];
if (state.finalized || !isDraftable(value)) {
return value;
} // Check for existing draft in modified state.
if (state.modified) {
// Assigned values are never drafted. This catches any drafts we created, too.
if (value !== peek(state.base, prop)) { return value; } // Store drafts on the copy (when one exists).
// @ts-ignore
drafts = state.copy;
}
return drafts[prop] = state.scope.immer.createProxy(value, state);
},
has: function (state, prop) {
return prop in latest(state);
},
ownKeys: function (state) {
return Reflect.ownKeys(latest(state));
},
set: function (state, prop
/* strictly not, but helps TS */
, value) {
if (!state.modified) {
var baseValue = peek(state.base, prop); // Optimize based on value's truthiness. Truthy values are guaranteed to
// never be undefined, so we can avoid the `in` operator. Lastly, truthy
// values may be drafts, but falsy values are never drafts.
var isUnchanged = value ? is(baseValue, value) || value === state.drafts[prop] : is(baseValue, value) && prop in state.base;
if (isUnchanged) { return true; }
prepareCopy(state);
markChanged(state);
}
state.assigned[prop] = true; // @ts-ignore
state.copy[prop] = value;
return true;
},
deleteProperty: function (state, prop) {
// The `undefined` check is a fast path for pre-existing keys.
if (peek(state.base, prop) !== undefined || prop in state.base) {
state.assigned[prop] = false;
prepareCopy(state);
markChanged(state);
} else if (state.assigned[prop]) {
// if an originally not assigned property was deleted
delete state.assigned[prop];
} // @ts-ignore
if (state.copy) { delete state.copy[prop]; }
return true;
},
// Note: We never coerce `desc.value` into an Immer draft, because we can't make
// the same guarantee in ES5 mode.
getOwnPropertyDescriptor: function (state, prop) {
var owner = latest(state);
var desc = Reflect.getOwnPropertyDescriptor(owner, prop);
if (desc) {
desc.writable = true;
desc.configurable = state.type !== ProxyType.ProxyArray || prop !== "length";
}
return desc;
},
defineProperty: function () {
throw new Error("Object.defineProperty() cannot be used on an Immer draft"); // prettier-ignore
},
getPrototypeOf: function (state) {
return Object.getPrototypeOf(state.base);
},
setPrototypeOf: function () {
throw new Error("Object.setPrototypeOf() cannot be used on an Immer draft"); // prettier-ignore
}
};
/**
* Array drafts
*/
var arrayTraps = {};
each(objectTraps, function (key, fn) {
// @ts-ignore
arrayTraps[key] = function () {
arguments[0] = arguments[0][0];
return fn.apply(this, arguments);
};
});
arrayTraps.deleteProperty = function (state, prop) {
if (isNaN(parseInt(prop))) {
throw new Error("Immer only supports deleting array indices"); // prettier-ignore
}
return objectTraps.deleteProperty.call(this, state[0], prop);
};
arrayTraps.set = function (state, prop, value) {
if (prop !== "length" && isNaN(parseInt(prop))) {
throw new Error("Immer only supports setting array indices and the 'length' property"); // prettier-ignore
}
return objectTraps.set.call(this, state[0], prop, value, state[0]);
};
/**
* Map drafts
*/
// Access a property without creating an Immer draft.
function peek(draft, prop) {
var state = draft[DRAFT_STATE];
var desc = Reflect.getOwnPropertyDescriptor(state ? latest(state) : draft, prop);
return desc && desc.value;
}
function markChanged(state) {
if (!state.modified) {
state.modified = true;
if (state.type === ProxyType.ProxyObject || state.type === ProxyType.ProxyArray) {
var copy_1 = state.copy = shallowCopy(state.base);
each(state.drafts, function (key, value) {
// @ts-ignore
copy_1[key] = value;
});
state.drafts = undefined;
}
if (state.parent) {
markChanged(state.parent);
}
}
}
function prepareCopy(state) {
if (!state.copy) {
state.copy = shallowCopy(state.base);
}
}
function willFinalizeES5(scope, result, isReplaced) {
scope.drafts.forEach(function (draft) {
draft[DRAFT_STATE].finalizing = true;
});
if (!isReplaced) {
if (scope.patches) {
markChangesRecursively(scope.drafts[0]);
} // This is faster when we don't care about which attributes changed.
markChangesSweep(scope.drafts);
} // When a child draft is returned, look for changes.
else if (isDraft(result) && result[DRAFT_STATE].scope === scope) {
markChangesSweep(scope.drafts);
}
}
function createES5Proxy(base, parent) {
var isArray = Array.isArray(base);
var draft = clonePotentialDraft(base);
each(draft, function (prop) {
proxyProperty(draft, prop, isArray || isEnumerable(base, prop));
});
var state = {
type: isArray ? ProxyType.ES5Array : ProxyType.ES5Object,
scope: parent ? parent.scope : ImmerScope.current,
modified: false,
finalizing: false,
finalized: false,
assigned: {},
parent: parent,
base: base,
draft: draft,
copy: null,
revoked: false,
isManual: false
};
createHiddenProperty(draft, DRAFT_STATE, state);
return draft;
} // Access a property without creating an Immer draft.
function peek$1(draft, prop) {
var state = draft[DRAFT_STATE];
if (state && !state.finalizing) {
state.finalizing = true;
var value = draft[prop];
state.finalizing = false;
return value;
}
return draft[prop];
}
function get$1(state, prop) {
assertUnrevoked(state);
var value = peek$1(latest(state), prop);
if (state.finalizing) { return value; } // Create a draft if the value is unmodified.
if (value === peek$1(state.base, prop) && isDraftable(value)) {
prepareCopy$1(state); // @ts-ignore
return state.copy[prop] = state.scope.immer.createProxy(value, state);
}
return value;
}
function set$1(state, prop, value) {
assertUnrevoked(state);
state.assigned[prop] = true;
if (!state.modified) {
if (is(value, peek$1(latest(state), prop))) { return; }
markChangedES5(state);
prepareCopy$1(state);
} // @ts-ignore
state.copy[prop] = value;
}
function markChangedES5(state) {
if (!state.modified) {
state.modified = true;
if (state.parent) { markChangedES5(state.parent); }
}
}
function prepareCopy$1(state) {
if (!state.copy) { state.copy = clonePotentialDraft(state.base); }
}
function clonePotentialDraft(base) {
var state = base && base[DRAFT_STATE];
if (state) {
state.finalizing = true;
var draft = shallowCopy(state.draft, true);
state.finalizing = false;
return draft;
}
return shallowCopy(base);
} // property descriptors are recycled to make sure we don't create a get and set closure per property,
// but share them all instead
var descriptors = {};
function proxyProperty(draft, prop, enumerable) {
var desc = descriptors[prop];
if (desc) {
desc.enumerable = enumerable;
} else {
descriptors[prop] = desc = {
configurable: true,
enumerable: enumerable,
get: function () {
return get$1(this[DRAFT_STATE], prop);
},
set: function (value) {
set$1(this[DRAFT_STATE], prop, value);
}
};
}
Object.defineProperty(draft, prop, desc);
}
function assertUnrevoked(state) {
if (state.revoked === true) { throw new Error("Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? " + JSON.stringify(latest(state))); }
} // This looks expensive, but only proxies are visited, and only objects without known changes are scanned.
function markChangesSweep(drafts) {
// The natural order of drafts in the `scope` array is based on when they
// were accessed. By processing drafts in reverse natural order, we have a
// better chance of processing leaf nodes first. When a leaf node is known to
// have changed, we can avoid any traversal of its ancestor nodes.
for (var i = drafts.length - 1; i >= 0; i--) {
var state = drafts[i][DRAFT_STATE];
if (!state.modified) {
switch (state.type) {
case ProxyType.ES5Array:
if (hasArrayChanges(state)) { markChangedES5(state); }
break;
case ProxyType.ES5Object:
if (hasObjectChanges(state)) { markChangedES5(state); }
break;
}
}
}
}
function markChangesRecursively(object) {
if (!object || typeof object !== "object") { return; }
var state = object[DRAFT_STATE];
if (!state) { return; }
var base = state.base,
draft = state.draft,
assigned = state.assigned,
type = state.type;
if (type === ProxyType.ES5Object) {
// Look for added keys.
// TODO: looks quite duplicate to hasObjectChanges,
// probably there is a faster way to detect changes, as sweep + recurse seems to do some
// unnecessary work.
// also: probably we can store the information we detect here, to speed up tree finalization!
each(draft, function (key) {
if (key === DRAFT_STATE) { return; } // The `undefined` check is a fast path for pre-existing keys.
if (base[key] === undefined && !has(base, key)) {
assigned[key] = true;
markChangedES5(state);
} else if (!assigned[key]) {
// Only untouched properties trigger recursion.
markChangesRecursively(draft[key]);
}
}); // Look for removed keys.
each(base, function (key) {
// The `undefined` check is a fast path for pre-existing keys.
if (draft[key] === undefined && !has(draft, key)) {
assigned[key] = false;
markChangedES5(state);
}
});
} else if (type === ProxyType.ES5Array && hasArrayChanges(state)) {
markChangedES5(state);
assigned.length = true;
if (draft.length < base.length) {
for (var i = draft.length; i < base.length; i++) { assigned[i] = false; }
} else {
for (var i = base.length; i < draft.length; i++) { assigned[i] = true; }
}
for (var i = 0; i < draft.length; i++) {
// Only untouched indices trigger recursion.
if (assigned[i] === undefined) { markChangesRecursively(draft[i]); }
}
}
}
function hasObjectChanges(state) {
var base = state.base,
draft = state.draft; // Search for added keys and changed keys. Start at the back, because
// non-numeric keys are ordered by time of definition on the object.
var keys = Object.keys(draft);
for (var i = keys.length - 1; i >= 0; i--) {
var key = keys[i];
var baseValue = base[key]; // The `undefined` check is a fast path for pre-existing keys.
if (baseValue === undefined && !has(base, key)) {
return true;
} // Once a base key is deleted, future changes go undetected, because its
// descriptor is erased. This branch detects any missed changes.
else {
var value = draft[key];
var state_1 = value && value[DRAFT_STATE];
if (state_1 ? state_1.base !== baseValue : !is(value, baseValue)) {
return true;
}
}
} // At this point, no keys were added or changed.
// Compare key count to determine if keys were deleted.
return keys.length !== Object.keys(base).length;
}
function hasArrayChanges(state) {
var draft = state.draft;
if (draft.length !== state.base.length) { return true; } // See #116
// If we first shorten the length, our array interceptors will be removed.
// If after that new items are added, result in the same original length,
// those last items will have no intercepting property.
// So if there is no own descriptor on the last position, we know that items were removed and added
// N.B.: splice, unshift, etc only shift values around, but not prop descriptors, so we only have to check
// the last one
var descriptor = Object.getOwnPropertyDescriptor(draft, draft.length - 1); // descriptor can be null, but only for newly created sparse arrays, eg. new Array(10)
if (descriptor && !descriptor.get) { return true; } // For all other cases, we don't have to compare, as they would have been picked up by the index setters
return false;
}
var DraftMap = function (_super) {
if (!_super) {
/* istanbul ignore next */
throw new Error("Map is not polyfilled");
}
__extends(DraftMap, _super); // Create class manually, cause #502
function DraftMap(target, parent) {
this[DRAFT_STATE] = {
type: ProxyType.Map,
parent: parent,
scope: parent ? parent.scope : ImmerScope.current,
modified: false,
finalized: false,
copy: undefined,
assigned: undefined,
base: target,
draft: this,
isManual: false,
revoked: false
};
return this;
}
var p = DraftMap.prototype; // TODO: smaller build size if we create a util for Object.defineProperty
Object.defineProperty(p, "size", {
get: function () {
return latest(this[DRAFT_STATE]).size;
},
enumerable: true,
configurable: true
});
p.has = function (key) {
return latest(this[DRAFT_STATE]).has(key);
};
p.set = function (key, value) {
var state = this[DRAFT_STATE];
assertUnrevoked(state);
if (latest(state).get(key) !== value) {
prepareCopy$2(state);
state.scope.immer.markChanged(state);
state.assigned.set(key, true);
state.copy.set(key, value);
state.assigned.set(key, true);
}
return this;
};
p.delete = function (key) {
if (!this.has(key)) {
return false;
}
var state = this[DRAFT_STATE];
assertUnrevoked(state);
prepareCopy$2(state);
state.scope.immer.markChanged(state);
state.assigned.set(key, false);
state.copy.delete(key);
return true;
};
p.clear = function () {
var state = this[DRAFT_STATE];
assertUnrevoked(state);
prepareCopy$2(state);
state.scope.immer.markChanged(state);
state.assigned = new Map();
return state.copy.clear();
};
p.forEach = function (cb, thisArg) {
var _this = this;
var state = this[DRAFT_STATE];
latest(state).forEach(function (_value, key, _map) {
cb.call(thisArg, _this.get(key), key, _this);
});
};
p.get = function (key) {
var state = this[DRAFT_STATE];
assertUnrevoked(state);
var 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
var draft = state.scope.immer.createProxy(value, state);
prepareCopy$2(state);
state.copy.set(key, draft);
return draft;
};
p.keys = function () {
return latest(this[DRAFT_STATE]).keys();
};
p.values = function () {
var _a;
var _this = this;
var iterator = this.keys();
return _a = {}, _a[iteratorSymbol] = function () {
return _this.values();
}, _a.next = function () {
var r = iterator.next();
/* istanbul ignore next */
if (r.done) { return r; }
var value = _this.get(r.value);
return {
done: false,
value: value
};
}, _a;
};
p.entries = function () {
var _a;
var _this = this;
var iterator = this.keys();
return _a = {}, _a[iteratorSymbol] = function () {
return _this.entries();
}, _a.next = function () {
var r = iterator.next();
/* istanbul ignore next */
if (r.done) { return r; }
var value = _this.get(r.value);
return {
done: false,
value: [r.value, value]
};
}, _a;
};
p[iteratorSymbol] = function () {
return this.entries();
};
return DraftMap;
}(Map);
function proxyMap(target, parent) {
// @ts-ignore
return new DraftMap(target, parent);
}
function prepareCopy$2(state) {
if (!state.copy) {
state.assigned = new Map();
state.copy = new Map(state.base);
}
}
var DraftSet = function (_super) {
if (!_super) {
/* istanbul ignore next */
throw new Error("Set is not polyfilled");
}
__extends(DraftSet, _super); // Create class manually, cause #502
function DraftSet(target, parent) {
this[DRAFT_STATE] = {
type: ProxyType.Set,
parent: parent,
scope: parent ? parent.scope : ImmerScope.current,
modified: false,
finalized: false,
copy: undefined,
base: target,
draft: this,
drafts: new Map(),
revoked: false,
isManual: false
};
return this;
}
var p = DraftSet.prototype;
Object.defineProperty(p, "size", {
get: function () {
return latest(this[DRAFT_STATE]).size;
},
enumerable: true,
configurable: true
});
p.has = function (value) {
var state = 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) {
var state = this[DRAFT_STATE];
assertUnrevoked(state);
if (state.copy) {
state.copy.add(value);
} else if (!state.base.has(value)) {
prepareCopy$3(state);
state.scope.immer.markChanged(state);
state.copy.add(value);
}
return this;
};
p.delete = function (value) {
if (!this.has(value)) {
return false;
}
var state = this[DRAFT_STATE];
assertUnrevoked(state);
prepareCopy$3(state);
state.scope.immer.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 () {
var state = this[DRAFT_STATE];
assertUnrevoked(state);
prepareCopy$3(state);
state.scope.immer.markChanged(state);
return state.copy.clear();
};
p.values = function () {
var state = this[DRAFT_STATE];
assertUnrevoked(state);
prepareCopy$3(state);
return state.copy.values();
};
p.entries = function entries() {
var state = this[DRAFT_STATE];
assertUnrevoked(state);
prepareCopy$3(state);
return state.copy.entries();
};
p.keys = function () {
return this.values();
};
p[iteratorSymbol] = function () {
return this.values();
};
p.forEach = function forEach(cb, thisArg) {
var iterator = this.values();
var result = iterator.next();
while (!result.done) {
cb.call(thisArg, result.value, result.value, this);
result = iterator.next();
}
};
return DraftSet;
}(Set);
function proxySet(target, parent) {
// @ts-ignore
return new DraftSet(target, parent);
}
function prepareCopy$3(state) {
if (!state.copy) {
// create drafts for all entries to preserve insertion order
state.copy = new Set();
state.base.forEach(function (value) {
if (isDraftable(value)) {
var draft = state.scope.immer.createProxy(value, state);
state.drafts.set(value, draft);
state.copy.add(draft);
} else {
state.copy.add(value);
}
});
}
}
function generatePatches(state, basePath, patches, inversePatches) {
switch (state.type) {
case ProxyType.ProxyObject:
case ProxyType.ES5Object:
case ProxyType.Map:
return generatePatchesFromAssigned(state, basePath, patches, inversePatches);
case ProxyType.ES5Array:
case ProxyType.ProxyArray:
return generateArrayPatches(state, basePath, patches, inversePatches);
case ProxyType.Set:
return generateSetPatches(state, basePath, patches, inversePatches);
}
}
function generateArrayPatches(state, basePath, patches, inversePatches) {
var _a, _b;
var base = state.base,
assigned = state.assigned,
copy = state.copy;
/* istanbul ignore next */
if (!copy) { die(); } // Reduce complexity by ensuring `base` is never longer.
if (copy.length < base.length) {
_a = [copy, base], base = _a[0], copy = _a[1];
_b = [inversePatches, patches], patches = _b[0], inversePatches = _b[1];
}
var delta = copy.length - base.length; // Find the first replaced index.
var start = 0;
while (base[start] === copy[start] && start < base.length) {
++start;
} // Find the last replaced index. Search from the end to optimize splice patches.
var end = base.length;
while (end > start && base[end - 1] === copy[end + delta - 1]) {
--end;
} // Process replaced indices.
for (var i = start; i < end; ++i) {
if (assigned[i] && copy[i] !== base[i]) {
var path = basePath.concat([i]);
patches.push({
op: "replace",
path: path,
value: copy[i]
});
inversePatches.push({
op: "replace",
path: path,
value: base[i]
});
}
}
var replaceCount = patches.length; // Process added indices.
for (var i = end + delta - 1; i >= end; --i) {
var path = basePath.concat([i]);
patches[replaceCount + i - end] = {
op: "add",
path: path,
value: copy[i]
};
inversePatches.push({
op: "remove",
path: path
});
}
} // This is used for both Map objects and normal objects.
function generatePatchesFromAssigned(state, basePath, patches, inversePatches) {
var base = state.base,
copy = state.copy;
each(state.assigned, function (key, assignedValue) {
var origValue = get(base, key);
var value = get(copy, key);
var op = !assignedValue ? "remove" : has(base, key) ? "replace" : "add";
if (origValue === value && op === "replace") { return; }
var path = basePath.concat(key);
patches.push(op === "remove" ? {
op: op,
path: path
} : {
op: op,
path: path,
value: value
});
inversePatches.push(op === "add" ? {
op: "remove",
path: path
} : op === "remove" ? {
op: "add",
path: path,
value: origValue
} : {
op: "replace",
path: path,
value: origValue
});
});
}
function generateSetPatches(state, basePath, patches, inversePatches) {
var base = state.base,
copy = state.copy;
var i = 0;
base.forEach(function (value) {
if (!copy.has(value)) {
var path = basePath.concat([i]);
patches.push({
op: "remove",
path: path,
value: value
});
inversePatches.unshift({
op: "add",
path: path,
value: value
});
}
i++;
});
i = 0;
copy.forEach(function (value) {
if (!base.has(value)) {
var path = basePath.concat([i]);
patches.push({
op: "add",
path: path,
value: value
});
inversePatches.unshift({
op: "remove",
path: path,
value: value
});
}
i++;
});
}
function applyPatches(draft, patches) {
patches.forEach(function (patch) {
var path = patch.path,
op = patch.op;
/* istanbul ignore next */
if (!path.length) { die(); }
var base = draft;
for (var i = 0; i < path.length - 1; i++) {
base = get(base, path[i]);
if (!base || typeof base !== "object") { throw new Error("Cannot apply patch, path doesn't resolve: " + path.join("/")); } // prettier-ignore
}
var type = getArchtype(base);
var value = deepClonePatchValue(patch.value); // used to clone patch to ensure original patch is not modified, see #411
var key = path[path.length - 1];
switch (op) {
case "replace":
switch (type) {
case Archtype.Map:
return base.set(key, value);
/* istanbul ignore next */
case Archtype.Set:
throw new Error('Sets cannot have "replace" patches.');
default:
// if value is an object, then it's assigned by reference
// in the following add or remove ops, the value field inside the patch will also be modifyed
// so we use value from the cloned patch
// @ts-ignore
return base[key] = value;
}
case "add":
switch (type) {
case Archtype.Array:
return base.splice(key, 0, value);
case Archtype.Map:
return base.set(key, value);
case Archtype.Set:
return base.add(value);
default:
return base[key] = value;
}
case "remove":
switch (type) {
case Archtype.Array:
return base.splice(key, 1);
case Archtype.Map:
return base.delete(key);
case Archtype.Set:
return base.delete(patch.value);
default:
return delete base[key];
}
default:
throw new Error("Unsupported patch operation: " + op);
}
});
return draft;
}
function deepClonePatchValue(obj) {
if (!obj || typeof obj !== "object") { return obj; }
if (Array.isArray(obj)) { return obj.map(deepClonePatchValue); }
if (isMap(obj)) { return new Map(Array.from(obj.entries()).map(function (_a) {
var k = _a[0],
v = _a[1];
return [k, deepClonePatchValue(v)];
})); } // Not needed: if (isSet(obj)) return new Set(Array.from(obj.values()).map(deepClone))
var cloned = Object.create(Object.getPrototypeOf(obj));
for (var key in obj) { cloned[key] = deepClonePatchValue(obj[key]); }
return cloned;
}
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
function __spreadArrays() {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
}
/* istanbul ignore next */
function verifyMinified() {}
var configDefaults = {
useProxies: typeof Proxy !== "undefined" && typeof Proxy.revocable !== "undefined" && typeof Reflect !== "undefined",
autoFreeze: typeof process !== "undefined" ? process.env.NODE_ENV !== "production" :
/* istanbul ignore next */
verifyMinified.name === "verifyMinified",
onAssign: null,
onDelete: null,
onCopy: null
};
var Immer =
/** @class */
function () {
function Immer(config) {
var _this = this;
this.useProxies = false;
this.autoFreeze = false;
each(configDefaults, function (key, value) {
var _a, _b; // @ts-ignore
_this[key] = (_b = (_a = config) === null || _a === void 0 ? void 0 : _a[key], _b !== null && _b !== void 0 ? _b : value);
});
this.setUseProxies(this.useProxies);
this.produce = this.produce.bind(this);
this.produceWithPatches = this.produceWithPatches.bind(this);
}
/**
* The `produce` function takes a value and a "recipe function" (whose
* return value often depends on the base state). The recipe function is
* free to mutate its first argument however it wants. All mutations are
* only ever applied to a __copy__ of the base state.
*
* Pass only a function to create a "curried producer" which relieves you
* from passing the recipe function every time.
*
* Only plain objects and arrays are made mutable. All other objects are
* considered uncopyable.
*
* Note: This function is __bound__ to its `Immer` instance.
*
* @param {any} base - the initial state
* @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified
* @param {Function} patchListener - optional function that will be called with all the patches produced here
* @returns {any} a new state, or the initial state if nothing was modified
*/
Immer.prototype.produce = function (base, recipe, patchListener) {
var _this = this; // curried invocation
if (typeof base === "function" && typeof recipe !== "function") {
var defaultBase_1 = recipe;
recipe = base;
var self_1 = this;
return function curriedProduce(base) {
var arguments$1 = arguments;
var _this = this;
if (base === void 0) {
base = defaultBase_1;
}
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments$1[_i];
}
return self_1.produce(base, function (draft) {
return recipe.call.apply(recipe, __spreadArrays([_this, draft], args));
}); // prettier-ignore
};
} // prettier-ignore
{
if (typeof recipe !== "function") {
throw new Error("The first or second argument to `produce` must be a function");
}
if (patchListener !== undefined && typeof patchListener !== "function") {
throw new Error("The third argument to `produce` must be a function or undefined");
}
}
var result; // Only plain objects, arrays, and "immerable classes" are drafted.
if (isDraftable(base)) {
var scope_1 = ImmerScope.enter(this);
var proxy = this.createProxy(base, undefined);
var hasError = true;
try {
result = recipe(proxy);
hasError = false;
} finally {
// finally instead of catch + rethrow better preserves original stack
if (hasError) { scope_1.revoke(); }else { scope_1.leave(); }
}
if (typeof Promise !== "undefined" && result instanceof Promise) {
return result.then(function (result) {
scope_1.usePatches(patchListener);
return processResult(_this, result, scope_1);
}, function (error) {
scope_1.revoke();
throw error;
});
}
scope_1.usePatches(patchListener);
return processResult(this, result, scope_1);
} else {
result = recipe(base);
if (result === NOTHING) { return undefined; }
if (result === undefined) { result = base; }
maybeFreeze(this, result, true);
return result;
}
};
Immer.prototype.produceWithPatches = function (arg1, arg2, arg3) {
var _this = this;
if (typeof arg1 === "function") {
return function (state) {
var arguments$1 = arguments;
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments$1[_i];
}
return _this.produceWithPatches(state, function (draft) {
return arg1.apply(void 0, __spreadArrays([draft], args));
});
};
} // non-curried form
/* istanbul ignore next */
if (arg3) { die(); }
var patches, inversePatches;
var nextState = this.produce(arg1, arg2, function (p, ip) {
patches = p;
inversePatches = ip;
});
return [nextState, patches, inversePatches];
};
Immer.prototype.createDraft = function (base) {
if (!isDraftable(base)) {
throw new Error("First argument to `createDraft` must be a plain object, an array, or an immerable object"); // prettier-ignore
}
var scope = ImmerScope.enter(this);
var proxy = this.createProxy(base, undefined);
proxy[DRAFT_STATE].isManual = true;
scope.leave();
return proxy;
};
Immer.prototype.finishDraft = function (draft, patchListener) {
var state = draft && draft[DRAFT_STATE];
if (!state || !state.isManual) {
throw new Error("First argument to `finishDraft` must be a draft returned by `createDraft`"); // prettier-ignore
}
if (state.finalized) {
throw new Error("The given draft is already finalized"); // prettier-ignore
}
var scope = state.scope;
scope.usePatches(patchListener);
return processResult(this, undefined, scope);
};
/**
* Pass true to automatically freeze all copies created by Immer.
*
* By default, auto-freezing is disabled in production.
*/
Immer.prototype.setAutoFreeze = function (value) {
this.autoFreeze = value;
};
/**
* Pass true to use the ES2015 `Proxy` class when creating drafts, which is
* always faster than using ES5 proxies.
*
* By default, feature detection is used, so calling this is rarely necessary.
*/
Immer.prototype.setUseProxies = function (value) {
this.useProxies = value;
};
Immer.prototype.applyPatches = function (base, patches) {
// If a patch replaces the entire state, take that replacement as base
// before applying patches
var i;
for (i = patches.length - 1; i >= 0; i--) {
var patch = patches[i];
if (patch.path.length === 0 && patch.op === "replace") {
base = patch.value;
break;
}
}
if (isDraft(base)) {
// N.B: never hits if some patch a replacement, patches are never drafts
return applyPatches(base, patches);
} // Otherwise, produce a copy of the base state.
return this.produce(base, function (draft) {
return applyPatches(draft, patches.slice(i + 1));
});
};
Immer.prototype.createProxy = function (value, parent) {
// precondition: createProxy should be guarded by isDraftable, so we know we can safely draft
var draft = isMap(value) ? proxyMap(value, parent) : isSet(value) ? proxySet(value, parent) : this.useProxies ? createProxy(value, parent) : createES5Proxy(value, parent);
var scope = parent ? parent.scope : ImmerScope.current;
scope.drafts.push(draft);
return draft;
};
Immer.prototype.willFinalize = function (scope, thing, isReplaced) {
if (!this.useProxies) { willFinalizeES5(scope, thing, isReplaced); }
};
Immer.prototype.markChanged = function (state) {
if (this.useProxies) {
markChanged(state);
} else {
markChangedES5(state);
}
};
return