You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

892 lines
30 KiB

'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var defineProperty = function (obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
};
var NOTHING = typeof Symbol !== "undefined" ? Symbol("immer-nothing") : defineProperty({}, "immer-nothing", true);
var DRAFT_STATE = typeof Symbol !== "undefined" ? Symbol("immer-state") : "__$immer_state";
function isDraft(value) {
return !!value && !!value[DRAFT_STATE];
}
function isDraftable(value) {
if (!value) return false;
if ((typeof value === "undefined" ? "undefined" : _typeof(value)) !== "object") return false;
if (Array.isArray(value)) return true;
var proto = Object.getPrototypeOf(value);
return proto === null || proto === Object.prototype;
}
function original(value) {
if (value && value[DRAFT_STATE]) {
return value[DRAFT_STATE].base;
}
// otherwise return undefined
}
var assign = Object.assign || function assign(target, value) {
for (var key in value) {
if (has(value, key)) {
target[key] = value[key];
}
}
return target;
};
function shallowCopy(value) {
if (Array.isArray(value)) return value.slice();
var target = value.__proto__ === undefined ? Object.create(null) : {};
return assign(target, value);
}
function each(value, cb) {
if (Array.isArray(value)) {
for (var i = 0; i < value.length; i++) {
cb(i, value[i], value);
}
} else {
for (var key in value) {
cb(key, value[key], value);
}
}
}
function has(thing, prop) {
return Object.prototype.hasOwnProperty.call(thing, prop);
}
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 generatePatches(state, basePath, patches, inversePatches) {
Array.isArray(state.base) ? generateArrayPatches(state, basePath, patches, inversePatches) : generateObjectPatches(state, basePath, patches, inversePatches);
}
function generateArrayPatches(state, basePath, patches, inversePatches) {
var base = state.base,
copy = state.copy,
assigned = state.assigned;
var minLength = Math.min(base.length, copy.length);
// Look for replaced indices.
for (var i = 0; i < minLength; i++) {
if (assigned[i] && base[i] !== copy[i]) {
var path = basePath.concat(i);
patches.push({ op: "replace", path: path, value: copy[i] });
inversePatches.push({ op: "replace", path: path, value: base[i] });
}
}
// Did the array expand?
if (minLength < copy.length) {
for (var _i = minLength; _i < copy.length; _i++) {
patches.push({
op: "add",
path: basePath.concat(_i),
value: copy[_i]
});
}
inversePatches.push({
op: "replace",
path: basePath.concat("length"),
value: base.length
});
}
// ...or did it shrink?
else if (minLength < base.length) {
patches.push({
op: "replace",
path: basePath.concat("length"),
value: copy.length
});
for (var _i2 = minLength; _i2 < base.length; _i2++) {
inversePatches.push({
op: "add",
path: basePath.concat(_i2),
value: base[_i2]
});
}
}
}
function generateObjectPatches(state, basePath, patches, inversePatches) {
var base = state.base,
copy = state.copy;
each(state.assigned, function (key, assignedValue) {
var origValue = base[key];
var value = copy[key];
var op = !assignedValue ? "remove" : key in base ? "replace" : "add";
if (origValue === base && 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 applyPatches(draft, patches) {
for (var i = 0; i < patches.length; i++) {
var patch = patches[i];
var path = patch.path;
if (path.length === 0 && patch.op === "replace") {
draft = patch.value;
} else {
var base = draft;
for (var _i3 = 0; _i3 < path.length - 1; _i3++) {
base = base[path[_i3]];
if (!base || (typeof base === "undefined" ? "undefined" : _typeof(base)) !== "object") throw new Error("Cannot apply patch, path doesn't resolve: " + path.join("/")); // prettier-ignore
}
var key = path[path.length - 1];
switch (patch.op) {
case "replace":
case "add":
// TODO: add support is not extensive, it does not support insertion or `-` atm!
base[key] = patch.value;
break;
case "remove":
if (Array.isArray(base)) {
if (key !== base.length - 1) throw new Error("Only the last index of an array can be removed, index: " + key + ", length: " + base.length); // prettier-ignore
base.length -= 1;
} else {
delete base[key];
}
break;
default:
throw new Error("Unsupported patch operation: " + patch.op);
}
}
}
return draft;
}
// @ts-check
var descriptors = {};
// For nested produce calls:
var scopes = [];
var currentScope = function currentScope() {
return scopes[scopes.length - 1];
};
function willFinalize(result, baseDraft, needPatches) {
var scope = currentScope();
scope.forEach(function (state) {
return state.finalizing = true;
});
if (result === undefined || result === baseDraft) {
if (needPatches) markChangesRecursively(baseDraft);
// This is faster when we don't care about which attributes changed.
markChangesSweep(scope);
}
}
function createDraft(base, parent) {
var draft = void 0;
if (isDraft(base)) {
var _state = base[DRAFT_STATE];
// Avoid creating new drafts when copying.
_state.finalizing = true;
draft = shallowCopy(_state.draft);
_state.finalizing = false;
} else {
draft = shallowCopy(base);
}
each(base, function (prop) {
Object.defineProperty(draft, "" + prop, createPropertyProxy("" + prop));
});
// See "proxy.js" for property documentation.
var state = {
scope: parent ? parent.scope : currentScope(),
modified: false,
finalizing: false, // es5 only
finalized: false,
assigned: {},
parent: parent,
base: base,
draft: draft,
copy: null,
revoke: revoke,
revoked: false // es5 only
};
createHiddenProperty(draft, DRAFT_STATE, state);
state.scope.push(state);
return draft;
}
function revoke() {
this.revoked = true;
}
function source(state) {
return state.copy || state.base;
}
function _get(state, prop) {
assertUnrevoked(state);
var value = source(state)[prop];
// Drafts are only created for proxyable values that exist in the base state.
if (!state.finalizing && value === state.base[prop] && isDraftable(value)) {
prepareCopy(state);
return state.copy[prop] = createDraft(value, state);
}
return value;
}
function _set(state, prop, value) {
assertUnrevoked(state);
state.assigned[prop] = true;
if (!state.modified) {
if (is(source(state)[prop], value)) return;
markChanged(state);
prepareCopy(state);
}
state.copy[prop] = value;
}
function markChanged(state) {
if (!state.modified) {
state.modified = true;
if (state.parent) markChanged(state.parent);
}
}
function prepareCopy(state) {
if (!state.copy) state.copy = shallowCopy(state.base);
}
function createPropertyProxy(prop) {
return descriptors[prop] || (descriptors[prop] = {
configurable: true,
enumerable: true,
get: function get$$1() {
return _get(this[DRAFT_STATE], prop);
},
set: function set$$1(value) {
_set(this[DRAFT_STATE], prop, value);
}
});
}
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(state.copy || state.base));
}
// This looks expensive, but only proxies are visited, and only objects without known changes are scanned.
function markChangesSweep(scope) {
// 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 = scope.length - 1; i >= 0; i--) {
var state = scope[i];
if (state.modified === false) {
if (Array.isArray(state.base)) {
if (hasArrayChanges(state)) markChanged(state);
} else if (hasObjectChanges(state)) markChanged(state);
}
}
}
function markChangesRecursively(object) {
if (!object || (typeof object === "undefined" ? "undefined" : _typeof(object)) !== "object") return;
var state = object[DRAFT_STATE];
if (!state) return;
var base = state.base,
draft = state.draft,
assigned = state.assigned;
if (!Array.isArray(object)) {
// Look for added keys.
Object.keys(draft).forEach(function (key) {
// The `undefined` check is a fast path for pre-existing keys.
if (base[key] === undefined && !has(base, key)) {
assigned[key] = true;
markChanged(state);
} else if (!assigned[key]) {
// Only untouched properties trigger recursion.
markChangesRecursively(draft[key]);
}
});
// Look for removed keys.
Object.keys(base).forEach(function (key) {
// The `undefined` check is a fast path for pre-existing keys.
if (draft[key] === undefined && !has(draft, key)) {
assigned[key] = false;
markChanged(state);
}
});
} else if (hasArrayChanges(state)) {
markChanged(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 _i2 = 0; _i2 < draft.length; _i2++) {
// Only untouched indices trigger recursion.
if (assigned[_i2] === undefined) markChangesRecursively(draft[_i2]);
}
}
}
function hasObjectChanges(state) {
var base = state.base,
draft = state.draft;
// Search for added 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--) {
// The `undefined` check is a fast path for pre-existing keys.
if (base[keys[i]] === undefined && !has(base, keys[i])) {
return true;
}
}
// Since no keys have been added, we can compare lengths to know if an
// object has been 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;
}
function createHiddenProperty(target, prop, value) {
Object.defineProperty(target, prop, {
value: value,
enumerable: false,
writable: true
});
}
var legacyProxy = Object.freeze({
scopes: scopes,
currentScope: currentScope,
willFinalize: willFinalize,
createDraft: createDraft
});
// @ts-check
// For nested produce calls:
var scopes$1 = [];
var currentScope$1 = function currentScope() {
return scopes$1[scopes$1.length - 1];
};
// Do nothing before being finalized.
function willFinalize$1() {}
function createDraft$1(base, parent) {
var state = {
// Track which produce call this is associated with.
scope: parent ? parent.scope : currentScope$1(),
// 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
};
var _ref = Array.isArray(base) ? Proxy.revocable([state], arrayTraps) : Proxy.revocable(state, objectTraps),
revoke = _ref.revoke,
proxy = _ref.proxy;
state.draft = proxy;
state.revoke = revoke;
state.scope.push(state);
return proxy;
}
var objectTraps = {
get: get$1,
has: function has$$1(target, prop) {
return prop in source$1(target);
},
ownKeys: function ownKeys(target) {
return Reflect.ownKeys(source$1(target));
},
set: set$1,
deleteProperty: deleteProperty,
getOwnPropertyDescriptor: getOwnPropertyDescriptor,
defineProperty: defineProperty$1,
setPrototypeOf: function setPrototypeOf() {
throw new Error("Immer does not support `setPrototypeOf()`.");
}
};
var arrayTraps = {};
each(objectTraps, function (key, fn) {
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 does not support deleting properties from arrays: " + prop);
return objectTraps.deleteProperty.call(this, state[0], prop);
};
arrayTraps.set = function (state, prop, value) {
if (prop !== "length" && isNaN(parseInt(prop))) throw new Error("Immer does not support setting non-numeric properties on arrays: " + prop);
return objectTraps.set.call(this, state[0], prop, value);
};
function source$1(state) {
return state.copy || state.base;
}
function get$1(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 = source$1(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 !== state.base[prop]) return value;
// Store drafts on the copy (when one exists).
drafts = state.copy;
}
return drafts[prop] = createDraft$1(value, state);
}
function set$1(state, prop, value) {
if (!state.modified) {
// 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(state.base[prop], value) || value === state.drafts[prop] : is(state.base[prop], value) && prop in state.base;
if (isUnchanged) return true;
markChanged$1(state);
}
state.assigned[prop] = true;
state.copy[prop] = value;
return true;
}
function deleteProperty(state, prop) {
// The `undefined` check is a fast path for pre-existing keys.
if (state.base[prop] !== undefined || prop in state.base) {
state.assigned[prop] = false;
markChanged$1(state);
}
if (state.copy) delete state.copy[prop];
return true;
}
function getOwnPropertyDescriptor(state, prop) {
var owner = state.modified ? state.copy : has(state.drafts, prop) ? state.drafts : state.base;
var descriptor = Reflect.getOwnPropertyDescriptor(owner, prop);
if (descriptor && !(Array.isArray(owner) && prop === "length")) descriptor.configurable = true;
return descriptor;
}
function defineProperty$1() {
throw new Error("Immer does not support defining properties on draft objects.");
}
function markChanged$1(state) {
if (!state.modified) {
state.modified = true;
state.copy = assign(shallowCopy(state.base), state.drafts);
state.drafts = null;
if (state.parent) markChanged$1(state.parent);
}
}
var modernProxy = Object.freeze({
scopes: scopes$1,
currentScope: currentScope$1,
willFinalize: willFinalize$1,
createDraft: createDraft$1
});
function verifyMinified() {}
var configDefaults = {
useProxies: typeof Proxy !== "undefined" && typeof Reflect !== "undefined",
autoFreeze: typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : verifyMinified.name === "verifyMinified",
onAssign: null,
onDelete: null,
onCopy: null
};
var Immer = function () {
function Immer(config) {
classCallCheck(this, Immer);
assign(this, configDefaults, config);
this.setUseProxies(this.useProxies);
this.produce = this.produce.bind(this);
}
createClass(Immer, [{
key: "produce",
value: function produce(base, recipe, patchListener) {
var _this = this;
// curried invocation
if (typeof base === "function" && typeof recipe !== "function") {
var defaultBase = recipe;
recipe = base;
// prettier-ignore
return function () {
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
var base = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultBase;
return _this.produce(base, function (draft) {
var _recipe;
return (_recipe = recipe).call.apply(_recipe, [draft, draft].concat(args));
});
};
}
// prettier-ignore
{
if (typeof recipe !== "function") throw new Error("if first argument is not a function, the second argument to produce should be a function");
if (patchListener !== undefined && typeof patchListener !== "function") throw new Error("the third argument of a producer should not be set or a function");
}
var result = void 0;
// Only create proxies for plain objects/arrays.
if (!isDraftable(base)) {
result = recipe(base);
if (result === undefined) return base;
}
// See #100, don't nest producers
else if (isDraft(base)) {
result = recipe.call(base, base);
if (result === undefined) return base;
}
// The given value must be proxied.
else {
this.scopes.push([]);
var baseDraft = this.createDraft(base);
try {
result = recipe.call(baseDraft, baseDraft);
this.willFinalize(result, baseDraft, !!patchListener);
// Never generate patches when no listener exists.
var patches = patchListener && [],
inversePatches = patchListener && [];
// Finalize the modified draft...
if (result === undefined || result === baseDraft) {
result = this.finalize(baseDraft, [], patches, inversePatches);
}
// ...or use a replacement value.
else {
// Users must never modify the draft _and_ return something else.
if (baseDraft[DRAFT_STATE].modified) 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
// Finalize the replacement in case it contains (or is) a subset of the draft.
if (isDraftable(result)) result = this.finalize(result);
if (patchListener) {
patches.push({
op: "replace",
path: [],
value: result
});
inversePatches.push({
op: "replace",
path: [],
value: base
});
}
}
} finally {
this.currentScope().forEach(function (state) {
return state.revoke();
});
this.scopes.pop();
}
patchListener && patchListener(patches, inversePatches);
}
// Normalize the result.
return result === NOTHING ? undefined : result;
}
}, {
key: "setAutoFreeze",
value: function setAutoFreeze(value) {
this.autoFreeze = value;
}
}, {
key: "setUseProxies",
value: function setUseProxies(value) {
this.useProxies = value;
assign(this, value ? modernProxy : legacyProxy);
}
/**
* @internal
* Finalize a draft, returning either the unmodified base state or a modified
* copy of the base state.
*/
}, {
key: "finalize",
value: function finalize(draft, path, patches, inversePatches) {
var state = draft[DRAFT_STATE];
if (!state) {
if (Object.isFrozen(draft)) return draft;
return this.finalizeTree(draft);
}
// Never finalize drafts owned by an outer scope.
if (state.scope !== this.currentScope()) {
return draft;
}
if (!state.modified) return state.base;
if (!state.finalized) {
state.finalized = true;
this.finalizeTree(state.draft, path, patches, inversePatches);
if (this.onDelete) {
var assigned = state.assigned;
for (var prop in assigned) {
assigned[prop] || this.onDelete(state, prop);
}
}
if (this.onCopy) this.onCopy(state);
// Nested producers must never auto-freeze their result,
// because it may contain drafts from parent producers.
if (this.autoFreeze && this.scopes.length === 1) {
Object.freeze(state.copy);
}
if (patches) generatePatches(state, path, patches, inversePatches);
}
return state.copy;
}
/**
* @internal
* Finalize all drafts in the given state tree.
*/
}, {
key: "finalizeTree",
value: function finalizeTree(root, path, patches, inversePatches) {
var _this2 = this;
var state = root[DRAFT_STATE];
if (state) {
root = this.useProxies ? state.copy : state.copy = shallowCopy(state.draft);
}
var onAssign = this.onAssign;
var finalizeProperty = function finalizeProperty(prop, value, parent) {
// Only `root` can be a draft in here.
var inDraft = !!state && parent === root;
if (isDraft(value)) {
// prettier-ignore
parent[prop] = value =
// Patches are never generated for assigned properties.
patches && inDraft && !state.assigned[prop] ? _this2.finalize(value, path.concat(prop), patches, inversePatches) : _this2.finalize(value);
// Unchanged drafts are ignored.
if (inDraft && value === state.base[prop]) return;
}
// Unchanged draft properties are ignored.
else if (inDraft && is(value, state.base[prop])) {
return;
}
// Search new objects for unfinalized drafts. Frozen objects should never contain drafts.
else if (isDraftable(value) && !Object.isFrozen(value)) {
each(value, finalizeProperty);
}
if (inDraft && onAssign) {
onAssign(state, prop, value);
}
};
each(root, finalizeProperty);
return root;
}
}]);
return Immer;
}();
var immer = new Immer();
/**
* 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
*/
var produce = immer.produce;
/**
* Pass true to automatically freeze all copies created by Immer.
*
* By default, auto-freezing is disabled in production.
*/
var setAutoFreeze = function setAutoFreeze(value) {
return immer.setAutoFreeze(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.
*/
var setUseProxies = function setUseProxies(value) {
return immer.setUseProxies(value);
};
/**
* Apply an array of Immer patches to the first argument.
*
* This function is a producer, which means copy-on-write is in effect.
*/
var applyPatches$1 = produce(applyPatches);
exports.produce = produce;
exports['default'] = produce;
exports.setAutoFreeze = setAutoFreeze;
exports.setUseProxies = setUseProxies;
exports.applyPatches = applyPatches$1;
exports.Immer = Immer;
exports.original = original;
exports.isDraft = isDraft;
exports.nothing = NOTHING;
//# sourceMappingURL=immer.js.map