save() {
    if (this.binaryDoc) return this.binaryDoc

    // Getting the byte array for the changes columns finalises their encoders, after which we can
    // no longer append values to them. We therefore copy their data over to fresh encoders.
    const newEncoders = this.changesEncoders.map(col => ({columnId: col.columnId, encoder: encoderByColumnId(col.columnId)}))
    const decoders = this.changesEncoders.map(col => {
      const decoder = decoderByColumnId(col.columnId, col.encoder.buffer)
      return {columnId: col.columnId, decoder}
    })
    copyColumns(newEncoders, decoders, this.changes.length)

    this.binaryDoc = encodeDocumentHeader({
      changesColumns: this.changesEncoders,
      opsColumns: concatBlocks(this.blocks),
      actorIds: this.actorIds, // TODO: sort actorIds (requires transforming all actorId columns in opsColumns)
      heads: this.heads,
      headsIndexes: this.heads.map(hash => this.changeIndexByHash[hash]),
      extraBytes: this.extraBytes
    })
    this.changesEncoders = newEncoders
    return this.binaryDoc
  }
Serialises the current document state into a single byte array.