OverviewFilesCommitsIssuesDocsReleasesPeers

mirall-cli / node_modules / bare-structured-clone / index.js

1336 lines · javascript ref:
const getType = require('bare-type')
const { isURL } = require('bare-url')
const { isBuffer } = require('bare-buffer')
const c = require('compact-encoding')
const constants = require('./lib/constants')
const errors = require('./lib/errors')
const binding = require('./binding')

const t = constants.type

module.exports = exports = function structuredClone(value, opts = {}) {
  return exports.deserializeWithTransfer(
    exports.serializeWithTransfer(value, opts.transfer, opts.interfaces),
    opts.interfaces
  )
}

// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserialize
exports.serialize = function serialize(value, forStorage = false, interfaces = []) {
  return serializeValue(value, forStorage, new InterfaceMap(interfaces), new ReferenceMap())
}

// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializewithtransfer
exports.serializeWithTransfer = function serializeWithTransfer(
  value,
  transferList = [],
  interfaces = []
) {
  return serializeValueWithTransfer(value, transferList, new InterfaceMap(interfaces))
}

// https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserialize
exports.deserialize = function deserialize(serialized, interfaces = []) {
  return deserializeValue(serialized, new InterfaceMap(interfaces), new Map())
}

// https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserializewithtransfer
exports.deserializeWithTransfer = function deserializeWithTransfer(serialized, interfaces = []) {
  return deserializeValueWithTransfer(serialized, new InterfaceMap(interfaces))
}

exports.constants = constants
exports.errors = errors

exports.symbols = {
  serialize: Symbol.for('bare.serialize'),
  deserialize: Symbol.for('bare.deserialize'),
  detach: Symbol.for('bare.detach'),
  attach: Symbol.for('bare.attach'),
  interface: Symbol.for('bare.interface')
}

exports.Serializable = class Serializable {
  [exports.symbols.serialize](forStorage) {}

  static [exports.symbols.deserialize](serialized) {}
}

exports.Transferable = class Transferable {
  constructor() {
    this.detached = false
  }

  [exports.symbols.detach]() {
    this.detached = true
  }

  static [exports.symbols.attach](serialized) {}
}

class InterfaceMap {
  constructor(interfaces) {
    this.ids = new WeakMap()
    this.keys = new Map()
    this.interfaces = new Map()

    let nextId = 1

    for (const constructor of interfaces) {
      const id = nextId++

      this.ids.set(constructor, id)

      const key = constructor[exports.symbols.interface]

      if (key !== undefined) this.keys.set(key, id)

      this.interfaces.set(id, constructor)
    }
  }

  id(constructor) {
    let id = this.ids.get(constructor)

    if (id) return id

    // The constructor was not registered by identity, but a distinct copy of
    // the same class might have been, for example when the value originates
    // from code that bundles its own copy of the interface. Fall back to the
    // interface key that the class declares for itself, a shared symbol such
    // as one from the global registry, so that any such copy with a matching
    // API resolves to the registered interface.
    const key = constructor[exports.symbols.interface]

    if (key !== undefined) {
      id = this.keys.get(key)

      if (id) {
        this.ids.set(constructor, id)

        return id
      }
    }

    throw errors.INVALID_INTERFACE(
      `Class '${constructor.name}' is not registered as a serializable or transferable interface`
    )
  }

  get(id) {
    const constructor = this.interfaces.get(id)

    if (!constructor) {
      throw errors.INVALID_INTERFACE(`Interface with ID '${id}' was not found`)
    }

    return constructor
  }
}

class ReferenceMap {
  constructor() {
    this.ids = new WeakMap()
    this.nextId = 1
  }

  id(object) {
    let id = this.ids.get(object)
    if (id) return id

    id = this.nextId++
    this.ids.set(object, id)

    return id
  }

  has(object) {
    return this.ids.has(object)
  }
}

function serializeValue(value, forStorage, interfaces, references) {
  const type = getType(value)

  if (type.isUndefined()) return { type: t.UNDEFINED }
  if (type.isNull()) return { type: t.NULL }
  if (type.isBoolean()) return { type: value ? t.TRUE : t.FALSE }
  if (type.isNumber()) return { type: t.NUMBER, value }
  if (type.isBigInt()) return { type: t.BIGINT, value }
  if (type.isString()) return serializeString(value)
  if (type.isSymbol()) return serializeSymbol(value)
  if (type.isObject()) {
    return serializeReferenceable(type, value, forStorage, interfaces, references)
  }
  if (type.isFunction()) return serializeFunction(value)
  if (type.isExternal()) return serializeExternal(value, forStorage, references)
}

function serializeString(value) {
  return { type: t.STRING, value }
}

function serializeSymbol(value) {
  throw errors.UNSERIALIZABLE_TYPE(`Symbol '${value.description}' cannot be serialized`)
}

function serializeFunction(value) {
  throw errors.UNSERIALIZABLE_TYPE(`Function '${value.name}' cannot be serialized`)
}

function serializeReferenceable(type, value, forStorage, interfaces, references) {
  if (references.has(value)) return serializeReference(value, references)

  if (isURL(value)) return serializeURL(value, references)
  if (isBuffer(value)) return serializeBuffer(value, forStorage, interfaces, references)

  if (type.isArray()) return serializeArray(value, forStorage, interfaces, references)
  if (type.isDate()) return serializeDate(value, references)
  if (type.isRegExp()) return serializeRegExp(value, references)
  if (type.isError()) return serializeError(value, forStorage, interfaces, references)
  if (type.isMap()) return serializeMap(value, forStorage, interfaces, references)
  if (type.isSet()) return serializeSet(value, forStorage, interfaces, references)
  if (type.isArrayBuffer()) return serializeArrayBuffer(value, references)
  if (type.isSharedArrayBuffer()) return serializeSharedArrayBuffer(value, forStorage, references)
  if (type.isTypedArray()) {
    return serializeTypedArray(type, value, forStorage, interfaces, references)
  }
  if (type.isDataView()) return serializeDataView(value, forStorage, interfaces, references)

  if (
    type.isPromise() ||
    type.isProxy() ||
    type.isWeakMap() ||
    type.isWeakSet() ||
    type.isWeakRef()
  ) {
    throw errors.UNSERIALIZABLE_TYPE(`${value.constructor.name} cannot be serialized`)
  }

  const serialize = value[Symbol.for('bare.serialize')]

  if (serialize) return serializeSerializable(value, serialize, forStorage, interfaces, references)

  return serializeObject(value, forStorage, interfaces, references)
}

function serializeReference(value, references) {
  return { type: t.REFERENCE, id: references.id(value) }
}

function serializeDate(value, references) {
  return { type: t.DATE, id: references.id(value), value: value.getTime() }
}

function serializeRegExp(value, references) {
  return {
    type: t.REGEXP,
    id: references.id(value),
    source: value.source,
    flags: value.flags
  }
}

function serializeError(value, forStorage, interfaces, references) {
  let name = 0

  switch (value.name) {
    case 'AggregateError':
      name = t.error.AGGREGATE
      break
    case 'EvalError':
      name = t.error.EVAL
      break
    case 'RangeError':
      name = t.error.RANGE
      break
    case 'ReferenceError':
      name = t.error.REFERENCE
      break
    case 'SyntaxError':
      name = t.error.SYNTAX
      break
    case 'TypeError':
      name = t.error.TYPE
      break
    case 'URIError':
      name = t.error.URI
      break
  }

  const serialized = {
    type: t.ERROR,
    id: references.id(value),
    name,
    message: value.message.toString(),
    stack: serializeValue(value.stack, forStorage, interfaces, references)
  }

  if ('cause' in value) {
    // Don't add unless defined
    serialized.cause = serializeValue(value.cause, forStorage, interfaces, references)
  }

  if (name === t.error.AGGREGATE) {
    serialized.errors = value.errors.map((err) =>
      serializeValue(err, forStorage, interfaces, references)
    )
  }

  return serialized
}

function serializeArrayBuffer(value, references) {
  if (value.detached) {
    throw errors.UNSERIALIZABLE_TYPE('Detached ArrayBuffer cannot be serialized')
  }

  const id = references.id(value)

  if (value.resizable) {
    return {
      type: t.RESIZABLEARRAYBUFFER,
      id,
      owned: false,
      data: value,
      maxByteLength: value.maxByteLength
    }
  }

  return {
    type: t.ARRAYBUFFER,
    id,
    owned: false,
    data: value
  }
}

function serializeSharedArrayBuffer(value, forStorage, references) {
  if (forStorage) {
    throw errors.UNSERIALIZABLE_TYPE('SharedArrayBuffer cannot be serialized to storage')
  }

  const id = references.id(value)

  const backingStore = binding.getSharedArrayBufferBackingStore(value)

  if (value.growable) {
    return {
      type: t.GROWABLESHAREDARRAYBUFFER,
      id,
      backingStore,
      maxByteLength: value.maxByteLength
    }
  }

  return {
    type: t.SHAREDARRAYBUFFER,
    id,
    backingStore
  }
}

function serializeTypedArray(type, value, forStorage, interfaces, references) {
  let view

  if (type.isUint8Array()) {
    view = t.typedarray.UINT8ARRAY
  } else if (type.isUint8ClampedArray()) {
    view = t.typedarray.UINT8CLAMPEDARRAY
  } else if (type.isInt8Array()) {
    view = t.typedarray.INT8ARRAY
  } else if (type.isUint16Array()) {
    view = t.typedarray.UINT16ARRAY
  } else if (type.isInt16Array()) {
    view = t.typedarray.INT16ARRAY
  } else if (type.isUint32Array()) {
    view = t.typedarray.UINT32ARRAY
  } else if (type.isInt32Array()) {
    view = t.typedarray.INT32ARRAY
  } else if (type.isBigUint64Array()) {
    view = t.typedarray.BIGUINT64ARRAY
  } else if (type.isBigInt64Array()) {
    view = t.typedarray.BIGINT64ARRAY
  } else if (type.isFloat16Array()) {
    view = t.typedarray.FLOAT16ARRAY
  } else if (type.isFloat32Array()) {
    view = t.typedarray.FLOAT32ARRAY
  } else if (type.isFloat64Array()) {
    view = t.typedarray.FLOAT64ARRAY
  }

  return {
    type: t.TYPEDARRAY,
    id: references.id(value),
    view,
    buffer: serializeValue(value.buffer, forStorage, interfaces, references),
    byteOffset: value.byteOffset,
    byteLength: value.byteLength,
    length: value.length
  }
}

function serializeDataView(value, forStorage, interfaces, references) {
  return {
    type: t.DATAVIEW,
    id: references.id(references),
    buffer: serializeValue(value.buffer, forStorage, interfaces, references),
    byteOffset: value.byteOffset,
    byteLength: value.byteLength
  }
}

function serializeMap(value, forStorage, interfaces, references) {
  const id = references.id(value)
  const data = []

  for (const entry of value) {
    const [key, value] = entry

    data.push({
      key: serializeValue(key, forStorage, interfaces, references),
      value: serializeValue(value, forStorage, interfaces, references)
    })
  }

  return { type: t.MAP, id, data }
}

function serializeSet(value, forStorage, interfaces, references) {
  const id = references.id(value)
  const data = []

  for (const entry of value) {
    data.push(serializeValue(entry, forStorage, interfaces, references))
  }

  return { type: t.SET, id, data }
}

function serializeArray(value, forStorage, interfaces, references) {
  const id = references.id(value)
  const properties = []

  for (const entry of Object.entries(value)) {
    const [key, value] = entry

    properties.push({
      key,
      value: serializeValue(value, forStorage, interfaces, references)
    })
  }

  return { type: t.ARRAY, id, length: value.length, properties }
}

function serializeObject(value, forStorage, interfaces, references) {
  const id = references.id(value)
  const properties = []

  for (const entry of Object.entries(value)) {
    const [key, value] = entry

    properties.push({
      key,
      value: serializeValue(value, forStorage, interfaces, references)
    })
  }

  return { type: t.OBJECT, id, properties }
}

function serializeURL(value, references) {
  return { type: t.URL, id: references.id(value), href: value.href }
}

function serializeBuffer(value, forStorage, interfaces, references) {
  if (value.detached) {
    throw errors.UNSERIALIZABLE_TYPE('Detached Buffer cannot be serialized')
  }

  return {
    type: t.BUFFER,
    id: references.id(value),
    buffer: serializeValue(value.buffer, forStorage, interfaces, references),
    byteOffset: value.byteOffset,
    byteLength: value.byteLength
  }
}

function serializeExternal(value, forStorage) {
  if (forStorage) {
    throw errors.UNSERIALIZABLE_TYPE('External pointer cannot be serialized to storage')
  }

  return {
    type: t.EXTERNAL,
    pointer: binding.getExternal(value)
  }
}

function serializeSerializable(value, serializer, forStorage, interfaces, references) {
  return {
    type: t.SERIALIZABLE,
    id: references.id(value),
    interface: interfaces.id(value.constructor),
    value: serializeValue(serializer.call(value, forStorage), forStorage, interfaces, references)
  }
}

function serializeValueWithTransfer(value, transferList, interfaces) {
  const references = new ReferenceMap()

  for (const transferable of transferList) {
    const type = getType(transferable)

    if (type.isArrayBuffer()) {
      if (transferable.detached) {
        throw errors.UNTRANSFERABLE_TYPE("Detached 'ArrayBuffer' cannot be transferred")
      }

      if (references.has(transferable)) {
        throw errors.ALREADY_TRANSFERRED("'ArrayBuffer' has already been transferred")
      }

      references.id(transferable)
    } else {
      const detach = transferable[exports.symbols.detach]

      if (detach) {
        if (transferable.detached) {
          throw errors.UNTRANSFERABLE_TYPE(
            `Detached '${transferable.constructor.name}' cannot be transferred`
          )
        }

        if (references.has(transferable)) {
          throw errors.ALREADY_TRANSFERRED(
            `'${transferable.constructor.name}' has already been transferred`
          )
        }

        references.id(transferable)
      } else {
        throw errors.UNTRANSFERABLE_TYPE('Value cannot be transferred')
      }
    }
  }

  const serialized = serializeValue(value, false, interfaces, references)

  const transfers = []

  for (const transferable of transferList) {
    const type = getType(transferable)

    if (type.isArrayBuffer()) {
      if (transferable.detached) {
        throw errors.UNTRANSFERABLE_TYPE('Detached ArrayBuffer cannot be transferred')
      }

      const backingStore = binding.getArrayBufferBackingStore(transferable)

      const id = references.id(transferable)

      let transfer

      if (value.resizable) {
        transfer = {
          type: t.RESIZABLEARRAYBUFFER,
          id,
          backingStore,
          maxByteLength: value.maxByteLength
        }
      } else {
        transfer = { type: t.ARRAYBUFFER, id, backingStore }
      }

      transfers.push(transfer)

      binding.detachArrayBuffer(transferable)
    } else {
      if (transferable.detached) {
        throw errors.UNTRANSFERABLE_TYPE(
          `Detached '${transferable.constructor.name}' cannot be transferred`
        )
      }

      const detach = transferable[exports.symbols.detach]

      const transfer = {
        type: t.TRANSFERABLE,
        id: references.id(transferable),
        interface: interfaces.id(transferable.constructor),
        value: serializeValue(detach.call(transferable), false, interfaces, references)
      }

      transfers.push(transfer)
    }
  }

  return { type: t.TRANSFER, transfers, value: serialized }
}

function deserializeValue(serialized, interfaces, references) {
  let value

  switch (serialized.type) {
    case t.UNDEFINED:
      return undefined
    case t.NULL:
      return null
    case t.TRUE:
      return true
    case t.FALSE:
      return false

    case t.NUMBER:
    case t.BIGINT:
    case t.STRING:
      return serialized.value

    case t.EXTERNAL:
      return binding.createExternal(serialized.pointer)

    case t.DATE:
      value = new Date(serialized.value)
      break

    case t.REGEXP:
      value = new RegExp(serialized.source, serialized.flags)
      break

    case t.ERROR: {
      const options = {}

      if ('cause' in serialized) {
        options.case = deserializeValue(serialized.cause, interfaces, references)
      }

      switch (serialized.name) {
        case t.error.AGGREGATE:
          value = new AggregateError(
            serialized.errors.map((err) => deserializeValue(err, interfaces, references)),
            serialized.message,
            options
          )
          break
        case t.error.EVAL:
          value = new EvalError(serialized.message, options)
          break
        case t.error.RANGE:
          value = new RangeError(serialized.message, options)
          break
        case t.error.REFERENCE:
          value = new ReferenceError(serialized.message, options)
          break
        case t.error.SYNTAX:
          value = new SyntaxError(serialized.message, options)
          break
        case t.error.TYPE:
          value = new TypeError(serialized.message, options)
          break
        default:
          value = new Error(serialized.message, options)
      }

      value.stack = deserializeValue(serialized.stack, interfaces, references)

      break
    }

    case t.ARRAYBUFFER:
      if (serialized.owned) value = serialized.data
      else {
        value = new ArrayBuffer(serialized.data.byteLength)

        Buffer.from(value).set(Buffer.from(serialized.data))
      }

      break

    case t.RESIZABLEARRAYBUFFER:
      if (serialized.owned) value = serialized.data
      else {
        value = new ArrayBuffer(serialized.data.byteLength, {
          maxByteLength: serialized.maxByteLength
        })

        Buffer.from(value).set(Buffer.from(serialized.data))
      }

      break

    case t.SHAREDARRAYBUFFER:
    case t.GROWABLESHAREDARRAYBUFFER:
      value = binding.createSharedArrayBuffer(serialized.backingStore)

      Buffer.from(serialized.backingStore).fill(0)

      break

    case t.TYPEDARRAY: {
      const buffer = deserializeValue(serialized.buffer, interfaces, references)

      switch (serialized.view) {
        case t.typedarray.UINT8ARRAY:
          value = new Uint8Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.UINT8CLAMPEDARRAY:
          value = new Uint8ClampedArray(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.INT8ARRAY:
          value = new Int8Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.UINT16ARRAY:
          value = new Uint16Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.INT16ARRAY:
          value = new Int16Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.UINT32ARRAY:
          value = new Uint32Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.INT32ARRAY:
          value = new Int32Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.BIGUINT64ARRAY:
          value = new BigUint64Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.BIGINT64ARRAY:
          value = new BigInt64Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.FLOAT16ARRAY:
          value = new Float16Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.FLOAT32ARRAY:
          value = new Float32Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.FLOAT64ARRAY:
          value = new Float64Array(buffer, serialized.byteOffset, serialized.length)
          break
      }

      break
    }

    case t.DATAVIEW:
      value = new DataView(
        deserializeValue(serialized.buffer, interfaces, references),
        serialized.byteOffset,
        serialized.byteLength
      )
      break

    case t.MAP:
      value = new Map()
      break
    case t.SET:
      value = new Set()
      break
    case t.ARRAY:
      value = new Array(serialized.length)
      break
    case t.OBJECT:
      value = {}
      break

    case t.REFERENCE:
      if (references.has(serialized.id)) value = references.get(serialized.id)
      else {
        throw errors.INVALID_REFERENCE(`Object with ID '${serialized.id}' was not found`)
      }

      return value

    case t.URL:
      return new URL(serialized.href)

    case t.BUFFER:
      value = Buffer.from(
        deserializeValue(serialized.buffer, interfaces, references),
        serialized.byteOffset,
        serialized.byteLength
      )
      break

    case t.SERIALIZABLE: {
      const constructor = interfaces.get(serialized.interface)

      const deserialize = constructor[exports.symbols.deserialize]

      value = deserialize.call(
        constructor,
        deserializeValue(serialized.value, interfaces, references)
      )
      break
    }
  }

  references.set(serialized.id, value)

  switch (serialized.type) {
    case t.MAP:
      for (const entry of serialized.data) {
        value.set(
          deserializeValue(entry.key, interfaces, references),
          deserializeValue(entry.value, interfaces, references)
        )
      }
      break

    case t.SET:
      for (const entry of serialized.data) {
        value.add(deserializeValue(entry, interfaces, references))
      }
      break

    case t.ARRAY:
    case t.OBJECT:
      for (const entry of serialized.properties) {
        value[entry.key] = deserializeValue(entry.value, interfaces, references)
      }
      break
  }

  return value
}

function deserializeValueWithTransfer(serialized, interfaces) {
  const references = new Map()

  for (const transfer of serialized.transfers) {
    switch (transfer.type) {
      case t.ARRAYBUFFER:
      case t.RESIZABLEARRAYBUFFER:
        references.set(transfer.id, binding.createArrayBuffer(transfer.backingStore))
        break

      case t.TRANSFERABLE: {
        const constructor = interfaces.get(transfer.interface)

        const attach = constructor[exports.symbols.attach]

        references.set(
          transfer.id,
          attach.call(constructor, deserializeValue(transfer.value, interfaces, references))
        )
        break
      }
    }
  }

  return deserializeValue(serialized.value, interfaces, references)
}

const header = {
  preencode(state) {
    c.uint.preencode(state, constants.VERSION)
    c.uint.preencode(state, 0) // Flags
  },
  encode(state) {
    c.uint.encode(state, constants.VERSION)
    c.uint.encode(state, 0) // Flags
  },
  decode(state) {
    const version = c.uint.decode(state)

    if (version !== constants.VERSION) {
      throw errors.INVALID_VERSION(`Invalid ABI version '${version}'`)
    }

    c.uint.decode(state) // Flags
  }
}

const property = {
  preencode(state, m) {
    c.string.preencode(state, m.key)
    value.preencode(state, m.value)
  },
  encode(state, m) {
    c.string.encode(state, m.key)
    value.encode(state, m.value)
  },
  decode(state) {
    return {
      key: c.string.decode(state),
      value: value.decode(state)
    }
  }
}

const properties = c.array(property)

const entry = {
  preencode(state, m) {
    value.preencode(state, m.key)
    value.preencode(state, m.value)
  },
  encode(state, m) {
    value.encode(state, m.key)
    value.encode(state, m.value)
  },
  decode(state) {
    return {
      key: value.decode(state),
      value: value.decode(state)
    }
  }
}

const entries = c.array(entry)

const transfer = {
  preencode(state, m) {
    c.uint.preencode(state, m.type)
    c.uint.preencode(state, m.id)

    switch (m.type) {
      case t.ARRAYBUFFER:
        c.arraybuffer.preencode(state, m.backingStore)
        break
      case t.RESIZABLEARRAYBUFFER:
        c.arraybuffer.preencode(state, m.backingStore)
        c.uint.preencode(state, m.maxByteLength)
        break
      case t.TRANSFERABLE:
        c.uint.preencode(state, m.interface)
        value.preencode(state, m.value)
        break
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.type)
    c.uint.encode(state, m.id)

    switch (m.type) {
      case t.ARRAYBUFFER:
        c.arraybuffer.encode(state, m.backingStore)
        break
      case t.RESIZABLEARRAYBUFFER:
        c.arraybuffer.encode(state, m.backingStore)
        c.uint.encode(state, m.maxByteLength)
        break
      case t.TRANSFERABLE:
        c.uint.encode(state, m.interface)
        value.encode(state, m.value)
        break
    }
  },
  decode(state) {
    const type = c.uint.decode(state)
    const id = c.uint.decode(state)

    switch (type) {
      case t.ARRAYBUFFER:
        return {
          type,
          id,
          backingStore: c.arraybuffer.decode(state)
        }
      case t.RESIZABLEARRAYBUFFER:
        return {
          type,
          id,
          backingStore: c.arraybuffer.decode(state),
          maxByteLength: c.uint.decode(state)
        }
      case t.TRANSFERABLE:
        return {
          type,
          id,
          interface: c.uint.decode(state),
          value: value.decode(state)
        }
    }
  }
}

const transfers = c.array(transfer)

const value = {
  preencode(state, m) {
    c.uint.preencode(state, m.type)

    switch (m.type) {
      case t.UNDEFINED:
      case t.NULL:
      case t.TRUE:
      case t.FALSE:
        return
      case t.NUMBER:
        return c.float64.preencode(state, m.value)
      case t.BIGINT:
        return c.bigint.preencode(state, m.value)
      case t.STRING:
        return c.string.preencode(state, m.value)
      case t.EXTERNAL:
        return c.arraybuffer.preencode(state, m.pointer)
      case t.TRANSFER:
        transfers.preencode(state, m.transfers)
        value.preencode(state, m.value)
        return
    }

    c.uint.preencode(state, m.type)

    switch (m.type) {
      case t.DATE:
        c.int.preencode(state, m.value)
        break
      case t.REGEXP:
        c.string.preencode(state, m.source)
        c.string.preencode(state, m.flags)
        break
      case t.ERROR:
        c.uint.preencode(state, 0) // Flags
        c.uint.preencode(state, m.name)
        c.string.preencode(state, m.message)
        value.preencode(state, m.stack)
        if ('cause' in m) value.preencode(state, m.cause)
        if (m.name === t.error.AGGREGATE) values.preencode(state, m.errors)
        break
      case t.ARRAYBUFFER:
        c.arraybuffer.preencode(state, m.data)
        break
      case t.RESIZABLEARRAYBUFFER:
        c.arraybuffer.preencode(state, m.data)
        c.uint.preencode(state, m.maxByteLength)
        break
      case t.SHAREDARRAYBUFFER:
        c.arraybuffer.preencode(state, m.backingStore)
        break
      case t.GROWABLESHAREDARRAYBUFFER:
        c.arraybuffer.preencode(state, m.backingStore)
        c.uint.preencode(state, m.maxByteLength)
        break
      case t.TYPEDARRAY:
        c.uint.preencode(state, m.view)
        value.preencode(state, m.buffer)
        c.uint.preencode(state, m.byteOffset)
        c.uint.preencode(state, m.byteLength)
        c.uint.preencode(state, m.length)
        break
      case t.DATAVIEW:
        value.preencode(state, m.buffer)
        c.uint.preencode(state, m.byteOffset)
        c.uint.preencode(state, m.byteLength)
        break
      case t.MAP:
        entries.preencode(state, m.data)
        break
      case t.SET:
        values.preencode(state, m.data)
        break
      case t.ARRAY:
        c.uint.preencode(state, m.length)
        properties.preencode(state, m.properties)
        break
      case t.OBJECT:
        properties.preencode(state, m.properties)
        break
      case t.REFERENCE:
        break
      case t.URL:
        c.string.preencode(state, m.href)
        break
      case t.BUFFER:
        value.preencode(state, m.buffer)
        c.uint.preencode(state, m.byteOffset)
        c.uint.preencode(state, m.byteLength)
        break
      case t.SERIALIZABLE:
        c.uint.preencode(state, m.interface)
        value.preencode(state, m.value)
        break
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.type)

    switch (m.type) {
      case t.UNDEFINED:
      case t.NULL:
      case t.TRUE:
      case t.FALSE:
        return
      case t.NUMBER:
        return c.float64.encode(state, m.value)
      case t.BIGINT:
        return c.bigint.encode(state, m.value)
      case t.STRING:
        return c.string.encode(state, m.value)
      case t.EXTERNAL:
        return c.arraybuffer.encode(state, m.pointer)
      case t.TRANSFER:
        transfers.encode(state, m.transfers)
        value.encode(state, m.value)
        return
    }

    c.uint.encode(state, m.id)

    switch (m.type) {
      case t.DATE:
        c.int.encode(state, m.value)
        break
      case t.REGEXP:
        c.string.encode(state, m.source)
        c.string.encode(state, m.flags)
        break
      case t.ERROR:
        c.uint.encode(state, 'cause' in m ? 1 : 0) // Flags
        c.uint.encode(state, m.name)
        c.string.encode(state, m.message)
        value.encode(state, m.stack)
        if ('cause' in m) value.encode(state, m.cause)
        if (m.name === t.error.AGGREGATE) values.encode(state, m.errors)
        break
      case t.ARRAYBUFFER:
        c.arraybuffer.encode(state, m.data)
        break
      case t.RESIZABLEARRAYBUFFER:
        c.arraybuffer.encode(state, m.data)
        c.uint.encode(state, m.maxByteLength)
        break
      case t.SHAREDARRAYBUFFER:
        c.arraybuffer.encode(state, m.backingStore)
        break
      case t.GROWABLESHAREDARRAYBUFFER:
        c.arraybuffer.encode(state, m.backingStore)
        c.uint.encode(state, m.maxByteLength)
        break
      case t.TYPEDARRAY:
        c.uint.encode(state, m.view)
        value.encode(state, m.buffer)
        c.uint.encode(state, m.byteOffset)
        c.uint.encode(state, m.byteLength)
        c.uint.encode(state, m.length)
        break
      case t.DATAVIEW:
        value.encode(state, m.buffer)
        c.uint.encode(state, m.byteOffset)
        c.uint.encode(state, m.byteLength)
        break
      case t.MAP:
        entries.encode(state, m.data)
        break
      case t.SET:
        values.encode(state, m.data)
        break
      case t.ARRAY:
        c.uint.encode(state, m.length)
        properties.encode(state, m.properties)
        break
      case t.OBJECT:
        properties.encode(state, m.properties)
        break
      case t.REFERENCE:
        break
      case t.URL:
        c.string.encode(state, m.href)
        break
      case t.BUFFER:
        value.encode(state, m.buffer)
        c.uint.encode(state, m.byteOffset)
        c.uint.encode(state, m.byteLength)
        break
      case t.SERIALIZABLE:
        c.uint.encode(state, m.interface)
        value.encode(state, m.value)
        break
    }
  },
  decode(state) {
    const type = c.uint.decode(state)

    switch (type) {
      case t.UNDEFINED:
      case t.NULL:
      case t.TRUE:
      case t.FALSE:
        return {
          type
        }
      case t.NUMBER:
        return {
          type,
          value: c.float64.decode(state)
        }
      case t.BIGINT:
        return {
          type,
          value: c.bigint.decode(state)
        }
      case t.STRING:
        return {
          type,
          value: c.string.decode(state)
        }
      case t.EXTERNAL:
        return {
          type,
          pointer: c.arraybuffer.decode(state)
        }
      case t.TRANSFER:
        return {
          type,
          transfers: transfers.decode(state),
          value: value.decode(state)
        }
    }

    const id = c.uint.decode(state)

    switch (type) {
      case t.DATE:
        return {
          type,
          id,
          value: c.int.decode(state)
        }
      case t.REGEXP:
        return {
          type,
          id,
          source: c.string.decode(state),
          flags: c.string.decode(state)
        }
      case t.ERROR: {
        const flags = c.uint.decode(state)

        const hasCause = (flags & 1) !== 0

        const m = {
          type,
          id,
          name: c.uint.decode(state),
          message: c.string.decode(state),
          stack: value.decode(state)
        }

        if (hasCause) m.cause = value.decode(state)

        if (m.name === t.error.AGGREGATE) m.errors = values.decode(state)

        return m
      }
      case t.ARRAYBUFFER:
        return {
          type,
          id,
          owned: true,
          data: c.arraybuffer.decode(state)
        }
      case t.RESIZABLEARRAYBUFFER:
        return {
          type,
          id,
          owned: true,
          data: c.arraybuffer.decode(state),
          maxByteLength: c.uint.decode(state)
        }
      case t.SHAREDARRAYBUFFER:
        return {
          type,
          id,
          backingStore: c.arraybuffer.decode(state)
        }
      case t.GROWABLESHAREDARRAYBUFFER:
        return {
          type,
          id,
          backingStore: c.arraybuffer.decode(state),
          maxByteLength: c.uint.decode(state)
        }
      case t.TYPEDARRAY:
        return {
          type,
          id,
          view: c.uint.decode(state),
          buffer: value.decode(state),
          byteOffset: c.uint.decode(state),
          byteLength: c.uint.decode(state),
          length: c.uint.decode(state)
        }
      case t.DATAVIEW:
        return {
          type,
          id,
          buffer: value.decode(state),
          byteOffset: c.uint.decode(state),
          byteLength: c.uint.decode(state)
        }
      case t.MAP:
        return {
          type,
          id,
          data: entries.decode(state)
        }
      case t.SET:
        return {
          type,
          id,
          data: values.decode(state)
        }
      case t.ARRAY:
        return {
          type,
          id,
          length: c.uint.decode(state),
          properties: properties.decode(state)
        }
      case t.OBJECT:
        return {
          type,
          id,
          properties: properties.decode(state)
        }
      case t.REFERENCE:
        return {
          type,
          id
        }
      case t.URL:
        return {
          type,
          id,
          href: c.string.decode(state)
        }
      case t.BUFFER:
        return {
          type,
          id,
          buffer: value.decode(state),
          byteOffset: c.uint.decode(state),
          byteLength: c.uint.decode(state)
        }
      case t.SERIALIZABLE:
        return {
          type,
          id,
          interface: c.uint.decode(state),
          value: value.decode(state)
        }
    }
  }
}

const values = c.array(value)

exports.preencode = function preencode(state, m) {
  header.preencode(state)
  value.preencode(state, m)
}

exports.encode = function encode(state, m) {
  header.encode(state)
  value.encode(state, m)
}

exports.decode = function decode(state) {
  header.decode(state)
  return value.decode(state)
}