docs: function SchemaObjectInstanceFactory(schema, options) {
          // Represents an actual instance of an object.
          class SchemaObjectInstance {
              // Extend instance factory.
              static extend(extendSchema, extendOptions = {}) {
                  // Extend requires reflection.
                  if (_isProxySupported === false) {
                      throw new Error('[schema-object] Extending object requires --harmony flag.');
                  }
  
                  // Merge schema and options together.
                  const mergedSchema = _.merge({}, schema, extendSchema);
                  const mergedOptions = _.merge({}, options, extendOptions);
  
                  // Allow method and constructor to call `this.super()`.
                  const methodHomes = ['methods', 'constructors'];
                  for (const methodHome of methodHomes) {
                      // Ensure object containing methods exists on both provided and original options.
                      if (_.size(options[methodHome]) && _.size(extendOptions[methodHome])) {
                          // Loop through each method in the original options.
                          // It's not necessary to bind `this.super()` for options that didn't already exist.
                          _.each(options[methodHome], (method, name) => {
                              // The original option may exist, but was it extended?
                              if (extendOptions[methodHome][name]) {
                                  // Extend method by creating a binding that takes the `this` context given and adds `self`.
                                  // `self` is a reference to the original method, also bound to the correct `this`.
                                  mergedOptions[methodHome][name] = function () {
                                      this[_privateKey]._reservedFields.super = () => {
                                          return method.apply(this, arguments);
                                      };
                                      const ret = extendOptions[methodHome][name].apply(this, arguments);
                                      delete this[_privateKey]._reservedFields.super;
                                      return ret;
                                  };
                              }
                          });
                      }
                  }
  
                  return new SchemaObject(mergedSchema, mergedOptions);
              }
  
              // Construct new instance pre-populated with values.
              constructor(values, _root) {
                  // Object used to store internals.
                  const _private = this[_privateKey] = {};
  
                  //
                  _private._root = options.inheritRootThis ? _root || this : this;
  
                  // Object with getters and setters bound.
                  _private._getset = this;
  
                  // Public version of ourselves.
                  // Overwritten with proxy if available.
                  _private._this = this;
  
                  // Object used to store raw values.
                  const obj = _private._obj = {};
  
                  // Schema as defined by constructor.
                  _private._schema = schema;
  
                  // Errors, retrieved with getErrors().
                  _private._errors = [];
  
                  // Options need to be accessible. Shared across ALL INSTANCES.
                  _private._options = options;
  
                  // Reserved keys for storing internal properties accessible from outside.
                  _private._reservedFields = {};
  
                  // Normalize schema properties to allow for shorthand declarations.
                  _.each(schema, (properties, index) => {
                      schema[index] = normalizeProperties.call(this, properties, index);
                  });
  
                  // Define getters/typecasts based off of schema.
                  _.each(schema, (properties, index) => {
                      // Use getter / typecast to intercept and re-route, transform, etc.
                      defineGetter.call(_private._getset, index, properties);
                      defineSetter.call(_private._getset, index, properties);
                  });
  
                  // Proxy used as interface to object allows to intercept all access.
                  // Without Proxy we must register individual getter/typecasts to put any logic in place.
                  // With Proxy, we still use the individual getter/typecasts, but also catch values that aren't in the schema.
                  if (_isProxySupported === true) {
                      const proxy = this[_privateKey]._this = new Proxy(this, {
                          // Ensure only public keys are shown.
                          ownKeys: (target) => {
                              return Object.keys(this.toObject());
                          },
  
                          // Return keys to iterate.
                          enumerate: (target) => {
                              return Object.keys(this[_privateKey]._this)[Symbol.iterator]();
                          },
  
                          // Check to see if key exists.
                          has: (target, key) => {
                              return !!_private._getset[key];
                          },
  
                          // Ensure correct prototype is returned.
                          getPrototypeOf: () => {
                              return _private._getset;
                          },
  
                          // Ensure readOnly fields are not writeable.
                          getOwnPropertyDescriptor: (target, key) => {
                              return {
                                  value: proxy[key],
                                  writeable: !schema[key] || schema[key].readOnly !== true,
                                  enumerable: true,
                                  configurable: true
                              };
                          },
  
                          // Intercept all get calls.
                          get: (target, name, receiver) => {
                              // First check to see if it's a reserved field.
                              if (_reservedFields.includes(name)) {
                                  return this[_privateKey]._reservedFields[name];
                              }
  
                              // Support dot notation via lodash.
                              if (options.dotNotation && typeof name === 'string' && name.indexOf('.') !== -1) {
                                  return _.get(this[_privateKey]._this, name);
                              }
  
                              // Use registered getter without hitting the proxy to avoid creating an infinite loop.
                              return this[name];
                          },
  
                          // Intercept all set calls.
                          set: (target, name, value, receiver) => {
                              // Support dot notation via lodash.
                              if (options.dotNotation && typeof name === 'string' && name.indexOf('.') !== -1) {
                                  return _.set(this[_privateKey]._this, name, value);
                              }
  
                              // Find real keyname if case sensitivity is off.
                              if (options.keysIgnoreCase && !schema[name]) {
                                  name = getIndex.call(this, name);
                              }
  
                              if (!schema[name]) {
                                  if (options.strict) {
                                      // Strict mode means we don't want to deal with anything not in the schema.
                                      // TODO: SetterError here.
                                      return true;
                                  } else {
                                      // Add index to schema dynamically when value is set.
                                      // This is necessary for toObject to see the field.
                                      addToSchema.call(this, name, {
                                          type: 'any'
                                      });
                                  }
                              }
  
                              // This hits the registered setter but bypasses the proxy to avoid an infinite loop.
                              this[name] = value;
  
                              // Necessary for Node v6.0. Prevents error: 'set' on proxy: trap returned falsish for property 'string'".
                              return true;
                          },
  
                          // Intercept all delete calls.
                          deleteProperty: (target, property) => {
                              this[property] = undefined;
                              return true;
                          }
                      });
                  }
  
                  // Populate schema defaults into object.
                  _.each(schema, (properties, index) => {
                      if (properties.default !== undefined) {
                          // Temporarily ensure readOnly is turned off to prevent the set from failing.
                          const readOnly = properties.readOnly;
                          properties.readOnly = false;
                          this[index] = _.isFunction(properties.default) ? properties.default.call(this) : properties.default;
                          properties.readOnly = readOnly;
                      }
                  });
  
                  // Call default constructor.
                  _private._options.constructors.default.call(this, values);
  
                  // May return actual object instance or Proxy, depending on harmony support.
                  return _private._this;
              }
  
              // Populate values.
              populate(values) {
                  for (const key in values) {
                      this[_privateKey]._this[key] = values[key];
                  }
              }
  
              // Clone and return SchemaObject.
              clone() {
                  return new SchemaObjectInstance(this.toObject(), this[_privateKey]._root);
              }
  
              // Return object without getter/typecasts, extra properties, etc.
              toObject() {
                  const options = this[_privateKey]._options;
                  let getObj = {};
  
                  // Populate all properties in schema.
                  _.each(this[_privateKey]._schema, (properties, index) => {
                      // Do not write values to object that are marked as invisible.
                      if (properties.invisible) {
                          return;
                      }
  
                      // Fetch value through the public interface.
                      let value = this[_privateKey]._this[index];
  
                      // Do not write undefined values to the object because of strange behavior when using with MongoDB.
                      // MongoDB will convert undefined to null and overwrite existing values in that field.
                      if (value === undefined && options.setUndefined !== true) {
                          return;
                      }
  
                      // Clone objects so they can't be modified by reference.
                      if (_.isObject(value)) {
                          if (value._isSchemaObject) {
                              value = value.toObject();
                          } else if (value._isSchemaArray) {
                              value = value.toArray();
                          } else if (_.isArray(value)) {
                              value = value.splice(0);
                          } else if (_.isDate(value)) {
                              // https://github.com/documentcloud/underscore/pull/863
                              // _.clone doesn't work on Date object.
                              getObj[index] = new Date(value.getTime());
                          } else {
                              value = _.clone(value);
                          }
  
                          // Don't write empty objects or arrays.
                          if (!_.isDate(value) && !options.setUndefined && !_.size(value)) {
                              return;
                          }
                      }
  
                      // Write to object.
                      getObj[index] = value;
                  });
  
                  // If options contains toObject, pass through before returning final object.
                  if (_.isFunction(options.toObject)) {
                      getObj = options.toObject.call(this, getObj);
                  }
  
                  return getObj;
              }
  
              // toJSON is an interface used by JSON.stringify.
              // Return the raw object if called.
              toJSON() {
                  return this.toObject();
              }
  
              // Clear all values.
              clear() {
                  _.each(this[_privateKey]._schema, (properties, index) => {
                      clearField.call(this[_privateKey]._this, index, properties);
                  });
              }
  
              // Get all errors.
              getErrors() {
                  const errors = [];
                  for (let error of this[_privateKey]._errors) {
                      error = _.cloneDeep(error);
                      error.schemaObject = this;
                      errors.push(error);
                  }
  
                  _.each(this[_privateKey]._schema, (properties, index) => {
                      let required = properties.required;
                      let message = `${index} is required but not provided`;
  
                      //If required is an array, set custom message
                      if (Array.isArray(required)) {
                        message = required[1] || message;
                        required = required[0];
                      }
                      //Skip if required does not exist
                      if (!required) {
                          return;
                      }
                      //Skip if required is a function, but returns false
                      else if (typeof required === 'function' && !required.call(this)) {
                          return;
                      }
  
                      //Skip if property has a value, is a boolean set to false, or if it's falsy and falsy values are allowed
                      if (
                        this[index] ||
                        typeof this[index] === 'boolean' ||
                        this[_privateKey]._options.allowFalsyValues && this[index] !== undefined
                      ) {
                        return;
                      }
  
                      const error = new SetterError(message, this[index], this[index], properties);
                      error.schemaObject = this;
                      errors.push(error);
                  });
  
                  // Look for sub-SchemaObjects.
                  for (const name in this[_privateKey]._schema) {
                      const field = this[_privateKey]._schema[name];
                      if (field.type === 'object' && typeof field.objectType === 'function') {
                          const subErrors = this[name].getErrors();
                          for (const subError of subErrors) {
                              subError.fieldSchema.name = `${name}.${subError.fieldSchema.name}`;
                              subError.schemaObject = this;
                              errors.push(subError);
                          }
                      }
                  }
  
                  return errors;
              }
  
              // Clear all errors
              clearErrors() {
                  this[_privateKey]._errors.length = 0;
  
                  // Look for sub-SchemaObjects.
                  for (const name in this[_privateKey]._schema) {
                      const field = this[_privateKey]._schema[name];
                      if (field.type === 'object' && typeof field.objectType === 'function') {
                          this[name].clearErrors();
                      }
                  }
              }
  
              // Has errors?
              isErrors() {
                  return this.getErrors().length > 0;
              }
  
              // Used to detect instance of schema object internally.
              _isSchemaObject() {
                  return true;
              }
          }
  
          // Add custom methods to factory-generated class.
          _.each(options.methods, (method, key) => {
              if (SchemaObjectInstance.prototype[key]) {
                  throw new Error(`Cannot overwrite existing ${key} method with custom method.`);
              }
              SchemaObjectInstance.prototype[key] = method;
          });
  
          return SchemaObjectInstance;
      }