extend(schema = {}) {
    if (Array.isArray(schema)) throw new Error('You may not pass an array of schemas to the SimpleSchema constructor or to extend()');

    let schemaObj;
    if (SimpleSchema.isSimpleSchema(schema)) {
      schemaObj = schema._schema;
      this._validators = this._validators.concat(schema._validators);
      this._docValidators = this._docValidators.concat(schema._docValidators);
      Object.assign(this._cleanOptions, schema._cleanOptions);
      Object.assign(this._constructorOptions, schema._constructorOptions);
    } else {
      schemaObj = expandShorthand(schema);
    }

    const schemaKeys = Object.keys(schemaObj);
    const combinedKeys = new Set([...Object.keys(this._schema), ...schemaKeys]);

    // Update all of the information cached on the instance
    schemaKeys.forEach((fieldName) => {
      const definition = standardizeDefinition(schemaObj[fieldName]);

      // Merge/extend with any existing definition
      if (this._schema[fieldName]) {
        if (!Object.prototype.hasOwnProperty.call(this._schema, fieldName)) {
          // fieldName is actually a method from Object itself!
          throw new Error(`${fieldName} key is actually the name of a method on Object, please rename it`);
        }

        const { type, ...definitionWithoutType } = definition; // eslint-disable-line no-unused-vars

        this._schema[fieldName] = {
          ...this._schema[fieldName],
          ...definitionWithoutType,
        };

        if (definition.type) this._schema[fieldName].type.extend(definition.type);
      } else {
        this._schema[fieldName] = definition;
      }

      checkAndScrubDefinition(fieldName, this._schema[fieldName], this._constructorOptions, combinedKeys);
    });

    checkSchemaOverlap(this._schema);

    // Set/Reset all of these
    this._schemaKeys = Object.keys(this._schema);
    this._autoValues = [];
    this._blackboxKeys = new Set();
    this._firstLevelSchemaKeys = [];

    // Update all of the information cached on the instance
    this._schemaKeys.forEach((fieldName) => {
      // Make sure parent has a definition in the schema. No implied objects!
      if (fieldName.indexOf('.') > -1) {
        const parentFieldName = fieldName.slice(0, fieldName.lastIndexOf('.'));
        if (!Object.prototype.hasOwnProperty.call(this._schema, parentFieldName)) throw new Error(`"${fieldName}" is in the schema but "${parentFieldName}" is not`);
      }

      const definition = this._schema[fieldName];

      // Keep list of all top level keys
      if (fieldName.indexOf('.') === -1) this._firstLevelSchemaKeys.push(fieldName);

      // Keep list of all blackbox keys for passing to MongoObject constructor
      // XXX For now if any oneOf type is blackbox, then the whole field is.
      /* eslint-disable no-restricted-syntax */
      for (const oneOfDef of definition.type.definitions) {
        // XXX If the type is SS.Any, also consider it a blackbox
        if (oneOfDef.blackbox === true || oneOfDef.type === SimpleSchema.Any) {
          this._blackboxKeys.add(fieldName);
          break;
        }
      }
      /* eslint-enable no-restricted-syntax */

      // Keep list of autoValue functions
      if (typeof definition.autoValue === 'function') {
        this._autoValues.push({
          closestSubschemaFieldName: '',
          fieldName,
          func: definition.autoValue,
        });
      }
    });

    return this;
  }
Extends (mutates) this schema with another schema, key by key.

@param {SimpleSchema|Object} schema
@returns The SimpleSchema instance (chainable)