"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.State = exports.Model = void 0;

var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));

var _toArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toArray"));

var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));

var _get3 = _interopRequireDefault(require("@babel/runtime/helpers/get"));

var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));

var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));

var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));

var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));

var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var _tsFns = require("ts-fns");

var _schema = require("./schema.js");

var _store = require("./store.js");

var _utils = require("./shared/utils.js");

var _edit = require("./shared/edit.js");

var _meta = require("./meta.js");

var _factory = require("./factory.js");

var _configs = require("./shared/configs.js");

var _type = require("./ty/type.js");

var _constants = require("./shared/constants.js");

function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2.default)(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2.default)(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2.default)(this, result); }; }

function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }

var State = /*#__PURE__*/(0, _createClass2.default)(function State(options) {
  (0, _classCallCheck2.default)(this, State);
  Object.assign(this, options);
});
exports.State = State;

var Model = /*#__PURE__*/function () {
  function Model() {
    var _this = this;

    var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    (0, _classCallCheck2.default)(this, Model);
    var $this = this; // const Constructor = getConstructorOf(this)

    var parent = options.parent,
        keyInParent = options.key,
        _options$scenes = options.scenes,
        scenes = _options$scenes === void 0 ? this._takeSceneCodes() : _options$scenes;
    (0, _tsFns.define)(this, '$$events', []);
    (0, _tsFns.define)(this, '$$attrs', _objectSpread(_objectSpread({}, _configs.RESERVED_ATTRIBUTES), this._takeAttrs()));
    (0, _tsFns.define)(this, '$$state', this._takeState());
    (0, _tsFns.define)(this, '$$deps', {});
    (0, _tsFns.define)(this, '$$memories', []);
    (0, _tsFns.define)(this, '$$scenes', {
      value: scenes,
      configurable: true
    });
    /**
     * create schema
     */

    var Schema = /*#__PURE__*/function (_Schema2) {
      (0, _inherits2.default)(Schema, _Schema2);

      var _super = _createSuper(Schema);

      function Schema(metas) {
        (0, _classCallCheck2.default)(this, Schema);

        var overrideMetas = $this._takeOverrideMetas();

        var defs = (0, _tsFns.map)(metas, function (def) {
          if (!def) {
            return;
          }

          if ((0, _tsFns.isInheritedOf)(def, _meta.Meta)) {
            def = new def();
          }

          if ((0, _tsFns.isInstanceOf)(def, _meta.Meta)) {
            if (overrideMetas.length) {
              var item = overrideMetas.find(function (item) {
                return isMatchMeta(def, item.meta);
              });

              if (item) {
                return def.extend(item.attrs);
              }
            } // use .extend to forbide use the some instance and be overrided by scene switching


            return def.extend({});
          }

          if ((0, _tsFns.isObject)(def) && (0, _tsFns.inObject)('default', def)) {
            var meta = new _meta.Meta(def);
            return meta;
          }
          /**
           * class SomeModel extends Model {
           *   static some = OtherModel
           * }
           */


          if ((0, _tsFns.isInheritedOf)(def, Model)) {
            return _factory.Factory.createMeta(def);
          }
          /**
           * class SomeModel extends Model {
           *   static some = [OtherModel, AnyModel]
           * }
           */


          if ((0, _tsFns.isArray)(def) && !def.some(function (def) {
            return !(0, _tsFns.isInheritedOf)(def, Model);
          })) {
            return _factory.Factory.createMeta(def);
          }

          return def;
        });
        return _super.call(this, defs);
      }

      (0, _createClass2.default)(Schema, [{
        key: "onError",
        value: function onError(e) {
          // dont throw error when generate data when initialize
          if ($this.$init && e.attr === 'create') {
            return;
          }

          $this.onError(e);
          $this.emit('error', e);
        }
      }]);
      return Schema;
    }(_schema.Schema); // create schema


    var schema = this._takeSchema(Schema); // support schema instance or object


    if (!(0, _tsFns.isInstanceOf)(schema, _schema.Schema)) {
      schema = (0, _tsFns.map)(schema, function (meta) {
        if ((0, _tsFns.isObject)(meta) && (0, _tsFns.hasOwnKey)(meta, 'default')) {
          return new _meta.Meta(meta);
        }

        return meta;
      });
      schema = new Schema(schema);
    }

    (0, _tsFns.define)(this, '$schema', schema);
    /**
     * process generated metas
     * metas' attributes may be changed dynamicly after schema created
     */

    var metaKeys = Object.keys(this.$schema); // switchScene should come before needs check

    var deferers = [];
    metaKeys.forEach(function (metaKey) {
      var meta = _this.$schema[metaKey];

      if ((0, _tsFns.isInstanceOf)(meta, _meta.AsyncMeta)) {
        // make async attribute enable to notify back
        meta._awaitMeta($this, metaKey, meta);
      } else if (scenes && (0, _tsFns.isInstanceOf)(meta, _meta.SceneMeta)) {
        // make async attribute enable to notify back
        meta._awaitMeta($this, metaKey, meta); // switch to new scene


        var deferer = meta.switchScene(scenes);

        if ((0, _tsFns.isInstanceOf)(deferer, Promise)) {
          deferers.push(deferer);
        }
      }
    }); // override by _takeOverrideAttrs

    var overrideAttrs = function overrideAttrs(dispatch) {
      var overrides = _this._takeOverrideAttrs();

      if (!overrides.length) {
        return;
      }

      metaKeys.forEach(function (metaKey) {
        var meta = _this.$schema[metaKey];
        var item = overrides.find(function (item) {
          return isMatchMeta(meta, item.meta);
        });

        if (!item) {
          return;
        }

        Object.assign(meta, item.attrs);

        if (dispatch) {
          _this.$store.forceDispatch("!".concat(metaKey), 'override attrs');
        }
      });
    }; // check needs() and deps()


    var checkNeeds = function checkNeeds() {
      var needs = [];
      var gives = [];
      metaKeys.forEach(function (metaKey) {
        var meta = _this.$schema[metaKey];

        if ((0, _tsFns.isFunction)(meta.needs)) {
          needs.push.apply(needs, (0, _toConsumableArray2.default)(meta.needs.call(_this, metaKey).map(function (item) {
            return {
              need: item,
              by: meta
            };
          })));
        }

        gives.push(meta); // if it is Model, make visible in gives

        if ((0, _tsFns.isInstanceOf)(meta, _factory.FactoryMeta)) {
          var entries = meta.$entries;
          gives.push.apply(gives, (0, _toConsumableArray2.default)([].concat(entries)));
        }
      });

      if (!needs.length) {
        return;
      }

      var _loop = function _loop(i, len) {
        var _needs$i = needs[i],
            need = _needs$i.need,
            by = _needs$i.by;

        if (gives.some(function (item) {
          return isMatchMeta(item, need);
        })) {
          return "break";
        }

        console.error(need, 'is needed by', by, 'but not given in Model', $this);
        throw new Error('Dependence is not given, please check dependencies graph.');
      };

      for (var i = 0, len = needs.length; i < len; i++) {
        var _ret = _loop(i, len);

        if (_ret === "break") break;
      }
    };

    if (deferers.length) {
      Promise.all(deferers).then(function () {
        overrideAttrs(true);
        setTimeout(checkNeeds, 0);
      });
    } else {
      overrideAttrs();
      setTimeout(checkNeeds, 0);
    }
    /**
     * patch nested model properties
     * $root, $parent, $keyPath, $absKeyPath
     */
    // use passed parent


    if (parent && (0, _tsFns.isInstanceOf)(parent, Model) && keyInParent) {
      this._setParent([parent, keyInParent]);
    }

    (0, _tsFns.define)(this, '$root', function () {
      var parent = _this.$parent;

      if (!parent) {
        return;
      }

      while (parent.$parent) {
        parent = parent.$parent;
      }

      return parent;
    });
    (0, _tsFns.define)(this, '$absKeyPath', function () {
      var parent = _this.$parent;

      if (!parent) {
        return [];
      }

      var path = (0, _toConsumableArray2.default)(_this.$keyPath);

      while (parent && parent.$keyPath) {
        path.unshift.apply(path, (0, _toConsumableArray2.default)(parent.$keyPath));
        parent = parent.$parent;
      }

      return path;
    });
    /**
     * create store
     */

    var Store = /*#__PURE__*/function (_Store2) {
      (0, _inherits2.default)(Store, _Store2);

      var _super2 = _createSuper(Store);

      function Store() {
        (0, _classCallCheck2.default)(this, Store);
        return _super2.apply(this, arguments);
      }

      (0, _createClass2.default)(Store, [{
        key: "_traps",
        value: function _traps(traps) {
          var isNotNeed = function isNotNeed(keyPath) {
            if (keyPath.length !== 1) {
              return true;
            }

            var _keyPath = (0, _slicedToArray2.default)(keyPath, 1),
                key = _keyPath[0];

            var meta = $this.$schema[key];

            if (!meta) {
              return true;
            }

            if (!(0, _tsFns.isInstanceOf)(meta, _factory.FactoryMeta)) {
              return true;
            }

            var $entries = meta.$entries;

            if (!(0, _tsFns.isArray)($entries)) {
              return true;
            }
          };

          var inserter = function inserter(keyPath, args) {
            if (isNotNeed(keyPath)) {
              return args;
            }

            var _keyPath2 = (0, _slicedToArray2.default)(keyPath, 1),
                key = _keyPath2[0];

            var meta = $this.$schema[key];
            var $entries = meta.$entries,
                $create = meta.$create,
                $adapt = meta.$adapt;

            try {
              var nexts = args.filter(function (item) {
                if ($adapt($entries, item, key, $this)) {
                  return true;
                }

                if ((0, _tsFns.isObject)(item)) {
                  return true;
                }

                return false;
              });
              var values = $create(nexts, key, $this);
              return values;
            } catch (e) {
              console.error(e);
            }
          };

          traps.push = inserter;
          traps.unshift = inserter;

          traps.splice = function (keyPath, args) {
            if (isNotNeed(keyPath)) {
              return args;
            }

            var _args = (0, _toArray2.default)(args),
                start = _args[0],
                count = _args[1],
                items = _args.slice(2);

            var values = items.length ? inserter(keyPath, items) : [];
            return [start, count].concat((0, _toConsumableArray2.default)(values));
          };

          traps.fill = function (keyPath, args) {
            if (isNotNeed(keyPath)) {
              return args;
            }

            var _args2 = (0, _slicedToArray2.default)(args, 3),
                value = _args2[0],
                start = _args2[1],
                end = _args2[2];

            var _keyPath3 = (0, _slicedToArray2.default)(keyPath, 1),
                key = _keyPath3[0];

            var current = $this[key];
            var len = current.length; // without effects

            if (start && start >= len) {
              return false;
            }

            var from = start || 0;

            if (end && end <= from) {
              return false;
            }

            var to = !end || end > len ? len : end;
            var count = to - from;
            var items = (0, _tsFns.createArray)(value, count);
            var values = inserter(keyPath, items);
            return {
              to: 'splice',
              args: [start, count].concat((0, _toConsumableArray2.default)(values))
            };
          };

          var trapGet = traps.get;

          traps.get = function (keyPath, active) {
            trapGet(keyPath, active);

            if (!$this.$collection) {
              var deep = [];
              var child = $this;
              var _parent = child.$parent;
              var flag = null;

              while (_parent) {
                var _keyPath4 = child.$keyPath;
                deep.unshift.apply(deep, (0, _toConsumableArray2.default)(_keyPath4));

                if (_parent.$collection) {
                  flag = _parent;
                  break;
                }

                child = _parent;
                _parent = child.$parent;
              }

              if (flag) {
                flag.$collection.items.push([deep, $this]);
                $this.collect();
              }
            }

            if ($this.$collection && $this.$collection.enable && $this.$collection.fields) {
              $this.$collection.items.push([keyPath, active]);

              if ((0, _tsFns.isInstanceOf)(active, Model)) {
                active.collect();
              }
            }

            return active;
          };

          return traps;
        }
      }, {
        key: "dispatch",
        value: function dispatch(keyPath, e, force) {
          var notified = (0, _get3.default)((0, _getPrototypeOf2.default)(Store.prototype), "dispatch", this).call(this, keyPath, e, force); // propagation

          if (notified && $this.$parent && $this.$keyPath && $this.$keyPath[0] !== false) {
            $this.$parent.$store.dispatch([].concat((0, _toConsumableArray2.default)($this.$keyPath), (0, _toConsumableArray2.default)(keyPath)), e, true);
          }

          return notified;
        }
      }, {
        key: "getContext",
        value: function getContext() {
          return $this;
        }
      }]);
      return Store;
    }(_store.Store);

    var store = new Store();
    (0, _tsFns.define)(this, '$store', store);
    /**
     * finish
     */

    this.init(data);
    this.emit('init');
    (0, _tsFns.define)(this, '$inited', true);
    /**
     * support async onInit
     * i.e.
     *
     * async onInit() {
     *   const options = await this.$schema.some.getOptionsAsync()
     *   this.options = options
     * }
     *
     * async getOptions() {
     *   await this.$ready
     *   return this.options
     * }
     */

    (0, _tsFns.define)(this, '$ready', Promise.resolve(this.onInit()));
  }

  (0, _createClass2.default)(Model, [{
    key: "_takeOverrideMetas",
    value: function _takeOverrideMetas() {
      return [];
    }
    /**
     * the same given with _takeOverrideMetas
     * this will force override given attributes, have highest priority
     */

  }, {
    key: "_takeOverrideAttrs",
    value: function _takeOverrideAttrs() {
      var Constructor = (0, _tsFns.getConstructorOf)(this);
      var overrideAttrs = Constructor.Attrs || [];
      return overrideAttrs;
    }
  }, {
    key: "_takeSceneCodes",
    value: function _takeSceneCodes() {
      var Constructor = (0, _tsFns.getConstructorOf)(this);
      var sceneCodes = [];

      var append = function append(code) {
        if (!sceneCodes.includes(code)) {
          sceneCodes.unshift(code);
        }
      };

      var pushSceneCodes = function pushSceneCodes(target) {
        if ((0, _tsFns.isArray)(target[_constants.SceneCodesSymbol])) {
          var codes = target[_constants.SceneCodesSymbol];

          for (var i = codes.length - 1; i >= 0; i--) {
            var code = codes[i];
            append(code);
          }
        } else if ((0, _tsFns.isString)(target[_constants.SceneCodesSymbol])) {
          append(target[_constants.SceneCodesSymbol]);
        }
      };

      (0, _utils.traverseChain)(Constructor, Model, pushSceneCodes);
      return sceneCodes;
    }
  }, {
    key: "_takeSchema",
    value: function _takeSchema() {
      var Constructor = (0, _tsFns.getConstructorOf)(this);
      var schema = Constructor.prototype.schema ? Constructor.prototype.schema.call(this) : {}; // create schema by model's static properties

      var chain = (0, _utils.ofChain)(this, Model);
      Object.assign(chain, schema);
      return chain;
    }
  }, {
    key: "_takeState",
    value: function _takeState() {
      var _this2 = this;

      var properties = (0, _utils.ofChain)(this, Model);
      var Constructor = (0, _tsFns.getConstructorOf)(this);
      var state = Constructor.prototype.state ? Constructor.prototype.state.call(this) : {};
      (0, _tsFns.each)(properties, function (item, key) {
        // this.state has higher priority
        if (item && (0, _tsFns.isInstanceOf)(item, State) && !(0, _tsFns.inObject)(key, state)) {
          var get = item.get,
              set = item.set;

          if (get || set) {
            (0, _tsFns.define)(state, key, {
              get: get && get.bind(_this2),
              set: set && set.bind(_this2),
              enumerable: true
            });
          } else {
            state[key] = item.value;
          }
        }
      });
      return state;
    }
  }, {
    key: "_takeAttrs",
    value: function _takeAttrs() {
      var Constructor = (0, _tsFns.getConstructorOf)(this);
      var attrs = Constructor.prototype.attrs ? Constructor.prototype.attrs.call(this) : {};
      return attrs;
    }
  }, {
    key: "init",
    value: function init() {
      var _this3 = this;

      var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

      if (this.$ready) {
        return;
      }

      var keys = Object.keys(this.$schema); // patch keys to this

      keys.forEach(function (key) {
        (0, _tsFns.define)(_this3, key, {
          get: function get() {
            return _this3.get(key);
          },
          set: function set(value) {
            return _this3.set(key, value);
          },
          enumerable: true,
          configurable: true
        });
      }); // views

      var views = {};
      var reactivators = [];
      var reacceptors = []; // provide a possibility to check whether during validating

      var validatingQueues = {};
      var cachedErrorsUpdators = {};
      keys.forEach(function (key) {
        // patch attributes from meta
        var meta = _this3.$schema[key]; // default attributes which will be used by Model/Schema, can not be reset by userself

        var attrs = _this3.$$attrs; // define a view

        var view = {}; // use defineProperties to define view properties

        var viewDef = {};

        if (meta.activate) {
          reactivators.push({
            key: key,
            activate: meta.activate
          });
        }

        if (meta.accept) {
          reacceptors.push({
            key: key,
            accept: meta.accept
          });
        }

        (0, _tsFns.each)(attrs, function (fallback, attr) {
          if ((0, _tsFns.isNull)(fallback)) {
            return;
          }

          viewDef[attr] = {
            get: function get() {
              // use schema prototype methods to determine, so that go though inside logic
              if (typeof _configs.RESERVED_ATTRIBUTES[attr] === 'boolean') {
                return _this3.$schema[attr](key, _this3.get(key), _this3);
              }

              return _this3.$schema.$decide(attr, key, _this3.get(key), _this3)(fallback);
            },
            enumerable: true
          };
        });
        var asyncReactors = {};
        (0, _tsFns.each)(meta, function (descriptor, attr) {
          if ((0, _tsFns.inArray)(attr[0], ['$', '_'])) {
            return;
          }

          if ((0, _tsFns.inObject)(attr, attrs)) {
            return;
          }

          var get = descriptor.get,
              set = descriptor.set;

          if (get || set) {
            viewDef[attr] = {
              get: get && get.bind(_this3),
              set: set && set.bind(_this3),
              enumerable: true,
              configurable: true
            };
          } else if ((0, _utils.isAsyncRef)(descriptor.value)) {
            var value = descriptor.value;
            var current = value.current,
                attach = value.attach,
                deps = value.deps;
            var attrValue = current; // async set attr value

            var _get2 = function _get2() {
              return attach.call(_this3, key, attr).then(function (next) {
                if (next === attrValue) {
                  return;
                }

                var prev = attrValue;
                attrValue = next;

                _this3.$store.forceDispatch("!".concat(key, ".").concat(attr), next, prev);
              });
            };

            _get2();

            if ((0, _tsFns.isArray)(deps)) {
              deps.map(function (dep) {
                if ((0, _tsFns.isInstanceOf)(dep, _meta.Meta) || (0, _tsFns.isInheritedOf)(dep, _meta.Meta)) {
                  return _this3.use(dep).key;
                }

                return dep;
              }).forEach(function (dep) {
                asyncReactors[dep] = asyncReactors[dep] || [];
                asyncReactors[dep].push(_get2);
              });
            }

            viewDef[attr] = {
              get: function get() {
                if (_this3.$collection && _this3.$collection.enable && _this3.$collection.views) {
                  _this3.$collection.items.push(["!".concat(key, ".").concat(attr)]);
                }

                return attrValue;
              },
              set: function set(value) {
                var prev = attrValue;
                attrValue = value;

                _this3.$store.forceDispatch("!".concat(key, ".").concat(attr), value, prev);
              },
              enumerable: true,
              configurable: true
            };
          } else if ((0, _utils.isMemoRef)(descriptor.value)) {
            var _value = descriptor.value;
            var getter = _value.getter,
                compare = _value.compare,
                depend = _value.depend;
            viewDef[attr] = {
              get: function get() {
                return _this3.memo(getter, compare, depend);
              },
              enumerable: true,
              configurable: true
            };
          } // use as a getter
          else if ((0, _tsFns.isFunction)(descriptor.value)) {
            var _value2 = descriptor.value;
            viewDef[attr] = {
              get: function get() {
                var data = getData();
                return _value2.call(_this3, data, key);
              },
              enumerable: true,
              configurable: true
            };
          } // patch to view directly
          else {
            var _value3 = descriptor.value;
            var _attrValue = _value3;
            viewDef[attr] = {
              get: function get() {
                if (_this3.$collection && _this3.$collection.enable && _this3.$collection.views) {
                  _this3.$collection.items.push(["!".concat(key, ".").concat(attr)]);
                }

                return _attrValue;
              },
              set: function set(value) {
                var prev = _attrValue;
                _attrValue = value;

                _this3.$store.forceDispatch("!".concat(key, ".").concat(attr), value, prev);
              },
              enumerable: true,
              configurable: true
            };
          }
        }, true);
        Object.keys(asyncReactors).forEach(function (key) {
          _this3.watch(key, function (_ref) {
            var prev = _ref.prev,
                next = _ref.next;

            if (prev === next) {
              return;
            }

            var fns = asyncReactors[key];
            fns.forEach(function (fn) {
              return fn();
            });
          }, true);
        }); // unwritable mandatory view properties

        var getData = function getData() {
          return _this3.getData(key);
        };

        var changed = false; // whether the field has changed
        // delcare current key's validatingQueue

        var validatingQueue = [];
        validatingQueues[key] = validatingQueue; // delcare cachedErrors

        var cachedErrors = [];
        var cachedErrorsInited = false;
        var cachedTimer = null;
        var cachedDeferTimer = null;

        var setCachedErrors = function setCachedErrors(errors, silent) {
          var prev = cachedErrors;
          cachedErrors = errors && errors.length ? (0, _utils.makeMsg)(errors) : [];

          if (!silent) {
            _this3.$store.forceDispatch("!".concat(key, ".errors"), cachedErrors, prev);
          }
        };

        var updateCachedErrors = function updateCachedErrors() {
          var _this3$$schema$key$va;

          // check asyncly and dispatch
          if ((_this3$$schema$key$va = _this3.$schema[key].validators) !== null && _this3$$schema$key$va !== void 0 && _this3$$schema$key$va.some(function (item) {
            return item.async;
          })) {
            clearTimeout(cachedDeferTimer);
            cachedDeferTimer = setTimeout(function () {
              var deferer = _this3.$schema.$validateAsync(key, _this3.get(key), _this3)([]);

              validatingQueue.push(deferer);
            }, 7);
            clearTimeout(cachedTimer);
            cachedTimer = setTimeout(function () {
              Promise.all(validatingQueue) // make sure use the latest errors
              .then(function (list) {
                return list.pop();
              }).then(setCachedErrors).finally(function () {
                validatingQueue.length = 0;
              });
            }, 15);
          } // bugfix: fallback, to make UI stable without twinkling


          var errors = _this3.$schema.$validate(key, _this3.get(key), _this3)([]); // when value change, notification will be send, so make it silent


          setCachedErrors(errors, true);
          cachedErrorsInited = true;
        };

        _this3.watch(key, updateCachedErrors, true);

        _this3.on('validate', function (keys) {
          return keys.includes(key) && updateCachedErrors();
        }); // recompute errors after invoke validate/validateAsync


        cachedErrorsUpdators[key] = updateCachedErrors; // NOTICE: it will be used to watch after all fields initialized

        Object.assign(viewDef, {
          key: {
            get: function get() {
              return key;
            },
            enumerable: true
          },
          value: {
            get: function get() {
              return _this3.get(key);
            },
            set: function set(value) {
              return _this3.set(key, value);
            },
            enumerable: true
          },
          errors: {
            get: function get() {
              // before we read `view.errors`, cachedErrors is empty
              // when first get errors, generate cachedErrors
              if (!cachedErrorsInited) {
                updateCachedErrors();
              }

              return cachedErrors;
            },
            set: setCachedErrors,
            enumerable: true
          },
          empty: {
            get: function get() {
              return _this3.$schema.empty(key, _this3.get(key), _this3);
            },
            enumerable: true
          },
          data: {
            get: function get() {
              return getData();
            },
            enumerable: true
          },
          text: {
            get: function get() {
              return _this3.$schema.format(key, _this3.getData(key), _this3);
            },
            enumerable: true
          },
          state: {
            get: function get() {
              var state = meta.state ? meta.state.call(_this3, key) : {};
              var keys = Object.keys(state);
              var proxy = new Proxy(state, {
                get: function get(_, key) {
                  return (0, _tsFns.inArray)(key, keys) ? _this3.get(key) : undefined;
                },
                set: function set(_, key, value) {
                  return (0, _tsFns.inArray)(key, keys) && _this3.set(key, value);
                }
              });
              return proxy;
            },
            enumerable: true
          },
          absKeyPath: {
            get: function get() {
              var absKeyPath = _this3.$absKeyPath;
              return [].concat((0, _toConsumableArray2.default)(absKeyPath), [key]);
            },
            enumerable: true
          },
          changed: {
            get: function get() {
              return changed;
            },
            set: function set(status) {
              // make sure the computed field value sync to data
              if (status && !meta[_constants.ComputeSymbol] && meta.compute) {
                _this3.$store.set(key, view.value, true);
              }

              var prev = changed;
              changed = !!status;

              if (prev !== changed) {
                _this3.$store.forceDispatch("!".concat(key, ".changed"), changed, prev);
              }
            },
            enumerable: true
          }
        });
        Object.defineProperties(view, viewDef);
        (0, _tsFns.define)(views, key, {
          get: function get() {
            if (_this3.$collection && _this3.$collection.enable && _this3.$collection.views) {
              _this3.$collection.items.push(["!".concat(key)]);
            }

            return view;
          },
          enumerable: true
        });
      });
      (0, _tsFns.define)(this, '$views', views);
      (0, _tsFns.define)(this.$views, '$validatings', function () {
        var queues = Object.values(validatingQueues);
        var deferers = (0, _tsFns.flatArray)(queues);
        return Promise.all(deferers);
      }); // create errors, so that is's easy and quick to know the model's current status

      (0, _tsFns.define)(this.$views, '$errors', function () {
        var errors = [];
        (0, _tsFns.each)(views, function (view) {
          errors.push.apply(errors, (0, _toConsumableArray2.default)(view.errors));
        });
        return (0, _utils.makeMsg)(errors);
      }); // create changed, so that it's easy to find out whether the data has changed

      (0, _tsFns.define)(this.$views, '$changed', {
        get: function get() {
          return _this3.collect(function () {
            return keys.some(function (key) {
              return _this3.$views[key].changed;
            });
          }, true);
        },
        set: function set(status) {
          return _this3.collect(function () {
            return keys.forEach(function (key) {
              return _this3.$views[key].changed = status;
            });
          }, true);
        }
      }); // create $state, so that it's easy to read state from $views

      (0, _tsFns.define)(this.$views, '$state', function () {
        var state = _this3._combineState();

        var keys = Object.keys(state);
        var output = {};
        keys.forEach(function (key) {
          (0, _tsFns.define)(output, key, {
            enumerable: true,
            get: function get() {
              return _this3[key];
            },
            set: function set(value) {
              return _this3[key] = value;
            }
          });
        });
        return output;
      }); // init data

      this._initData(data); // make meta.accept work


      reacceptors.forEach(function (acceptor) {
        var key = acceptor.key,
            accept = acceptor.accept;

        var value = _this3.daemon(accept, function (value) {
          _this3.set(key, value);
        }, false, true);

        _this3.reset(key, value);
      }); // make meta.activate work

      reactivators.forEach(function (activator) {
        var key = activator.key,
            activate = activator.activate;

        var value = _this3.daemon(activate, function (value) {
          _this3.set(key, value, true);
        });

        _this3.reset(key, value);
      }); // register a global listener to watch all changes

      this.watch('*', function (e) {
        var key = e.key,
            compute = e.compute; // root: changed field key

        var root = key[0]; // changed meta

        var def = _this3.$schema[root];
        var fields = Object.keys(_this3.$schema);

        var notifyStateToField = function notifyStateToField(field) {
          var state = _this3.$schema[field].state;

          if (!state) {
            return;
          }

          var stateObj = state.call(_this3, field);
          var stateKeys = Object.keys(stateObj);

          if (stateKeys.includes(root)) {
            _this3.$store.forceDispatch("!".concat(field), "by state ".concat(root));
          }
        };

        if (!def) {
          // if a state changed, should notify fields which depend on it
          if (root in _this3.$$state) {
            fields.forEach(function (field) {
              notifyStateToField(field);
            });

            _this3.onChange(root);
          }

          return;
        } // disable for private properties


        if ((0, _tsFns.inArray)(key[key.length - 1][0], ['$', '_'])) {
          return;
        } // response for def.watch attribute


        if ((0, _tsFns.isFunction)(def.watch)) {
          def.watch.call(_this3, e, root);
        }

        fields.forEach(function (field) {
          if (root === field) {
            return;
          }

          var meta = _this3.$schema[root];
          var _this3$$schema$field = _this3.$schema[field],
              follow = _this3$$schema$field.follow,
              needs = _this3$$schema$field.needs,
              deps = _this3$$schema$field.deps;

          if ((0, _tsFns.isFunction)(follow)) {
            var items = follow.call(_this3, e, field, root);

            if ((0, _tsFns.isArray)(items)) {
              items.forEach(function (item) {
                var meta = item.meta,
                    _item$key = item.key,
                    key = _item$key === void 0 ? meta : _item$key,
                    action = item.action;

                _this3.use(key, function (view) {
                  if (view.key === root) {
                    action.call(_this3, view.value, root);
                  }
                });
              });
            }
          }

          if (needs) {
            var needMetas = needs.call(_this3, field);

            if (needMetas.some(function (item) {
              return isMatchMeta(meta, item);
            })) {
              _this3.$store.forceDispatch("!".concat(field), "needs ".concat(root)); // after dependencies changed, errors should be recompute


              var triggerForErrors = cachedErrorsUpdators[field];
              triggerForErrors();
            }
          }

          if (deps) {
            var depMap = deps.call(_this3, field);

            if (depMap[root]) {
              _this3.$store.forceDispatch("!".concat(field), "depends on ".concat(root)); // after dependencies changed, errors should be recompute


              var _triggerForErrors = cachedErrorsUpdators[field];

              _triggerForErrors();
            }
          }

          notifyStateToField(field);
        });
        /**
         * factors need to traverse to deep subm models
         */

        var traverseToImpact = function traverseToImpact(model) {
          var $this = _this3;

          function walk(model, key) {
            var subs = model[key];

            if ((0, _tsFns.isArray)(subs)) {
              subs.forEach(impact);
            } else {
              impact(subs);
            }
          }

          function impact(sub) {
            if (!(0, _tsFns.isInstanceOf)(sub, Model)) {
              return;
            }

            var schema = sub.$schema;
            var keys = Object.keys(schema);
            keys.forEach(function (key) {
              var meta = schema[key];

              if ((0, _tsFns.isInstanceOf)(meta, _factory.FactoryMeta)) {
                walk(sub, key);
              } else {
                var factors = meta.factors;

                if (!factors) {
                  return;
                }

                var items = factors.call($this, key);
                var matched = items.find(function (item) {
                  return isMatchMeta(def, item);
                });

                if (matched) {
                  sub.$store.forceDispatch("!".concat(key), 'by factors', matched);
                }
              }
            });
          }

          impact(model);
        };

        traverseToImpact(_this3); // check $parent

        _this3._ensure(root); // modify view.changed


        if (!compute) {
          _this3.collect(function () {
            _this3.$views[root].changed = true;
          }, true);
        }

        if (_this3.$$deps[root]) {
          var _this3$$$deps$root = _this3.$$deps[root],
              deps = _this3$$$deps$root.deps,
              fn = _this3$$$deps$root.fn;
          deps.forEach(function (dep) {
            _this3.unwatch(dep, fn);
          });
        }

        _this3.onChange(root);
      }, true); // propagate recover with force dispatch

      this.on('recover', function () {
        if (_this3.$parent) {
          _this3.$parent.$store.forceDispatch(_this3.$keyPath, 'sub recover');
        }
      });
      keys.forEach(function (key) {
        var meta = _this3.$schema[key]; // invoke `init` attribute

        if (meta.init) {
          meta.init.call(_this3, key);
        }
      });
    }
  }, {
    key: "_initData",
    value: function _initData(data) {
      this.$initing = true;
      this.Chunk().fromJSON(data);
      delete this.$initing;
    }
  }, {
    key: "_initState",
    value: function _initState() {
      var _this4 = this;

      var output = {};
      var state = this.$$state;

      var combine = function combine(state) {
        (0, _tsFns.each)(state, function (descriptor, key) {
          if ((0, _tsFns.inObject)('value', descriptor) && (0, _utils.isAsyncRef)(descriptor.value)) {
            var value = descriptor.value;
            var current = value.current,
                attach = value.attach;
            output[key] = current;

            if (_this4.$ready) {
              return;
            } // async set attr value


            attach(key).then(function (next) {
              if (_this4[key] === next) {
                return;
              }

              if (_this4[key] && (0, _tsFns.refineProxy)(_this4[key]) === next) {
                return;
              }

              _this4[key] = next; // will trigger watcher

              _this4.$store.runSilent(function () {
                _this4.$views[key].changed = false;
              });
            });
            return;
          }

          (0, _tsFns.define)(output, key, _objectSpread(_objectSpread({}, descriptor), {}, {
            enumerable: true,
            configurable: true
          }));
        }, true);
      };

      (0, _tsFns.each)(this.$schema, function (meta, key) {
        if (!meta.state) {
          return;
        }

        var metaState = meta.state.call(_this4, key);

        if (metaState) {
          combine(metaState);
        }
      }); // this.state has higher priority

      combine(state);
      return output;
    }
  }, {
    key: "_combineState",
    value: function _combineState() {
      var _this5 = this;

      var basicState = this.$$state;
      var metasState = {};
      (0, _tsFns.each)(this.$schema, function (meta, key) {
        if (!meta.state) {
          return;
        }

        var metaState = meta.state.call(_this5, key);

        if (metaState) {
          Object.assign(metasState, metaState);
        }
      });

      var combinedState = _objectSpread(_objectSpread({}, metasState), basicState);

      var keys = Object.keys(combinedState);
      var state = {};
      keys.forEach(function (key) {
        state[key] = _this5[key];
      });
      return state;
    }
    /**
     * reset property value to be default value
     * @param {*} key
     * @returns
     */

  }, {
    key: "reset",
    value: function reset(key, value) {
      var _this6 = this;

      if (!key) {
        return this;
      }

      if ((0, _tsFns.isInstanceOf)(key, _meta.Meta) || (0, _tsFns.isInheritedOf)(key, _meta.Meta)) {
        key = this.use(key, function (view) {
          return view.key;
        });
      }

      if ((0, _tsFns.isUndefined)(value)) {
        value = this.$schema.getDefault(key, this);
      }

      this.collect(function () {
        _this6.set(key, value, true);

        _this6.use(key, function (view) {
          view.changed = false;
        });
      }, true); // reset compute

      var def = this.$schema[key];

      if (def[_constants.ComputeSymbol]) {
        def.compute = def[_constants.ComputeSymbol];
        delete def[_constants.ComputeSymbol];
      }

      this.emit('reset');
      return this;
    }
    /**
     * only change data of this, without `create` and parse, without dispatch, reset `changed`
     * notice: only properties those which in this will be changed
     * @param {*} data
     * @returns
     */

  }, {
    key: "patch",
    value: function patch(data) {
      var _this7 = this;

      if (!this.$store.editable) {
        return this;
      }

      this.$store.runSilent(function () {
        // reset changed, make sure changed=false after recompute
        _this7.collect(function () {
          var keys = Object.keys(data);
          keys.forEach(function (key) {
            if (!(0, _tsFns.inObject)(key, _this7)) {
              return;
            }

            var value = data[key];
            _this7[key] = value;

            if (_this7.$views[key]) {
              _this7.$views[key].changed = false;
            }
          });
        }, true);
      });
      this.emit('patch');
      return this;
    }
    /**
     * reset and cover all data, original model will be clear first, and will use new data to cover the whole model.
     * notice that, properties which are in original model be not in schema may be removed.
     * @param {*} data
     */

  }, {
    key: "restore",
    value: function restore(data) {
      var _this8 = this;

      if (!this.$store.editable) {
        return this;
      }

      var schema = this.$schema;

      var state = this._initState();

      var params = {};
      var keys = {}; // keys of state and schema and this
      // patch state

      (0, _tsFns.each)(state, function (descriptor, key) {
        if ((0, _tsFns.inObject)(key, data)) {
          var desc = Object.getOwnPropertyDescriptor(data, key);
          Object.defineProperty(params, key, desc);
        } else {
          Object.defineProperty(params, key, descriptor);
        }

        keys[key] = !0; // redefine state here so that we can invoke this.state() only once when initialize

        (0, _tsFns.define)(_this8, key, {
          get: function get() {
            return _this8.get(key);
          },
          set: function set(value) {
            return _this8.set(key, value);
          },
          enumerable: true,
          configurable: true
        });
      }, true); // patch fields

      var ensure = function ensure(value, key) {
        if ((0, _tsFns.isArray)(value)) {
          value.forEach(function (item) {
            return ensure(item, key);
          });
        } else if ((0, _tsFns.isInstanceOf)(value, Model)) {
          value._setParent([_this8, key]);
        }
      };

      var asyncReactors = {};
      (0, _tsFns.each)(schema, function (meta, key) {
        var push = function push(value) {
          if (!(0, _utils.isAsyncRef)(value)) {
            params[key] = value;
            ensure(value, key);
            return;
          }

          var attach = value.attach,
              deps = value.deps,
              current = value.current;

          var get = function get() {
            return attach(key, 'default').then(function (next) {
              if (_this8[key] === next) {
                return;
              }

              if (_this8[key] && (0, _tsFns.refineProxy)(_this8[key]) === next) {
                return;
              }

              _this8[key] = next; // will trigger watcher

              _this8.$store.runSilent(function () {
                _this8.$views[key].changed = false;
              });
            });
          };

          params[key] = current;
          ensure(current, key);
          get();

          if ((0, _tsFns.isArray)(deps)) {
            deps.map(function (dep) {
              if ((0, _tsFns.isInstanceOf)(dep, _meta.Meta) || (0, _tsFns.isInheritedOf)(dep, _meta.Meta)) {
                return _this8.use(dep).key;
              }

              return dep;
            }).forEach(function (dep) {
              asyncReactors[dep] = asyncReactors[dep] || [];
              asyncReactors[dep].push(get);
            });
          }
        };

        keys[key] = !0;

        if ((0, _tsFns.inObject)(key, data)) {
          // disable compute ability
          if (!meta[_constants.ComputeSymbol] && meta.compute) {
            meta[_constants.ComputeSymbol] = meta.compute;
            delete meta.compute;
          } // use the value directly


          var value = data[key];
          push(value);
        } else {
          // recover compute
          if (meta[_constants.ComputeSymbol]) {
            meta.compute = meta[_constants.ComputeSymbol];
            delete meta[_constants.ComputeSymbol];
          } // notice here, we call this in default(), we can get passed state properties


          var _value4 = schema.getDefault(key, _this8);

          push(_value4);
        }
      });
      Object.keys(asyncReactors).forEach(function (key) {
        _this8.watch(key, function (_ref2) {
          var prev = _ref2.prev,
              next = _ref2.next;

          if (prev === next) {
            return;
          }

          var fns = asyncReactors[key];
          fns.forEach(function (fn) {
            return fn();
          });
        }, true);
      }); // reset into store

      var initParams = this.onSwitch(params);
      this.emit('switch', initParams);
      this.$store.init(initParams); // delete the outdate properties,
      // these properties may be pended by manully by `define`, we should clear them to reset the model

      (0, _tsFns.each)(this.$store.state, function (_, key) {
        if (keys[key]) {
          return;
        } // disable for private properties


        if ((0, _tsFns.inArray)(key[0], ['$', '_'])) {
          return;
        }

        _this8.$store.del(key);

        delete _this8[key];
      }, true); // reset changed

      this.$views.$changed = false; // dependencies collection
      // after onRestore, so that developers can do some thing before collection

      (0, _tsFns.each)(schema, function (meta, key) {
        if (!meta[_constants.ComputeSymbol] && meta.compute) {
          _this8.getData(key);
        }
      }); // patch those which are not in store but on `this`
      // should must be after previous delete

      this.$store.runSilent(function () {
        (0, _tsFns.each)(data, function (value, key) {
          if (keys[key]) {
            return;
          } // cover those which are on this


          if ((0, _tsFns.inObject)(key, _this8)) {
            _this8[key] = value;
          }
        });
      });
      this.onRestore();
      this.emit('restore', keys);
      return this;
    }
    /**
     * get field value, with formatting by `getter`
     * @param {array|string} keyPath
     */

  }, {
    key: "get",
    value: function get(keyPath) {
      var chain = (0, _tsFns.isArray)(keyPath) ? (0, _toConsumableArray2.default)(keyPath) : (0, _tsFns.makeKeyChain)(keyPath);
      var key = chain.shift();
      var value = this.getData(key);
      var transformed = this.$schema.get(key, value, this);
      var output = (0, _tsFns.parse)(transformed, chain);
      return output;
    }
    /**
     * @param {*} type 0: start, 1: end
     */

  }, {
    key: "collect",
    value: function collect(collector, inverse) {
      var _this9 = this;

      if ((0, _tsFns.isFunction)(collector)) {
        if (inverse === true) {
          // if true, do not collect deps, and return normal value of collector fn
          if (this.$collection) {
            this.$collection.enable = false;
          }

          var res = collector();

          if (this.$collection) {
            this.$collection.enable = true;
          }

          return res;
        } // if function, return value of collector, and exec inverse with deps


        if ((0, _tsFns.isFunction)(inverse)) {
          this.collect();

          var _res = collector();

          var deps = this.collect(true);
          inverse(deps);
          return _res;
        } // if false, do fn, return deps


        this.collect();
        collector();
        return this.collect(true);
      }

      var summarize = function summarize() {
        var items = _this9.$collection.items;
        var records = [];
        items.forEach(function (_ref3) {
          var _ref4 = (0, _slicedToArray2.default)(_ref3, 2),
              keyPath = _ref4[0],
              value = _ref4[1];

          var key = (0, _tsFns.isArray)(keyPath) ? (0, _tsFns.makeKeyPath)(keyPath, true) : keyPath;
          records.push({
            key: key,
            value: value
          });

          if ((0, _tsFns.isInstanceOf)(value, Model)) {
            var _deps = value.collect(true);

            var keys = _deps.map(function (dep) {
              var chain = (0, _tsFns.makeKeyChain)(dep);
              var key = (0, _tsFns.makeKeyPath)([].concat((0, _toConsumableArray2.default)(keyPath), (0, _toConsumableArray2.default)(chain)), true);
              return key;
            });

            keys.forEach(function (key) {
              records.push({
                key: key,
                subof: value
              });
            });
          }
        });
        var res = [];

        var push = function push(key, value) {
          if (!(0, _tsFns.isFunction)(value) && !res.includes(key) && key.split('.').pop() !== 'length') {
            res.push(key);
          }
        };

        var prev = null;
        records.forEach(function (record, i) {
          if (i === 0 && i !== records.length - 1) {
            prev = record;
            return;
          }

          var key = record.key,
              value = record.value,
              subof = record.subof;

          if (prev) {
            var _prev = prev,
                prevKey = _prev.key,
                prevValue = _prev.value;

            if (subof && subof !== prevValue) {
              push(prevKey, prevValue);
            } else if (prevKey === key) {
              push(prevKey, prevValue);
            } else if (key.indexOf(prevKey) !== 0) {
              push(prevKey, prevValue);
            }
          }

          if (i === records.length - 1) {
            push(key, value);
          }

          prev = record;
        });
        return res;
      }; // end, clear, and return deps


      if (collector === true) {
        var collection = this.$collection;

        if (!collection) {
          return [];
        }

        var keys = summarize();
        var _timer = collection.timer;
        clearTimeout(_timer);
        delete this.$collection;
        return keys;
      } // dont start collect again


      if (this.$collection) {
        return summarize;
      } // start collecting


      var items = [];
      var timer = setTimeout(function () {
        return _this9.collect(true);
      }, 32);
      (0, _tsFns.define)(this, '$collection', {
        value: {
          items: items,
          // clear automaticly to free memory
          timer: timer,
          enable: true,
          views: collector && (0, _typeof2.default)(collector) === 'object' ? collector.views : false,
          fields: collector && (0, _typeof2.default)(collector) === 'object' && 'fields' in collector ? collector.fields : true
        },
        writable: false,
        configurable: true
      }); // end before timeout

      return summarize;
    }
    /**
     * get a view of field by its keyPath
     * @param {*} keyPath
     * @param {function} fn when keyPath find a view, invoke callback with the view and return as `fn` result
     */

  }, {
    key: "use",
    value: function use(keyPath, fn) {
      if (keyPath && ((0, _tsFns.isInstanceOf)(keyPath, _meta.Meta) || (0, _tsFns.isInheritedOf)(keyPath, _meta.Meta))) {
        var keys = Object.keys(this.$schema);

        for (var i = 0, len = keys.length; i < len; i++) {
          var _key2 = keys[i];
          var meta = this.$schema[_key2];

          if (isMatchMeta(meta, keyPath)) {
            var view = this.$views[_key2];
            return view && (0, _tsFns.isFunction)(fn) ? fn.call(this, view) : view;
          }
        }

        return;
      }

      var chain = (0, _tsFns.isArray)(keyPath) ? (0, _toConsumableArray2.default)(keyPath) : (0, _tsFns.makeKeyChain)(keyPath);
      var key = chain.pop();

      if (!chain.length) {
        var _view = this.$views[key];
        return _view && (0, _tsFns.isFunction)(fn) ? fn.call(this, _view) : _view;
      }

      var target = (0, _tsFns.parse)(this, chain);

      if ((0, _tsFns.isInstanceOf)(target, Model)) {
        var _view2 = target.$views[key];
        return _view2 && (0, _tsFns.isFunction)(fn) ? fn.call(this, _view2) : _view2;
      }
    }
    /**
     * use a meta definition to find out view
     * @param {Meta} Meta
     * @param {function} [fn] key => any
     * @returns {view}
     * @deprecated
     * @example
     * class Some extends Meta {
     *   ...
     * }
     * class Dog extends Meta {
     *   drop() {
     *     const some = this.reflect(Some) || {} // undefined if Some not used
     *     return some.value
     *   }
     * }
     */

  }, {
    key: "reflect",
    value: function reflect(Meta, fn) {
      return this.use(Meta, fn);
    }
    /**
     * set field value, with `readonly`, `type` checking, and formatting by `setter`
     * @param {array|string} keyPath
     * @param {*} next
     * @param {boolean} force force set, ignore `readonly`
     */

  }, {
    key: "set",
    value: function set(keyPath, next, force) {
      if (!this.$store.editable) {
        return (0, _tsFns.parse)(this, keyPath);
      }

      var chain = (0, _tsFns.isArray)(keyPath) ? (0, _toConsumableArray2.default)(keyPath) : (0, _tsFns.makeKeyChain)(keyPath);
      var key = chain.shift(); // deep set

      if (chain.length) {
        var current = this.$store.get(key);
        var cloned = (0, _tsFns.clone)(current);
        (0, _tsFns.assign)(cloned, chain, next);
        next = cloned;
      }

      var meta = this.$schema[key];

      if (!meta) {
        return this.define(key, next);
      }

      this._check(key);

      var prev = this.$store.get(key);
      var value = force ? this.$schema.$set(key, next, this) : this.$schema.set(key, next, prev, this);

      if (prev !== value && this.$views[key]) {
        this.$views[key].changed = true;
      }

      var coming = this.$store.set(key, value); // BREAKING CHANGE
      // drop the ability of compute, after change vlaue manully, the field will be a normal field

      if (!meta[_constants.ComputeSymbol] && meta.compute) {
        meta[_constants.ComputeSymbol] = meta.compute;
        delete meta.compute;
      }

      this.emit('set', keyPath, coming, prev);
      return coming;
    }
  }, {
    key: "update",
    value: function update(data) {
      var _this10 = this;

      if (!this.$store.editable) {
        return this;
      }

      (0, _tsFns.each)(data, function (value, key) {
        // only update existing props, ignore those which are not on model
        // this shakes affects by over-given props
        if ((0, _tsFns.inObject)(key, _this10)) {
          _this10[key] = value;
        }
      });
      this.emit('update');
      return this;
    }
  }, {
    key: "define",
    value: function define(key, value) {
      var _this11 = this;

      if (!this.$store.editable) {
        return (0, _tsFns.parse)(this, key);
      }

      if (this.$schema[key]) {
        return this[key];
      } // delete this key


      if ((0, _tsFns.isUndefined)(value)) {
        delete this[key];
        this.$store.del(key);
        return;
      }

      var def = {
        get: function get() {
          return _this11.$store.get(key);
        },
        configurable: true,
        enumerable: true
      };

      if (!(0, _tsFns.isFunction)(value)) {
        def.set = function (value) {
          return _this11.$store.set(key, value);
        };
      }

      Object.defineProperty(this, key, def);
      var coming = (0, _tsFns.isFunction)(value) ? this.$store.define(key, value) : this.$store.set(key, value);
      this.emit('define', key, value);
      return coming;
    }
    /**
     * create a daemon runner for a computed value
     * @param {function} getter the compute function to get the value
     * @param {function} runner the runner to run when dependencies change
     * @param {boolean} [immediate] whether to run runner immediately, if true, runner will be run before result returned
     * @param {boolean} [silent] whether to run runner silently
     */

  }, {
    key: "daemon",
    value: function daemon(getter, runner, immediate, silent) {
      var _this12 = this;

      var run = function run(value) {
        if (silent) {
          _this12.$store.runSilent(function () {
            runner.call(_this12, value);
          });
        } else {
          runner.call(_this12, value);
        }
      };

      var create = function create() {
        var next = _this12.collect(function () {
          return getter.call(_this12);
        }, function (deps) {
          deps.forEach(function (dep) {
            var watcher = function watcher(_ref5) {
              var prev = _ref5.prev,
                  next = _ref5.next;

              if (prev === next) {
                return;
              }

              var value = create();
              run(value);
            };

            _this12.watch(dep, watcher, true, true);
          });
        });

        return next;
      };

      var next = create();

      if (immediate) {
        run(next);
      }

      return next;
    }
  }, {
    key: "watch",
    value: function watch(key, fn, deep, once) {
      if ((0, _tsFns.isInstanceOf)(key, _meta.Meta) || (0, _tsFns.isInheritedOf)(key, _meta.Meta)) {
        key = this.use(key, function (view) {
          return view.key;
        });
      }

      if (!key) {
        return this;
      }

      this.$store.watch(key, fn, deep, once);
      return this;
    }
  }, {
    key: "unwatch",
    value: function unwatch(key, fn) {
      if ((0, _tsFns.isInstanceOf)(key, _meta.Meta) || (0, _tsFns.isInheritedOf)(key, _meta.Meta)) {
        key = this.use(key, function (view) {
          return view.key;
        });
      }

      this.$store.unwatch(key, fn);
      return this;
    }
  }, {
    key: "validate",
    value: function validate(key) {
      var _this13 = this;

      var errs = this.onCheck(key) || [];
      this.emit('check', key, errs);

      var validate = function validate(key, emit) {
        _this13._check(key, true);

        var value = _this13.get(key);

        var outs = (0, _tsFns.decideby)(function () {
          // check the given meta validators at first
          var outs = _this13.$schema.validate(key, value, _this13);

          if ((0, _tsFns.isArray)(value) && !value.some(function (item) {
            return !(0, _tsFns.isInstanceOf)(item, Model);
          }) && !_this13.$schema.disabled(key, value, _this13)) {
            var suberrs = value.map(function (model) {
              return model.validate();
            });
            suberrs.forEach(function (items, i) {
              items.forEach(function (item) {
                item.key = (0, _tsFns.makeKeyPath)([].concat((0, _toConsumableArray2.default)(_this13.$absKeyPath), [key, i, item.key]));
                outs.push(item);
              });
            });
            return outs;
          }

          if (value && (0, _tsFns.isInstanceOf)(value, Model) && !_this13.$schema.disabled(key, value, _this13)) {
            var items = value.validate();
            items.forEach(function (item) {
              item.key = (0, _tsFns.makeKeyPath)([].concat((0, _toConsumableArray2.default)(_this13.$absKeyPath), [key, item.key]));
              outs.push(item);
            });
            return outs;
          }

          return outs;
        });
        var errors = [].concat((0, _toConsumableArray2.default)(errs), (0, _toConsumableArray2.default)(outs));

        if (emit) {
          _this13.emit('validate', [key], errors);
        }

        return (0, _utils.makeMsg)(errors);
      }; // validate all properties once together


      if (!key || (0, _tsFns.isArray)(key)) {
        var keys = !key ? Object.keys(this.$schema) : key;
        var groups = keys.map(function (key) {
          return validate(key);
        });
        var errors = [].concat((0, _toConsumableArray2.default)(errs), (0, _toConsumableArray2.default)((0, _tsFns.flatArray)(groups)));
        this.emit('validate', keys, errors);
        return (0, _utils.makeMsg)(errors);
      }

      return validate(key, true);
    }
  }, {
    key: "validateAsync",
    value: function validateAsync(key) {
      var _this14 = this;

      var errs = this.onCheck(key) || [];
      this.emit('check', errs);

      var validate = function validate(key, emit) {
        _this14._check(key, true);

        var value = _this14.get(key);

        var defer = (0, _tsFns.decideby)(function () {
          // check the given meta validators at first
          return _this14.$schema.validateAsync(key, value, _this14).then(function (preouts) {
            var outs = (0, _toConsumableArray2.default)(preouts);

            if ((0, _tsFns.isArray)(value) && !value.some(function (item) {
              return !(0, _tsFns.isInstanceOf)(item, Model);
            }) && !_this14.$schema.disabled(key, value, _this14)) {
              var subdefers = value.map(function (model) {
                return model.validateAsync();
              });
              return Promise.all(subdefers).then(function (suberrs) {
                suberrs.forEach(function (items, i) {
                  items.forEach(function (item) {
                    item.key = (0, _tsFns.makeKeyPath)([].concat((0, _toConsumableArray2.default)(_this14.$absKeyPath), [key, i, item.key]));
                    outs.push(item);
                  });
                });
                return outs;
              });
            } else if (value && (0, _tsFns.isInstanceOf)(value, Model) && !_this14.$schema.disabled(key, value, _this14)) {
              return value.validateAsync().then(function (items) {
                items.forEach(function (item) {
                  item.key = (0, _tsFns.makeKeyPath)([].concat((0, _toConsumableArray2.default)(_this14.$absKeyPath), [key, item.key]));
                  outs.push(item);
                });
                return outs;
              });
            }

            return outs;
          });
        });
        return defer.then(function (outs) {
          var errors = [].concat((0, _toConsumableArray2.default)(errs), (0, _toConsumableArray2.default)(outs));

          if (emit) {
            _this14.emit('validate', [key], errors);
          }

          return (0, _utils.makeMsg)(errors);
        });
      }; // validate all properties once together


      if (!key || (0, _tsFns.isArray)(key)) {
        var keys = !key ? Object.keys(this.$schema) : key;
        return Promise.all(keys.map(function (key) {
          return validate(key);
        })).then(function (groups) {
          var errors = [].concat((0, _toConsumableArray2.default)(errs), (0, _toConsumableArray2.default)((0, _tsFns.flatArray)(groups)));

          _this14.emit('validate', keys, errors);

          return (0, _utils.makeMsg)(errors);
        });
      }

      return validate(key, true);
    }
  }, {
    key: "getData",
    value: function getData(key) {
      var _this15 = this;

      var value = this.$store.get(key);
      var meta = this.$schema[key];
      var view = this.collect(function () {
        return _this15.$views[key];
      }, true); // if value is changed manully, we will use changed value

      if (view && view.changed) {
        return value;
      } // if value is not changed, we will use computed value
      else if (meta && !meta[_constants.ComputeSymbol] && meta.compute) {
        // clear all previous dependencies' watchers
        var depent = this.$$deps[key];

        if (depent && depent.deps && depent.deps.length) {
          var fn = depent.fn,
              _deps2 = depent.deps;

          _deps2.forEach(function (key) {
            _this15.unwatch(key, fn);
          });
        } // find out new deps


        this.collect();
        var res = (0, _utils.tryGet)(function () {
          return meta.compute.call(_this15);
        }, value);
        var deps = this.collect(true); // subscribe new watchers

        if (deps.length) {
          var _fn = function _fn() {
            var prev = res;

            var value = _this15.$store.get(key);

            var next = (0, _utils.tryGet)(function () {
              return meta.compute.call(_this15);
            }, value);

            _this15.$store.dispatch(key, {
              value: next,
              next: next,
              prev: prev,
              active: next,
              invalid: prev,
              compute: true
            });
          };

          deps.forEach(function (key) {
            _this15.watch(key, _fn, true);
          });
          this.$$deps[key] = {
            deps: deps,
            fn: _fn
          };
        } // make sure the computed field value sync to data


        this.$store.set(key, res, true);
        return res;
      } else {
        return value;
      }
    }
  }, {
    key: "_bundleData",
    value: function _bundleData() {
      var _this16 = this;

      var data = this.$store.data;
      var schema = this.$schema;
      var views = this.$views;
      var computed = {};
      (0, _tsFns.each)(schema, function (meta, key) {
        if (_this16.collect(function () {
          return views[key] && views[key].changed;
        }, true)) {
          return;
        }

        if (!meta[_constants.ComputeSymbol] && meta.compute) {
          var value = (0, _utils.tryGet)(function () {
            return meta.compute.call(_this16);
          }, data[key]);
          computed[key] = value;
        }
      });
      return _objectSpread(_objectSpread({}, data), computed);
    }
  }, {
    key: "Chunk",
    value: function Chunk(chunk) {
      var _this17 = this;

      var Constructor = (0, _tsFns.getConstructorOf)(this);
      chunk = chunk || Constructor.Chunk;
      var isChunk = chunk && (0, _tsFns.isInstanceOf)(chunk, _factory.FactoryChunk);

      var checkType = function checkType(data, message) {
        if (isChunk && chunk.type && (0, _tsFns.isInstanceOf)(chunk.type, _type.Type)) {
          chunk.type.trace(data).catch(function (e) {
            console.error(message, e, data, chunk);
          });
        }
      };

      var fromJSON = function fromJSON(data) {
        if (isChunk && chunk.fromJSON) {
          var json = chunk.fromJSON(data);

          _this17.fromJSON(json);

          return;
        }

        checkType(data, 'chunk.fromJSON');

        _this17.fromJSON(data);
      };

      return {
        fromChunk: function fromChunk() {
          if (isChunk) {
            var _chunk;

            return Promise.resolve((_chunk = chunk).data.apply(_chunk, arguments)).then(fromJSON);
          }

          return Promise.reject(new Error('chunk is not a FactoryChunk.'));
        },
        fromJSON: fromJSON,
        toData: function toData() {
          if (isChunk && chunk.toData) {
            var res = chunk.toData(_this17);
            var result = _this17.onExport(res) || res;

            _this17.emit('export', result);

            return result;
          }

          return _this17.toData();
        },
        toJSON: function toJSON() {
          if (isChunk && chunk.toJSON) {
            var res = chunk.toJSON(_this17);
            var result = _this17.onRecord(res) || res;

            _this17.emit('record', result);

            checkType(result, 'chunk.toJSON');
            return result;
          }

          return _this17.toJSON();
        }
      };
    }
    /**
     * use schema `create` option to generate and restore data
     * @param {*} json
     */

  }, {
    key: "fromJSON",
    value: function fromJSON(json) {
      var _this18 = this;

      if (!this.$store.editable) {
        return this;
      } // prepare for sub models


      this.$children = []; // !!! required, although state will be patch twice, it is required to be set at the first
      // patch state into this, so that we can get passed state in default()
      // dont be worried about reactive, the properties will be override by restore()
      // this make state getter may throw error when the Model inistalize at the first time because of non generated fields
      // so developers should must ensure every situations

      var state = this._initState();

      var patches = (0, _tsFns.map)(state, function (value, key) {
        if ((0, _tsFns.inObject)(key, json)) {
          return json[key];
        } else {
          return value;
        }
      });
      Object.assign(this, patches); // when new Model, onParse may throw error

      var entry = (0, _utils.tryGet)(function () {
        return _this18.onParse(json) || json;
      }, json);
      this.emit('parse', entry);
      var data = this.$schema.parse(entry, this);

      var next = _objectSpread(_objectSpread({}, entry), data); // if computed properties are not passed in json,
      // we should remove theme from `next`, so that computed properties will be generated automaticly in `restore`
      // if we do not do this, the compute ability will lose because of `restore` logic


      (0, _tsFns.each)(this.$schema, function (def, key) {
        var asset = def.asset,
            compute = def.compute;
        var dataKey = asset ? (0, _tsFns.isFunction)(asset) ? asset(entry, key) : asset : key;

        if ((compute || def[_constants.ComputeSymbol]) && !(0, _tsFns.inObject)(dataKey, entry)) {
          delete next[key];
        }
      });
      this.restore(next); // ask children to recompute computed properties

      this.$children.forEach(function (child) {
        return child.onRegress();
      }); // we do not need $children

      delete this.$children; // reset changed, make sure changed=false after recompute

      this.$views.$changed = false;
      this.emit('fromJSON');
      this.emit('recover');
      return this;
    }
  }, {
    key: "toJSON",
    value: function toJSON() {
      this._check();

      var data = (0, _tsFns.clone)(this._bundleData()); // original data format

      var output = this.$schema.record(data, this);

      var state = this._combineState();

      var res = _objectSpread(_objectSpread({}, state), output);

      var result = this.onRecord(res) || res;
      this.emit('record', result);
      return result;
    }
    /**
     * update model by passing data, which will use schema `create` attribute to generate value, without dispatch, reset `changed`
     * @param {*} data
     */

  }, {
    key: "fromJSONPatch",
    value: function fromJSONPatch(data) {
      if (!this.$store.editable) {
        return;
      } // prepare for sub models


      this.$children = [];
      var output = this.$schema.discover(data, this);
      this.patch(output); // ask children to recompute computed properties

      this.$children.forEach(function (child) {
        return child.onRegress();
      }); // we do not need $children

      delete this.$children;
      this.emit('fromJSONPatch');
      this.emit('recover');
      return this;
    }
  }, {
    key: "toData",
    value: function toData() {
      this._check();

      var data = _objectSpread({}, this); // value format


      var output = this.$schema.export(data, this);
      var result = this.onExport(output) || output;
      this.emit('export', result);
      return result;
    }
  }, {
    key: "toParams",
    value: function toParams(determine) {
      var data = this.Chunk().toData();

      var output = _factory.Factory.toParams(data, determine);

      return output;
    }
  }, {
    key: "toFormData",
    value: function toFormData(determine) {
      var data = this.Chunk().toData();

      var formdata = _factory.Factory.toFormData(data, determine);

      return formdata;
    }
  }, {
    key: "on",
    value: function on(hook, fn) {
      this.$$events.push({
        hook: hook,
        fn: fn
      });
      return this;
    }
  }, {
    key: "off",
    value: function off(hook, fn) {
      var _this19 = this;

      this.$$events.forEach(function (item, i) {
        if (hook === item.hook && ((0, _tsFns.isUndefined)(fn) || fn === item.fn)) {
          _this19.$$events.splice(i, 1);
        }
      });
      return this;
    }
  }, {
    key: "emit",
    value: function emit(hook) {
      var _this20 = this;

      for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key3 = 1; _key3 < _len; _key3++) {
        args[_key3 - 1] = arguments[_key3];
      }

      this.$$events.forEach(function (item) {
        var _item$fn;

        if (hook !== item.hook) {
          return;
        }

        (_item$fn = item.fn).call.apply(_item$fn, [_this20].concat(args));
      });
    } // when initialized

  }, {
    key: "onInit",
    value: function onInit() {} // before restore model datas

  }, {
    key: "onSwitch",
    value: function onSwitch(params) {
      return params;
    } // parse data before parse, should be override

  }, {
    key: "onParse",
    value: function onParse(data) {
      return data;
    } // by toJSON

  }, {
    key: "onRecord",
    value: function onRecord(data) {
      return data;
    } // serialize data after export, should be override

  }, {
    key: "onExport",
    value: function onExport(data) {
      return data;
    }
  }, {
    key: "onCheck",
    value: function onCheck() {}
  }, {
    key: "onError",
    value: function onError() {}
  }, {
    key: "onEnsure",
    value: function onEnsure() {}
  }, {
    key: "onRestore",
    value: function onRestore() {}
  }, {
    key: "onRegress",
    value: function onRegress() {}
  }, {
    key: "onChange",
    value: function onChange(_key) {}
  }, {
    key: "lock",
    value: function lock() {
      this.$store.editable = true;
      this.emit('lock');
    }
  }, {
    key: "unlock",
    value: function unlock() {
      this.$store.editable = false;
      this.emit('unlock');
    }
  }, {
    key: "_setParent",
    value: function _setParent(_ref6) {
      var _this21 = this;

      var _ref7 = (0, _slicedToArray2.default)(_ref6, 2),
          parent = _ref7[0],
          key = _ref7[1];

      if (this.$parent && this.$parent === parent && this.$keyPath && this.$keyPath[0] === key) {
        return this;
      }

      (0, _tsFns.define)(this, '$parent', {
        value: parent,
        writable: false,
        configurable: true
      });
      (0, _tsFns.define)(this, '$keyPath', {
        get: function get() {
          var field = parent[key];
          var res = parent.collect(function () {
            if ((0, _tsFns.isArray)(field)) {
              var index = field.indexOf(_this21);
              var keyPath = [key, index];

              if (index === -1) {
                keyPath.unshift(false);
              }

              return keyPath;
            } else {
              var _keyPath5 = [key];

              if (field !== _this21) {
                _keyPath5.unshift(false);
              }

              return _keyPath5;
            }
          }, true);
          return res;
        },
        configurable: true
      }); // record sub models

      if (parent.$children) {
        parent.$children.push(this);
      } // recompute depend on $parent
      else {
        this.onRegress();
      }

      return this;
    }
  }, {
    key: "_ensure",
    value: function _ensure(key) {
      var _this22 = this;

      var add = function add(value, key) {
        if ((0, _tsFns.isInstanceOf)(value, Model) && !value.$parent) {
          value._setParent([_this22, key]);

          value.onEnsure(_this22);
          value.emit('ensure', _this22);
        }
      };

      var use = function use(value, key) {
        if ((0, _tsFns.isArray)(value)) {
          value.forEach(function (item) {
            return add(item, key);
          });
        } else {
          add(value, key);
        }
      };

      var root = (0, _tsFns.isArray)(key) ? key[0] : key;
      var value = this.getData(root);
      use(value, key);
    }
  }, {
    key: "_check",
    value: function _check(key) {
      var _this23 = this;

      var isValidate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
      var schema = this.$schema;
      var keys = key ? [key] : Object.keys(schema);
      keys.forEach(function (key) {
        // dont check if disabled
        if (_this23.$schema.disabled(key, _this23.getData(key), _this23)) {
          return;
        }

        var def = schema[key];
        (0, _tsFns.each)(def, function (value, attr) {
          var str = '';

          if (attr === 'validators' && isValidate) {
            value.forEach(function (item) {
              (0, _tsFns.each)(item, function (value) {
                str += (0, _tsFns.isFunction)(value) ? value + '' : '';
              });
            });
          } else {
            str += (0, _tsFns.isFunction)(value) ? value + '' : '';
          }

          if (str.indexOf('this.$parent') > -1 && !_this23.$parent) {
            var e = {
              key: key,
              attr: attr,
              action: '_check $parent',
              message: "this.$parent is called in ".concat(attr, ", but current model has no $parent")
            };

            _this23.onError(e);

            _this23.emit('error', e);
          }
        });
      });
    }
  }, {
    key: "Edit",
    value: function Edit() {
      var $this = this;
      var Constructor = (0, _tsFns.getConstructorOf)(this);

      var Editor = /*#__PURE__*/function (_Constructor$Edit) {
        (0, _inherits2.default)(Editor, _Constructor$Edit);

        var _super3 = _createSuper(Editor);

        function Editor() {
          (0, _classCallCheck2.default)(this, Editor);
          return _super3.apply(this, arguments);
        }

        (0, _createClass2.default)(Editor, [{
          key: "init",
          value: function init(data) {
            // patch source
            (0, _tsFns.define)(this, '$$editof', {
              value: $this,
              configurable: true
            });
            (0, _get3.default)((0, _getPrototypeOf2.default)(Editor.prototype), "init", this).call(this, data);
          }
        }]);
        return Editor;
      }(Constructor.Edit());

      var data = this.Chunk().toJSON();
      var parent = this.$parent,
          $keyPath = this.$keyPath,
          scenes = this.$$scenes;
      var editor = new Editor(data, {
        scenes: scenes,
        parent: parent,
        key: $keyPath ? $keyPath[0] : void 0
      });
      this.emit('editor', editor);
      return editor;
    }
    /**
     * remember a value when using for getter in meta
     * @param {function} getter () => any
     * @param {function} [depend] (value: any) => any, value is the return value of `getter`
     * @param {function} compare (deps: any) => boolean, when `true` use memoried value, when `false` re-compute, deps is the return value of `depend`
     * @returns
     * @example
     * class SomeMeta extends Meta {
     *   static custom_value() {
     *     let count = 0
     *     return this.memo(
     *       () => this.reflect(SomeMeta).value + 12,
     *       (value) => {
     *         count ++
     *         return { value, count }
     *       },
     *       (deps) => deps.count < 10, // when count < 10, use memoried value for each time
     *     )
     *   }
     * }
     */

  }, {
    key: "memo",
    value: function memo(getter, compare, depend) {
      var memory = this.$$memories.find(function (item) {
        return item.getter === getter && item.compare === compare;
      });

      if (!memory) {
        var _value5 = getter.call(this);

        var _deps3 = depend ? depend.call(this, _value5) : null;

        this.$$memories.push({
          getter: getter,
          compare: compare,
          depend: depend,
          value: _value5,
          deps: _deps3
        });
        return _value5;
      }

      var prev = memory.value;
      var deps = memory.deps;
      var isEqual = compare.call(this, deps);

      if (isEqual) {
        return prev;
      }

      var value = getter.call(this);
      memory.value = value;
      memory.deps = depend ? depend.call(this, value) : null;
      return value;
    }
  }], [{
    key: "Edit",
    value: function Edit() {
      var Editor = (0, _edit.edit)(this);
      return Editor;
    }
  }, {
    key: "Scene",
    value: function Scene(sceneCode) {
      var Constructor = this;

      var SceneModel = /*#__PURE__*/function (_Constructor) {
        (0, _inherits2.default)(SceneModel, _Constructor);

        var _super4 = _createSuper(SceneModel);

        function SceneModel() {
          (0, _classCallCheck2.default)(this, SceneModel);
          return _super4.apply(this, arguments);
        }

        return (0, _createClass2.default)(SceneModel);
      }(Constructor);

      (0, _defineProperty2.default)(SceneModel, _constants.SceneCodesSymbol, (0, _tsFns.isArray)(sceneCode) ? sceneCode : [sceneCode]);
      return SceneModel;
    }
  }]);
  return Model;
}();

exports.Model = Model;

function isMatchMeta(give, need) {
  if (give === need) {
    return true;
  }

  if ((0, _tsFns.isInheritedOf)(need, _meta.Meta) && ((0, _tsFns.isInstanceOf)(give, need) || (0, _tsFns.isInheritedOf)(give, need))) {
    return true;
  }

  if ((0, _tsFns.isInstanceOf)(need, Model) && (0, _tsFns.isInstanceOf)(give, _factory.FactoryMeta)) {
    var entries = give.$entries;

    if ((0, _tsFns.isArray)(entries)) {
      return entries.some(function (item) {
        return item === need;
      });
    } else {
      return entries === need;
    }
  } // meta.extend


  var walk = function walk(meta) {
    var from = Object.getPrototypeOf(meta);

    if (!from) {
      return false;
    }

    if (from === need) {
      return true;
    }

    return walk(from);
  };

  return walk(give);
}