src/ShadowImpl.js
- import clone from "lodash.clone";
- import cloneDeep from "lodash.clonedeep";
- import isString from "lodash.isstring";
- import result from "lodash.result";
-
- import {
- assert,
- isPrimitive,
- isSomething,
- } from "akutils";
-
- import Access from "./Access";
- import extendProperty from "./extendProperty";
- import isShadow from "./isShadow";
- import reshadow from "./reshadow";
-
- import appDebug, { ShadowImplKey as DebugKey } from "./debug";
- const debug = appDebug(DebugKey);
-
-
- // instance variable names
- const _access = Symbol('access');
- // object to store expensive derived values
- const _cache = Symbol('cache');
- // flag indicating property has pending updates. Not safe to rely on this[_futureState] as it could be
- // undefined if that is the next value
- const _changed = Symbol('changed');
- const _date = Symbol('date');
- const _dead = Symbol('dead');
- const _didShadowCalled = Symbol('didShadowCalled');
- const _futureState = Symbol('futureState');
- const _invalid = Symbol('invalid');
- const _name = Symbol('name');
- const _nextName = Symbol('nextName');
- const _path = Symbol('path');
-
- // flag marks this property as obsolete and thus no longer to effect updates on the
- // next data model
- const _preventUpdates = Symbol('preventUpdates');
- const _previousTime = Symbol('previousTime');
- const _property = Symbol('property');
- const _readonly = Symbol('readonly');
- const _replaced = Symbol('replaced');
- const _scheduled = Symbol('scheduled');
- const _shadow = Symbol('shadow');
- const _state = Symbol('state');
- const _time = Symbol('time');
-
- // private method symbols
- const _createShadow = Symbol('createShadow');
- const _defineProperty = Symbol('defineProperty');
- const _modelForUpdate = Symbol('modelForUpdate');
- const _scheduleUpdate = Symbol('scheduleUpdate');
- const _setupShadow = Symbol('setupShadow');
-
-
- /*
- Todos:
- * Isolated support
- -
-
- * Reduce memory footprint:
- 1) investigate _time and _previousTime really needed
- 2) investigate getting access from property (reduce object creation and memory footprint)
-
- * Investigate replacing impl with Proxy
- */
-
- /**
- The base class for {@link Shadow} backing objects. Each shadow property has an 'impl' that
- performs the f.lux bookkeeping to enable the shadow state to work properly. The 'impl' is
- broken out from the shadow proper to prevent polluting the namespace with a bunch of crazy
- looking variables and methods.
-
- A shadow property's 'impl' is available through the {@link Shadow.__} method. Direct access
- to the 'impl' is rarely needed by custom properties, shadows, or application logic. And there
- almost certainly no reason to directly subclass this class.
-
- @see {@link Shadow.__}
- */
- export default class ShadowImpl {
- constructor(time, property, name, state, parent, shader, prev) {
- this[_property] = property;
- this[_name] = name;
- this[_state] = state;
- this[_time] = time;
-
- if (prev) {
- this[_previousTime] = prev[_time];
- }
-
- // TODO: quick hack till have unit tests and thought out life-cycle design
- // didShadow() is being called multiple times which is causing a problem with property
- // initialization that should only occur once.
- this[_didShadowCalled] = false;
- }
-
- access() {
- const parent = this.parent();
-
- if (!this[_access]) {
- if (parent && parent.access().create$ForChild) {
- // property does not know about this impl yet. So impl.property() will work but property.__() will not
- this[_access] = parent.access().create$ForChild(this);
- } else {
- this[_access] = this[_property].create$(this);
- }
- }
-
- return this[_access];
- }
-
- /**
- Replace the value of this property. This will result in this property tree being recreated.
-
- Note: This value will be used directly (not copied) so ensure the state is not altered.
- */
- assign(nextState, name) {
- nextState = isShadow(nextState) ?nextState.__().state() :nextState;
-
- //create a deep copy so not shared with the passed in value
- //this.deepcopy() will use current model if no value passed or value passed is null or undefined
- //in case of assigned those are legal values so must check explicitly
- return this.update( state => {
- return { name: name || "assign()", nextState, replace: true };
- });
- }
-
- /**
- Prevents all children from being able to obtain model in update() callbacks. Update callbacks
- should invoke this method when they perform wholesale
- */
- blockFurtherChildUpdates() {
- if (!this.hasChildren()) { return }
-
- const children = this.children();
-
- for (let i=0, child; child=children[i]; i++) {
- child.blockFurtherUpdates(true);
- }
- }
-
- /**
- Prevents this property and descendents from providing a model to update() callbacks.
-
- The update() method invokes this method when the callback returns a different object than the
- one passed into the callback.
- */
- blockFurtherUpdates(replaced) {
- this[_preventUpdates] = true;
- this.invalidate(null, this);
-
- if (replaced) {
- this[_replaced] = true;
- }
-
- this.blockFurtherChildUpdates();
- }
-
- changeParent(newParent) {
- assert( a => a.is(this.isValid(), `Property must be valid to change parent: ${ this.dotPath() }`)
- .not(this.isRoot(), `Root properties do not have parents: ${ this.dotPath() }`) );
-
- debug( d => d(`changeParent(): ${this.dotPath()}`) );
-
- // clear cache
- delete this[_cache];
-
- // setup access through shadows
- this[_defineProperty]();
- }
-
- cache() {
- if (!this[_cache]) {
- this[_cache] = {};
- }
-
- return this[_cache];
- }
-
- /**
- Create a copy of the internals during reshadowing when the property has not changed during the
- update process but some descendant has been modified.
- */
- createCopy(time, newModel, parentImpl) {
- const property = this[_property];
- const ImplClass = property.implementationClass();
- const name = this.nextName();
- const shader = this.shader(newModel);
-
- return new ImplClass(time, property, name, newModel, parentImpl, shader, this);
- }
-
- didShadow(time, newRoot) {
- const storeRootImpl = this.store().rootImpl;
-
- if (this[_time] == time && !this[_didShadowCalled] && storeRootImpl === this.root()) {
- this[_didShadowCalled] = true;
-
- if (this.isRoot()) {
- if (this[_previousTime] || !newRoot) {
- this[_property].onPropertyDidUpdate();
- } else {
- this[_property].onPropertyDidShadow();
- }
- } else {
- this[_previousTime] ?this[_property].onPropertyDidUpdate() :this[_property].onPropertyDidShadow();
- }
-
- if (this.hasChildren()) {
- const children = this.children();
- var childImpl;
-
- for (let i=0, len=children.length; i<len; i++) {
- let childImpl = children[i];
-
- if (childImpl) {
- childImpl.didShadow(time);
- }
- }
- }
- }
- }
-
- /**
- Intended for use by update() and replaying actions.
- */
- dispatchUpdate(action) {
- if (!this[_preventUpdates] && this.isUpdatable() && this.isActive()) {
- const { name, nextState, replace } = action;
-
- // Sending to store first ensures:
- // 1) nextState() returns value from before this udpate
- // 2) middleware provided chance to make changes to action
- this.store().onPreStateUpdate(action, this);
-
- // replacing the current object prevents further next state changes for sub-properties
- if (replace) {
- this[_replaced] = true;
- // block child updates because replacement makes them unreachable
- this.blockFurtherChildUpdates();
- this.onReplaced();
- }
-
- // set the next model data
- this[_futureState] = nextState;
-
- // update the parent's future state to reference the state returned by the action
- if (!this.isRoot() && !this.isIsolated()) {
- const parentNextData = this.parent()[_modelForUpdate]();
-
- // do nothing if parentNextData is not assignable
- if (parentNextData && !isPrimitive(parentNextData)) {
- parentNextData[this[_name]] = nextState;
- }
- }
-
- this.invalidate(null, this);
-
- this.store().onPostStateUpdate(action, this);
- this.root()[_scheduleUpdate]();
- }
- }
-
- /**
- Helpful debugging utility that returns the path joined by '.'. The root node will return the
- word 'root' for the path.
- */
- dotPath() {
- const cache = this.cache();
-
- if (!cache.dotPath) {
- const path = this.path();
-
- cache.dotPath = path.length ?path.join('.') :'root';
- }
-
- return cache.dotPath;
- }
-
- ensureMounted() {
- if (this.isRoot() || this.isIsolated() || this.__getCalled__) { return }
-
- result(this.store().shadow, this.dotPath())
- }
-
- findByPath(path) {
- if (path.length === 0) { return this; }
-
- const next = this.getChild(path[0]);
-
- return next && next.findByPath(path.slice(1));
- }
-
- /**
- Gets if an update has occurred directly to this property.
- */
- hasPendingChanges() {
- return !!this[_changed];
- }
-
- /**
- Marks property and ancestors as invalid. This means this property or one of its children
- has been updated. The invalid flag is set to the earliest timestamp when this property
- or one of its children was changed.
-
- Parameters:
- childImpl - the child implementation triggering this call or undefined if this implementation
- started the invalidation process
- source - the shadow implementation that triggered the invalidation
- */
- invalidate(childImpl, source=this) {
- const owner = this.owner();
-
- if (childImpl) {
- this[_property].onChildInvalidated(childImpl.property(), source.property());
- }
-
- if (this.isValid() && this.isActive()) {
- this[_invalid] = true;
-
- if (owner) {
- owner.invalidate(this, source);
- }
- }
- }
-
- /**
- Gets if the property represents live data.
- */
- isActive() {
- return !this[_dead];
- }
-
- isIsolated() {
- return this.property().isIsolated();
- }
-
- isLeaf() {
- return !this.hasChildren();
- }
-
- isRoot() {
- return this.property().isRoot();
- }
-
- /**
- Gets if this property or one of its child properties has pending updates. Returns true if there are no
- pending updates.
- */
- isValid() {
- return !this[_invalid];
- }
-
- latest() {
- return this.store().findByPath(this.path());
- }
-
- name() {
- return this[_name];
- }
-
- /**
- Gets the name after all model updates are performed.
- */
- nextName() {
- return this[_nextName] !== undefined ?this[_nextName] :this[_name];
- }
-
- /**
- Gets the model as it will be once all pending changes are recorded with the store. This must
- not be altered.
- */
- nextState() {
- return this.hasPendingChanges() || !this.isValid() ?this[_futureState] :this.state();
- }
-
- /**
- Marks this property as obsolete. Once marked obsolete a property may not interact with the store.
- A property becomes obsolete after it's value or ancestor's value has changed and the update process
- has completed.
-
- This method does not affect subproperties.
- */
- obsolete(callback) {
- if (callback) {
- callback(this);
- }
-
- this[_dead] = true;
- }
-
- obsoleteChildren() {
- if (this.hasChildren()) {
- const children = this.children();
-
- for (let i=0, len=children.length; i<len; i++) {
- let child = children[i];
-
- if (child) {
- child.obsoleteTree();
- }
- }
- }
- }
-
- /**
- Marks the entire subtree as inactive, aka dead.
- */
- obsoleteTree(callback) {
- if (!this[_dead]) {
- this.obsolete(callback);
- this.obsoleteChildren();
- }
- }
-
- owner() {
- const ownerProperty = this[_property].owner();
-
- return ownerProperty && ownerProperty.__();
- }
-
- parent() {
- const parentProperty = this.property().parent();
-
- return parentProperty && parentProperty.__();
- }
-
- /**
- Gets an array with the property names/indices from the root to this property.
- */
- path() {
- const cache = this.cache();
-
- if (this.isRoot()) {
- return [];
- } else if (!cache.path) {
- cache.path = this.parent().path().concat(this[_name]);
- }
-
- return cache.path;
- }
-
- property() {
- return this[_property];
- }
-
- readonly() {
- return this[_readonly] === undefined ?this[_property].isReadonly() :this[_readonly];
- }
-
- replaced() {
- return !!this[_replaced];
- }
-
- /**
- Invoked by reshadow() function for invalid parent property implementations when the directly
- managed state did not change.
-
- Calls the onReshadow(prev) method to provide subclasses an oppotunity to setup for futher
- action after a parent change.
- */
- reshadowed(prev) {
- debug( d => d(`reshadowed(): ${this.dotPath()}, mapped=${prev.isMapped()}, time=${this[_time]}, prevTime=${prev[_time]}`) );
-
- if (prev.__getCalled__) {
- this[_setupShadow](prev, true);
- }
-
- this.onReshadow(prev);
- }
-
- root() {
- if (this[_property].isRoot()) { return this }
-
- const cache = this.cache();
-
- if (!cache.root) {
- cache.root = this.owner().root();
- }
-
- return cache.root;
- }
-
- /**
- Sets the readonly flag which will prevent a 'set' function being set in defineProeprty().
-
- Note: this method must be called before defineProperty() is invoked or it will have no affect.
- */
- setReadonly(readonly) {
- this[_readonly] = readonly;
- }
-
- /**
- Creates shadow properties for root properties and sets this property on the parent property for
- non-root properties.
-
- Note: This method is called by shadowProperty() and reshadow() functions.
- */
- setupPropertyAccess(prev) {
- const property = this[_property];
-
- if (this.isRoot() || this.isIsolated()) {
- this[_setupShadow](prev);
- } else {
- this[_defineProperty](prev);
- }
- }
-
- /**
- Gets the shader needed to recreate the shadow property for the state.
- */
- shader(state) {
- return this[_property].shader(state);
- }
-
- /**
- Gets the user facing property represented by this implementation object.
- */
- shadow() {
- if (!this.isMapped()) { throw new Error(`Property implementation not mapped: ${this.dotPath()}`) }
-
- return this[_setupShadow]()
- }
-
- /**
- Helpful debugging utility that returns the path joined by '.'. The root node will return the
- word 'root' for the path.
- */
- slashPath() {
- const cache = this.cache();
-
- if (!cache.slashPath) {
- const path = this.path();
-
- cache.slashPath = path.length ?`/${path.join('/')}` :'/';
- }
-
- return cache.slashPath;
- }
-
- state() {
- return this[_state];
- }
-
- store() {
- return this[_property].store();
- }
-
- /**
- Transfers the nextName to the name attribute.
- */
- switchName() {
- if (this[_nextName] !== undefined) {
- this[_name] = this[_nextName];
- delete this[_nextName];
- }
- }
-
- time() {
- return this[_time];
- }
-
- /**
- Gets a compact version of this internal's state. It does NOT provide a JSON representation of the
- model state. The actual Property.toJSON() method returns the model JSON representation.
- */
- toJSON() {
- return {
- name: this[_name],
- path: this.dotPath(),
- active: !this[_dead],
- valid: this.isValid(),
- state: this.state(),
- }
- }
-
- //Gets a stringified version of the toJSON() method.
- toString() {
- return JSON.stringify(this);
- }
-
- /**
- Makes changes to the next property state. The callback should be pure (no side affects) but that
- is not a requirement. The callback must be of the form:
-
- (state) => return { nextState, replace }
-
- where:
- state - the next property state
- nextState - the state following the callback
- replace - boolean for whether nextState replaces the current value. The implication of true
- is that this property and all of it's children will not be able to make future changes
- to the model.
-
- To understand the reasoning behind the replace flag consider the following example:
-
- const model = { a: { b: { c: 1 } } }
- const oldB = model.a.b
-
- model.a.b = "foo"
- oldB.c = 5
-
- model.a.b.c === undefined
-
- Thus, oldB.c may change oldB'c property 'c' to 5 but model.a.b is still "foo".
- */
- update(callback) {
- assert( a => a.is(this.isActive(), `Property is not active: ${ this.dotPath() }`) );
-
- if (!this[_preventUpdates] && this.isUpdatable() && this.isActive()) {
- const next = this[_modelForUpdate]();
-
- // invoke callback without bind context to reduce overhead
- const action = callback(next);
- const { nextState, replace } = action;
-
- // mark property as having pending updates if the action callback returns a different
- // object/value or requests a replacement be created. An example where neither would be
- // true is a property touch() call because its shadow function signature changed.
- if (nextState !== next || replace) {
- this[_changed] = true;
- }
-
- this.dispatchUpdate(action);
-
- return true;
- }
-
- return false;
- }
-
- /**
- Marks this property as dead. Once marked obsolete a property may not accept further updates.
- A property is updated when the state changes but not a wholesale replacement or a descendents's
- value has changed and the update process has completed.
-
- This method does not affect subproperties.
- */
- updated() {
- this[_dead] = true;
-
- this.onUpdate();
- }
-
- /**
- Changes the name this property will have after updates. This is used when moving properties
- around in the model, such as when splice is used on an array. The nextName() method
- will return the property name for after updates are applied.
-
- Note: this method does not have any side effects beyond setting the _nextName instance
- variable. Subclasss will need to perform any book keeping associated with sub-properties.
- */
- updateName(name) {
- this[_nextName] = name;
- }
-
- /**
- Gets if shadow property is allowing state updates.
-
- @return {boolean} `false` if the property or its parent has been replaced, `true` otherwise.
- */
- updatesAllowed() {
- return !this[_preventUpdates] && !this[_replaced];
- }
-
- /**
- Invokes a callback once all pending changes have occurred. The callback should have the form:
-
- callback(property, implementation)
-
- where the property and implementation arguments are the latest version if they still exist.
-
- This method is safe to call on a dead property.
- */
- waitFor(callback) {
- if (this.isValid() && this.isActive()) {
- // short circuit if no changes pending
- callback(this.shadow());
- } else {
- this.store().waitFor( () => {
- const latest = this.latest();
-
- callback(latest && latest.shadow(), latest);
- });
- }
- }
-
- /**
- Invoked by the shadowing process to invoke appropriate {@link Property} life-cycle methods.
- The method name is a reflection that shadow state tree invocation chain for `willShadow()`
- occurs when the {@link Store} is going to shadow that state.
-
- @param {boolean} parentWillUnshadow - `true` when parent property is unshadowing.
- */
- willShadow(parentWillUnshadow) {
- var willUnshadow = parentWillUnshadow || false;
-
- if (parentWillUnshadow) {
- // all properties under an unshadowed proeprty also get unshadowed
- this[_property].onPropertyWillUnshadow();
- willUnshadow = true;
- } else if (this.isValid()) {
- // nothing else to do since this property and all subproperties must be fine
- return;
- } else if (this[_replaced] || this[_preventUpdates]) {
- this[_property].onPropertyWillUnshadow();
- willUnshadow = true;
- }
-
- if (this.hasChildren()) {
- const children = this.children();
- var childImpl;
-
- for (let i=0, len=children.length; i<len; i++) {
- let childImpl = children[i];
-
- if (childImpl) {
- childImpl.willShadow(willUnshadow);
- }
- }
- }
- }
-
-
- //------------------------------------------------------------------------------------------------------
- // Methods with base implementations that subclasses may need to override - no need to call super
- //------------------------------------------------------------------------------------------------------
-
- /**
- Creates a deep clone of the current property state.
- */
- copyState() {
- return cloneDeep(this.state());
- }
-
- /**
- Invoked on shadow getter access to obtain the get value.
-
- The default implementation returns the shadow.
-
- @return the shadow or other f.lux representative value for the shadow proeprty.
- */
- definePropertyGetValue(state) {
- return this[_createShadow]();
- }
-
- /**
- Invoked on shadow property assignment to perform the replacement f.lux action.
-
- The default implementation is to assign the new value with no checking.
-
- @param newValue - the new state value
- */
- definePropertySetValue(newValue) {
- this.assign(newValue);
- }
-
- /**
- Gets if the property has an child properties (not whether child properties are supported).
- */
- hasChildren() {
- return this.childCount() != 0;
- }
-
- /**
- Gets if this property type reprsents a primitive javascript type.
- */
- isPrimitive() {
- return false;
- }
-
- /**
- Gets whether the property value supports calls to update().
- */
- isUpdatable() {
- return true;
- }
-
- /**
- Property has just been reshadowed.
- */
- onReshadow(prev) { }
-
- /**
- Hook for when this property is no longer represented in the system state.
- */
- onReplaced() { }
-
- /**
- Hook for when this property is no longer represented in the system state due to a state
- update - not a replacement.
- */
- onUpdate() { }
-
-
- //------------------------------------------------------------------------------------------------------
- // Methods that ShadowImpl subclasses must be implemented by subclasses
- //------------------------------------------------------------------------------------------------------
-
- /**
- Merges a new state into this property by using the 'state' parameter to set default values, ie it
- will not overwrite any existing values. Useful when model objects arrive from external sources,
- such as an asyncrhonous save or a websocket based update.
- */
- defaults(state) {
- throw new Error("ShadowImpl subclasses must implement defaults()");
- }
-
- /**
- Merges a new state into this property. Useful when model objects arrive from external
- sources, such as an asyncrhonous save or a websocket based update.
- */
- merge(state) {
- throw new Error("ShadowImpl subclasses must implement merge()");
- }
-
-
- //------------------------------------------------------------------------------------------------------
- // Methods that ShadowImpl subclasses with children must implement
- //------------------------------------------------------------------------------------------------------
-
- /**
- Invoked during defineProperty() to define children properties marked for automount
- */
- automountChildren() {
- // throw new Error("ShadowImpl subclasses with children must implement children()");
- }
-
- /**
- Subclasses should implement this method in such a way as not to trigger a mapping.
- */
- childCount() {
- return 0;
- }
-
- /**
- Gets the implementation objects managed by this property.
- */
- children() {
- throw new Error("ShadowImpl subclasses with children must implement children()");
- }
-
- /**
- Gets a child implementation matching a property name or undefined if no such property exists.
- */
- getChild(name) {
- return undefined;
- }
-
- /**
- Maps all child properties onto this property using Object.defineProperty().
-
- @param {ShadowImpl} prev - the previous property shadow implementation instance.
- @param {boolean} inCtor - `true` if call occuring during shadowing process.
- */
- defineChildProperties(prev, inCtor) { }
-
- /**
- Gets if defineChildProperties() has been invoked.
- */
- isMapped() {
- return true;
- }
-
- /**
- Gets the keys/indices for this property.
-
- Implementation note: Subclasses should implement this method in such a way as not to trigger a mapping.
- */
- keys() {
- throw new Error("ShadowImpl subclasses with children must implement keys()");
- }
-
-
- //------------------------------------------------------------------------------------------------------
- // Private functions - should not be called by code outside this file.
- //------------------------------------------------------------------------------------------------------
-
- [_createShadow]() {
- if (!this[_shadow]) {
- let ShadowClass = this[_property].shadowClass();
-
- this[_shadow] = new ShadowClass(this);
- extendProperty(this[_property], this, this[_shadow]);
- }
-
- return this[_shadow];
- }
-
- [_setupShadow](prev, inCtor) {
- if (!this.__getCalled__) {
- let state = this.state();
-
- debug( d => d(`_setupShadow(): ${this.dotPath()}, time=${this[_time]}`) );
-
- this.__getCalled__ = true;
- var shadow = this.__getResonse__ = this.definePropertyGetValue(state);
-
- this.defineChildProperties(prev, inCtor);
-
- // freeze shadows in dev mode to provide check not assigning to non-shadowed property
- // this can have performance penalties so skip in production mode
- if (process.env.NODE_ENV !== 'production') {
- !Object.isFrozen(shadow) && Object.freeze(shadow);
- }
- }
-
- return this.__getResonse__;
- }
-
- /**
- Maps the getter and setter (if appropriate) onto the parent property.
- */
- [_defineProperty](prev) {
- if (this.isRoot() || this.isIsolated()) { return }
-
- // names with a leading '_' are not enumerable (way of hiding them)
- const enumerable = !(isString(this[_name]) && this[_name].startsWith('_'));
- const parentShadow = this.parent().shadow();
- const state = this.state();
- const set = this.readonly()
- ?undefined
- :newValue => {
- if (!this.isActive()) {
- if (process.env.NODE_ENV !== 'production') {
- console.error(`Attempting to set value on inactive property: ${this.dotPath()}`, newValue);
- }
-
- return
- }
-
- return this.definePropertySetValue(newValue);
- }
-
- try {
- Object.defineProperty(parentShadow, this[_name], {
- enumerable: enumerable,
- get: () => {
- if (isSomething(state)) {
- return this[_setupShadow]();
- } else {
- return state;
- }
- },
- set: set
- });
- } catch(error) {
- console.warn(`_defineProperty() Error: name=${this[_name]}, parent=${this.parent().dotPath()}`, error.stack);
- debugger
- }
-
- this.automountChildren(prev);
- }
-
- /**
- Gets the next model state for the property. This value is used for performing property updates through
- the update() function.
-
- Calls to update() trigger an update through the dispatcher upon which the new object will be mapped
- and the store informed of the change.
- */
- [_modelForUpdate]() {
- if (!this[_futureState]) {
- if (this.isRoot() || this.isIsolated()) {
- // next data will be a shallow copy of current model
- this[_futureState] = clone(this.state());
- } else {
- const parentNextState = this.parent()[_modelForUpdate]();
-
- // Primitive parent models do not support adding properties
- if (isPrimitive(parentNextState)) {
- return undefined;
- }
-
- // next data is a shallow copy of the parent's value of this property
- this[_futureState] = clone(parentNextState[this[_name]]);
-
- // place a shallow clone in place of current value
- parentNextState[this.nextName()] = this[_futureState]
- }
- }
-
- return this[_futureState];
- }
-
- /**
- Schedules an UPDATE action with the store. On action execution, the new property will be generated
- and returned to the store.
- */
- [_scheduleUpdate]() {
- if (!this.isRoot()) {
- return this.root()[_scheduleUpdate]();
- }
-
- if (!this[_scheduled] && !this.isValid() && !this[_dead]) {
- // flag never gets cleared
- this[_scheduled] = true;
-
- this.store().dispatchUpdate( time => reshadow(time, this[_futureState], this) );
- }
- }
- }
-
-