insertListItems(subpatch, index, values, newObject) {
    const list = newObject ? [] : this.getObject(subpatch.objectId)
    if (index < 0 || index > list.length) {
      throw new RangeError(`List index ${index} is out of bounds for list of length ${list.length}`)
    }
    if (values.length === 0) return

    let elemId = getElemId(list, index, true)
    const allPrimitive = values.every(v => typeof v === 'string' || typeof v === 'number' ||
                                           typeof v === 'boolean' || v === null ||
                                           (isObject(v) && (v instanceof Date || v instanceof Counter || v instanceof Int ||
                                                            v instanceof Uint || v instanceof Float64)))
    const allValueDescriptions = allPrimitive ? values.map(v => this.getValueDescription(v)) : []
    const allDatatypesSame = allValueDescriptions.every(t => t.datatype === allValueDescriptions[0].datatype)

    if (allPrimitive && allDatatypesSame && values.length > 1) {
      const nextElemId = this.nextOpId()
      const datatype = allValueDescriptions[0].datatype
      const values = allValueDescriptions.map(v => v.value)
      const op = {action: 'set', obj: subpatch.objectId, elemId, insert: true, values, pred: []}
      const edit = {action: 'multi-insert', elemId: nextElemId, index, values}
      if (datatype) {
        op.datatype = datatype
        edit.datatype = datatype
      }
      this.addOp(op)
      subpatch.edits.push(edit)
    } else {
      for (let offset = 0; offset < values.length; offset++) {
        let nextElemId = this.nextOpId()
        const valuePatch = this.setValue(subpatch.objectId, index + offset, values[offset], true, [], elemId)
        elemId = nextElemId
        subpatch.edits.push({action: 'insert', index: index + offset, elemId, opId: elemId, value: valuePatch})
      }
    }
  }
Inserts a sequence of new list elements `values` into a list, starting at position `index`.
`newObject` is true if we are creating a new list object, and false if we are updating an
existing one. `subpatch` is the patch for the list object being modified. Mutates
`subpatch` to reflect the sequence of values.