UNPKG

@freeboardgame.org/boardgame.io

Version:
1,803 lines (1,489 loc) 118 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function (obj) { return typeof obj; }; } else { _typeof = function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _defineProperty(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; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } } function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } var Flatted = (function (Primitive, primitive) { /*! * ISC License * * Copyright (c) 2018, Andrea Giammarchi, @WebReflection * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ var Flatted = { parse: function parse(text) { var input = JSON.parse(text, Primitives).map(primitives); var value = input[0]; return typeof value === 'object' && value ? revive(input, new Set, value) : value; }, stringify: function stringify(value) { for (var firstRun, known = new Map, input = [], output = [], i = +set(known, input, value), replace = function (key, value) { if (firstRun) return (firstRun = !firstRun), value; switch (typeof value) { case 'object': if (value === null) return value; case primitive: return known.get(value) || set(known, input, value); } return value; }; i < input.length; i++ ) { firstRun = true; output[i] = JSON.stringify(input[i], replace); } return '[' + output.join(',') + ']'; } }; return Flatted; function revive(input, parsed, output) { return Object.keys(output).reduce( function (output, key) { var value = output[key]; if (value instanceof Primitive) { var tmp = input[value]; if (typeof tmp === 'object' && !parsed.has(tmp)) { parsed.add(tmp); output[key] = revive(input, parsed, tmp); } else { output[key] = tmp; } } return output; }, output ); } function set(known, input, value) { var index = Primitive(input.push(value) - 1); known.set(value, index); return index; } function primitives(value) { return value instanceof Primitive ? Primitive(value) : value; } function Primitives(key, value) { return typeof value === primitive ? new Primitive(value) : value; } }(String, 'string')); const parse = Flatted.parse; const stringify = Flatted.stringify; /* * Copyright 2017 The boardgame.io Authors * * Use of this source code is governed by a MIT-style * license that can be found in the LICENSE file or at * https://opensource.org/licenses/MIT. */ var MAKE_MOVE = 'MAKE_MOVE'; var GAME_EVENT = 'GAME_EVENT'; var REDO = 'REDO'; var RESET = 'RESET'; var SYNC = 'SYNC'; var UNDO = 'UNDO'; var UPDATE = 'UPDATE'; // Inlined version of Alea from https://github.com/davidbau/seedrandom. /* * Copyright 2015 David Bau. * * Permission is hereby granted, free of charge, * to any person obtaining a copy of this software * and associated documentation files (the "Software"), * to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the * Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall * be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ function Alea(seed) { var me = this, mash = Mash(); me.next = function () { var t = 2091639 * me.s0 + me.c * 2.3283064365386963e-10; // 2^-32 me.s0 = me.s1; me.s1 = me.s2; return me.s2 = t - (me.c = t | 0); }; // Apply the seeding algorithm from Baagoe. me.c = 1; me.s0 = mash(' '); me.s1 = mash(' '); me.s2 = mash(' '); me.s0 -= mash(seed); if (me.s0 < 0) { me.s0 += 1; } me.s1 -= mash(seed); if (me.s1 < 0) { me.s1 += 1; } me.s2 -= mash(seed); if (me.s2 < 0) { me.s2 += 1; } mash = null; } function copy(f, t) { t.c = f.c; t.s0 = f.s0; t.s1 = f.s1; t.s2 = f.s2; return t; } function Mash() { var n = 0xefc8249d; var mash = function mash(data) { data = data.toString(); for (var i = 0; i < data.length; i++) { n += data.charCodeAt(i); var h = 0.02519603282416938 * n; n = h >>> 0; h -= n; h *= n; n = h >>> 0; h -= n; n += h * 0x100000000; // 2^32 } return (n >>> 0) * 2.3283064365386963e-10; // 2^-32 }; return mash; } function alea(seed, opts) { var xg = new Alea(seed), state = opts && opts.state, prng = xg.next; prng.quick = prng; if (state) { if (_typeof(state) == 'object') copy(state, xg); prng.state = function () { return copy(xg, {}); }; } return prng; } /** * Random * * Calls that require a pseudorandom number generator. * Uses a seed from ctx, and also persists the PRNG * state in ctx so that moves can stay pure. */ var Random = /*#__PURE__*/ function () { /** * constructor * @param {object} ctx - The ctx object to initialize from. */ function Random(ctx) { _classCallCheck(this, Random); // If we are on the client, the seed is not present. // Just use a temporary seed to execute the move without // crashing it. The move state itself is discarded, // so the actual value doesn't matter. this.state = ctx._random || { seed: '0' }; } /** * Updates ctx with the PRNG state. * @param {object} ctx - The ctx object to update. */ _createClass(Random, [{ key: "update", value: function update(state) { var ctx = _objectSpread({}, state.ctx, { _random: this.state }); return _objectSpread({}, state, { ctx: ctx }); } /** * Attaches the Random API to ctx. * @param {object} ctx - The ctx object to attach to. */ }, { key: "attach", value: function attach(ctx) { return _objectSpread({}, ctx, { random: this._api() }); } /** * Generate a random number. */ }, { key: "_random", value: function _random() { var R = this.state; var fn; if (R.prngstate === undefined) { // No call to a random function has been made. fn = new alea(R.seed, { state: true }); } else { fn = new alea('', { state: R.prngstate }); } var number = fn(); this.state = _objectSpread({}, R, { prngstate: fn.state() }); return number; } }, { key: "_api", value: function _api() { var random = this._random.bind(this); var SpotValue = { D4: 4, D6: 6, D8: 8, D10: 10, D12: 12, D20: 20 }; // Generate functions for predefined dice values D4 - D20. var predefined = {}; var _loop = function _loop(key) { var spotvalue = SpotValue[key]; predefined[key] = function (diceCount) { if (diceCount === undefined) { return Math.floor(random() * spotvalue) + 1; } else { return _toConsumableArray(new Array(diceCount).keys()).map(function () { return Math.floor(random() * spotvalue) + 1; }); } }; }; for (var key in SpotValue) { _loop(key); } return _objectSpread({}, predefined, { /** * Roll a die of specified spot value. * * @param {number} spotvalue - The die dimension (default: 6). * @param {number} diceCount - number of dice to throw. * if not defined, defaults to 1 and returns the value directly. * if defined, returns an array containing the random dice values. */ Die: function Die(spotvalue, diceCount) { if (spotvalue === undefined) { spotvalue = 6; } if (diceCount === undefined) { return Math.floor(random() * spotvalue) + 1; } else { return _toConsumableArray(new Array(diceCount).keys()).map(function () { return Math.floor(random() * spotvalue) + 1; }); } }, /** * Generate a random number between 0 and 1. */ Number: function Number() { return random(); }, /** * Shuffle an array. * * @param {Array} deck - The array to shuffle. Does not mutate * the input, but returns the shuffled array. */ Shuffle: function Shuffle(deck) { var clone = deck.slice(0); var srcIndex = deck.length; var dstIndex = 0; var shuffled = new Array(srcIndex); while (srcIndex) { var randIndex = srcIndex * random() | 0; shuffled[dstIndex++] = clone[randIndex]; clone[randIndex] = clone[--srcIndex]; } return shuffled; } }); } }]); return Random; }(); /** * Removes the attached Random api from ctx. * * @param {object} ctx - The ctx object with the Random API attached. * @returns {object} A plain ctx object without the Random API. */ Random.detach = function (ctx) { var random = ctx.random, rest = _objectWithoutProperties(ctx, ["random"]); // eslint-disable-line no-unused-vars return rest; }; /** * Generates a new seed from the current date / time. */ Random.seed = function () { return (+new Date()).toString(36).slice(-10); }; /* * Copyright 2017 The boardgame.io Authors * * Use of this source code is governed by a MIT-style * license that can be found in the LICENSE file or at * https://opensource.org/licenses/MIT. */ /** * Generate an automatic game event that is a side-effect of a move. * @param {string} type - The event type. * @param {Array} args - Additional arguments. * @param {string} playerID - The ID of the player making this action. * @param {string} credentials - (optional) The credentials for the player making this action. */ var automaticGameEvent = function automaticGameEvent(type, args, playerID, credentials) { return { type: GAME_EVENT, payload: { type: type, args: args, playerID: playerID, credentials: credentials }, automatic: true }; }; /** * Events */ var Events = /*#__PURE__*/ function () { function Events(flow, playerID) { _classCallCheck(this, Events); this.flow = flow; this.playerID = playerID; this.dispatch = []; } /** * Attaches the Events API to ctx. * @param {object} ctx - The ctx object to attach to. */ _createClass(Events, [{ key: "attach", value: function attach(ctx) { var _this = this; var events = {}; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { var _loop = function _loop() { var key = _step.value; events[key] = function () { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this.dispatch.push({ key: key, args: args }); }; }; for (var _iterator = this.flow.eventNames[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { _loop(); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } return _objectSpread({}, ctx, { events: events }); } /** * Updates ctx with the triggered events. * @param {object} state - The state object { G, ctx }. */ }, { key: "update", value: function update$$1(state) { var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = this.dispatch[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var item = _step2.value; var action = automaticGameEvent(item.key, item.args, this.playerID); state = _objectSpread({}, state, this.flow.processGameEvent(state, action)); } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return != null) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } return state; } }]); return Events; }(); /** * Detaches the Events API from ctx. * @param {object} ctx - The ctx object to strip. */ Events.detach = function (ctx) { var events = ctx.events, rest = _objectWithoutProperties(ctx, ["events"]); // eslint-disable-line no-unused-vars return rest; }; var obj; var NOTHING = typeof Symbol !== "undefined" ? Symbol("immer-nothing") : ( obj = {}, obj["immer-nothing"] = true, obj ); var DRAFTABLE = typeof Symbol !== "undefined" && Symbol.for ? Symbol.for("immer-draftable") : "__$immer_draftable"; var DRAFT_STATE = typeof Symbol !== "undefined" && Symbol.for ? Symbol.for("immer-state") : "__$immer_state"; function isDraft(value) { return !!value && !!value[DRAFT_STATE]; } function isDraftable(value) { if (!value || typeof value !== "object") { return false; } if (Array.isArray(value)) { return true; } var proto = Object.getPrototypeOf(value); if (!proto || proto === Object.prototype) { return true; } return !!value[DRAFTABLE] || !!value.constructor[DRAFTABLE]; } var assign = Object.assign || function assign(target, value) { for (var key in value) { if (has(value, key)) { target[key] = value[key]; } } return target; }; var ownKeys = typeof Reflect !== "undefined" && Reflect.ownKeys ? Reflect.ownKeys : typeof Object.getOwnPropertySymbols !== "undefined" ? function (obj) { return Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj)); } : Object.getOwnPropertyNames; 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 each(value, cb) { if (Array.isArray(value)) { for (var i = 0; i < value.length; i++) { cb(i, value[i], value); } } else { ownKeys(value).forEach(function (key) { return cb(key, value[key], value); }); } } function isEnumerable(base, prop) { var desc = Object.getOwnPropertyDescriptor(base, prop); return !!desc && desc.enumerable; } 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; } } /** Each scope represents a `produce` call. */ var ImmerScope = function ImmerScope(parent) { this.drafts = []; this.parent = parent; // 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; // To avoid prototype lookups: this.patches = null; }; ImmerScope.prototype.usePatches = function usePatches (patchListener) { if (patchListener) { this.patches = []; this.inversePatches = []; this.patchListener = patchListener; } }; ImmerScope.prototype.revoke = function revoke$1 () { this.leave(); this.drafts.forEach(revoke); this.drafts = null; // Make draft-related methods throw. }; ImmerScope.prototype.leave = function leave () { if (this === ImmerScope.current) { ImmerScope.current = this.parent; } }; ImmerScope.current = null; ImmerScope.enter = function () { return this.current = new ImmerScope(this.current); }; function revoke(draft) { draft[DRAFT_STATE].revoke(); } // but share them all instead var descriptors = {}; function willFinalize(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 createProxy(base, parent) { var isArray = Array.isArray(base); var draft = clonePotentialDraft(base); each(draft, function (prop) { proxyProperty(draft, prop, isArray || isEnumerable(base, prop)); }); // See "proxy.js" for property documentation. var scope = parent ? parent.scope : ImmerScope.current; var state = { scope: scope, modified: false, finalizing: false, // es5 only finalized: false, assigned: {}, parent: parent, base: base, draft: draft, copy: null, revoke: revoke$1, revoked: false // es5 only }; createHiddenProperty(draft, DRAFT_STATE, state); scope.drafts.push(draft); return draft; } function revoke$1() { this.revoked = true; } function source(state) { return state.copy || state.base; } // Access a property without creating an Immer draft. function peek(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(state, prop) { assertUnrevoked(state); var value = peek(source(state), prop); if (state.finalizing) { return value; } // Create a draft if the value is unmodified. if (value === peek(state.base, prop) && isDraftable(value)) { prepareCopy(state); return state.copy[prop] = createProxy(value, state); } return value; } function set$1(state, prop, value) { assertUnrevoked(state); state.assigned[prop] = true; if (!state.modified) { if (is(value, peek(source(state), prop))) { 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 = 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); } function proxyProperty(draft, prop, enumerable) { var desc = descriptors[prop]; if (desc) { desc.enumerable = enumerable; } else { descriptors[prop] = desc = { configurable: true, enumerable: enumerable, get: function get$1() { return get(this[DRAFT_STATE], prop); }, set: function set$1(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(source(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) { if (Array.isArray(state.base)) { if (hasArrayChanges(state)) { markChanged(state); } } else if (hasObjectChanges(state)) { markChanged(state); } } } } function markChangesRecursively(object) { if (!object || typeof object !== "object") { return; } var state = object[DRAFT_STATE]; if (!state) { return; } var base = state.base; var draft = state.draft; var 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$1 = base.length; i$1 < draft.length; i$1++) { assigned[i$1] = true; } } for (var i$2 = 0; i$2 < draft.length; i$2++) { // Only untouched indices trigger recursion. if (assigned[i$2] === undefined) { markChangesRecursively(draft[i$2]); } } } } function hasObjectChanges(state) { var base = state.base; var 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; } function createHiddenProperty(target, prop, value) { Object.defineProperty(target, prop, { value: value, enumerable: false, writable: true }); } var legacyProxy = /*#__PURE__*/Object.freeze({ willFinalize: willFinalize, createProxy: createProxy }); function willFinalize$1() {} function createProxy$1(base, parent) { var scope = parent ? parent.scope : ImmerScope.current; var state = { // Track which produce call this is associated with. scope: scope, // 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) ? // [state] is used for arrays, to make sure the proxy is array-ish and not violate invariants, // although state itself is an object Proxy.revocable([state], arrayTraps) : Proxy.revocable(state, objectTraps); var revoke = ref.revoke; var proxy = ref.proxy; state.draft = proxy; state.revoke = revoke; scope.drafts.push(proxy); return proxy; } var objectTraps = { get: get$1, has: function has(target, prop) { return prop in source$1(target); }, ownKeys: function ownKeys(target) { return Reflect.ownKeys(source$1(target)); }, set: set$1$1, deleteProperty: deleteProperty, getOwnPropertyDescriptor: getOwnPropertyDescriptor, defineProperty: function defineProperty() { throw new Error("Object.defineProperty() cannot be used on an Immer draft"); // prettier-ignore }, getPrototypeOf: function getPrototypeOf(target) { return Object.getPrototypeOf(target.base); }, setPrototypeOf: function setPrototypeOf() { throw new Error("Object.setPrototypeOf() cannot be used on an Immer draft"); // prettier-ignore } }; 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 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); }; // returns the object we should be reading the current value from, which is base, until some change has been made function source$1(state) { return state.copy || state.base; } // Access a property without creating an Immer draft. function peek$1(draft, prop) { var state = draft[DRAFT_STATE]; var desc = Reflect.getOwnPropertyDescriptor(state ? source$1(state) : draft, prop); return desc && desc.value; } 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 !== peek$1(state.base, prop)) { return value; } // Store drafts on the copy (when one exists). drafts = state.copy; } return drafts[prop] = createProxy$1(value, state); } function set$1$1(state, prop, value) { if (!state.modified) { var baseValue = peek$1(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; } 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 (peek$1(state.base, prop) !== undefined || prop in state.base) { state.assigned[prop] = false; markChanged$1(state); } 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. function getOwnPropertyDescriptor(state, prop) { var owner = source$1(state); var desc = Reflect.getOwnPropertyDescriptor(owner, prop); if (desc) { desc.writable = true; desc.configurable = !Array.isArray(owner) || prop !== "length"; } return desc; } 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 = /*#__PURE__*/Object.freeze({ willFinalize: willFinalize$1, createProxy: createProxy$1 }); 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 assign, assign$1; var base = state.base; var copy = state.copy; var assigned = state.assigned; // Reduce complexity by ensuring `base` is never longer. if (copy.length < base.length) { (assign = [copy, base], base = assign[0], copy = assign[1]); (assign$1 = [inversePatches, patches], patches = assign$1[0], inversePatches = assign$1[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 useRemove = end != base.length; var replaceCount = patches.length; // Process added indices. for (var i$1 = end + delta - 1; i$1 >= end; --i$1) { var path$1 = basePath.concat([i$1]); patches[replaceCount + i$1 - end] = { op: "add", path: path$1, value: copy[i$1] }; if (useRemove) { inversePatches.push({ op: "remove", path: path$1 }); } } // One "replace" patch reverses all non-splicing "add" patches. if (!useRemove) { inversePatches.push({ op: "replace", path: basePath.concat(["length"]), value: base.length }); } } function generateObjectPatches(state, basePath, patches, inversePatches) { var base = state.base; var 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 === 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 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 i$1 = 0; i$1 < path.length - 1; i$1++) { base = base[path[i$1]]; if (!base || 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": base[key] = patch.value; break; case "add": if (Array.isArray(base)) { // TODO: support "foo/-" paths for appending to an array base.splice(key, 0, patch.value); } else { base[key] = patch.value; } break; case "remove": if (Array.isArray(base)) { base.splice(key, 1); } else { delete base[key]; } break; default: throw new Error("Unsupported patch operation: " + patch.op); } } } return draft; } 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 Immer(config) { assign(this, configDefaults, config); this.setUseProxies(this.useProxies); this.produce = this.produce.bind(this); }; Immer.prototype.produce = function produce (base, recipe, patchListener) { var this$1 = this; // curried invocation if (typeof base === "function" && typeof recipe !== "function") { var defaultBase = recipe; recipe = base; var self = this; return function curriedProduce(base) { var this$1 = this; if ( base === void 0 ) base = defaultBase; var args = [], len = arguments.length - 1; while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; return self.produce(base, function (draft) { return recipe.call.apply(recipe, [ this$1, draft ].concat( 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 = ImmerScope.enter(); var proxy = this.createProxy(base); var hasError = true; try { result = recipe(proxy); hasError = false; } finally { // finally instead of catch + rethrow better preserves original stack if (hasError) { scope.revoke(); }else { scope.leave(); } } if (result instanceof Promise) { return result.then(function (result) { scope.usePatches(patchListener); return this$1.processResult(result, scope); }, function (error) { scope.revoke(); throw error; }); } scope.usePatches(patchListener); return this.processResult(result, scope); } else { result = recipe(base); if (result === undefined) { return base; } return result !== NOTHING ? result : undefined; } }; Immer.prototype.createDraft = function createDraft (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(); var proxy = this.createProxy(base); proxy[DRAFT_STATE].isManual = true; scope.leave(); return proxy; }; Immer.prototype.finishDraft = function finishDraft (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 this.processResult(undefined, scope); }; Immer.prototype.setAutoFreeze = function setAutoFreeze (value) { this.autoFreeze = value; }; Immer.prototype.setUseProxies = function setUseProxies (value) { this.useProxies = value; assign(this, value ? modernProxy : legacyProxy); }; Immer.prototype.applyPatches = function applyPatches$1 (base, patches) { // Mutate the base state when a draft is passed. if (isDraft(base)) { return applyPatches(base, patches); } // Otherwise, produce a copy of the base state. return this.produce(base, function (draft) { return applyPatches(draft, patches); }); }; /** @internal */ Immer.prototype.processResult = function processResult (result, scope) { var baseDraft = scope.drafts[0]; var isReplaced = result !== undefined && result !== baseDraft; this.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 = this.finalize(result, null, scope); } 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 = this.finalize(baseDraft, [], scope); } scope.revoke(); if (scope.patches) { scope.patchListener(scope.patches, scope.inversePatches); } return result !== NOTHING ? result : undefined; }; /** * @internal * Finalize a draft, returning either the unmodified base state or a modified * copy of the base state. */ Immer.prototype.finalize = function finalize (draft, path, scope) { var this$1 = this; var state = draft[DRAFT_STATE]; if (!state) { if (Object.isFrozen(draft)) { return draft; } return this.finalizeTree(draft, null, scope); } // Never finalize drafts owned by another scope. if (state.scope !== scope) { return draft; } if (!state.modified) { return state.base; } if (!state.finalized) { state.finalized = true; this.finalizeTree(state.draft, path, scope); if (this.onDelete) { // The `assigned` object is unreliable with ES5 drafts. if (this.useProxies) { var assigned = state.assigned; for (var prop in assigned) { if (!assigned[prop]) { this.onDelete(state, prop); } } } else { var base = state.base; var copy = state.copy; each(base, function (prop) { if (!has(copy, prop)) { this$1.onDelete(state, prop); } }); } } if (this.onCopy) { this.onCopy(state); } // At this point, all descendants of `state.copy` have been finalized, // so we can be sure that `scope.canAutoFreeze` is accurate. if (this.autoFreeze && scope.canAutoFreeze) { Object.freeze(state.copy); } if (path && scope.patches) { generatePatches(state, path, scope.patches, scope.inversePatches); } } return state.copy; }; /** * @internal * Finalize all drafts in the given state tree. */ Immer.prototype.finalizeTree = function finalizeTree (root, rootPath, scope) { var this$1 = this; var state = root[DRAFT_STATE]; if (state) { if (!this.useProxies) { // Create the final copy, with added keys and without deleted keys. state.copy = shallowCopy(state.draft, true); } root = state.copy; } var needPatches = !!rootPath && !!scope.patches; var finalizeProperty = function (prop, value, parent) { if (value === parent) { throw Error("Immer forbids circular references"); } // In the `finalizeTree` method, only the `root` object may be a draft. var isDraftProp = !!state && parent === root; if (isDraft(value)) { var path = isDraftProp && needPatches && !state.assigned[prop] ? rootPath.concat(prop) : null; // Drafts owned by `scope` are finalized here. value = this$1.finalize(value, path, scope); // Drafts from another scope must prevent auto-freezing. if (isDraft(value)) { scope.canAutoFreeze = false; } // Preserve non-enumerable properties. if (Array.isArray(parent) || isEnumerable(parent, prop)) { parent[prop] = value; } else { Object.defineProperty(parent, prop, { value: value }); } // Unchanged drafts are never passed to the `onAssign` hook. if (isDraftProp && value === state.base[prop]) { return; } } // Unchanged draft properties are ignored. else if (isDraftProp && 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 (isDraftProp && this$1.onAssign) { this$1.onAssign(state, prop, value); } }; each(root, finalizeProperty); return root; }; 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 = immer.setAutoFreeze.bind(immer); /** * 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 = immer.setUseProxies.bind(immer); /** * 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 = immer.applyPatches.bind(immer); /** * Create an Immer draft from the given base state, which may be a draft itself. * The draft can be modified until you finalize it with the `finishDraft` function. */ var createDraft = immer.createDraft.bind(immer); /** * Finalize an Immer draft from a `createDraft` call, returning the base state * (if no changes were made) or a modified copy. The draft must *not* be * mutated afterwards. * * Pass a function as the 2nd argument to generate Immer patches based on the * changes that were made. */ var finishDraft = immer.finishDraft.bind(immer); /* * Copyright 2018 The boardgame.io Authors * * Use of this source code is governed by a MIT-style * license that can be found in the LICENSE file or at * https://opensource.org/licenses/MIT. */ /** * Plugin that allows using Immer to make immutable changes * to G by just mutating it. */ var PluginImmer = { fnWrap: function fnWrap(move) { return produce(move); } }; /** * List of plugins that are a