setUserMapping(doc, clientid, userDescription, {
    filter = () => true
  } = {}) {
    const users = this.yusers;
    let user = users.get(userDescription);

    if (!user) {
      user = new YMap();
      user.set('ids', new YArray());
      user.set('ds', new YArray());
      users.set(userDescription, user);
    }

    user.get('ids').push([clientid]);
    users.observe(event => {
      setTimeout(() => {
        const userOverwrite = users.get(userDescription);

        if (userOverwrite !== user) {
          // user was overwritten, port all data over to the next user object
          // @todo Experiment with Y.Sets here
          user = userOverwrite; // @todo iterate over old type

          this.clients.forEach((_userDescription, clientid) => {
            if (userDescription === _userDescription) {
              user.get('ids').push([clientid]);
            }
          });
          const encoder = new DSEncoderV1();
          const ds = this.dss.get(userDescription);

          if (ds) {
            writeDeleteSet(encoder, ds);
            user.get('ds').push([encoder.toUint8Array()]);
          }
        }
      }, 0);
    });
    doc.on('afterTransaction',
    /** @param {Transaction} transaction */
    transaction => {
      setTimeout(() => {
        const yds = user.get('ds');
        const ds = transaction.deleteSet;

        if (transaction.local && ds.clients.size > 0 && filter(transaction, ds)) {
          const encoder = new DSEncoderV1();
          writeDeleteSet(encoder, ds);
          yds.push([encoder.toUint8Array()]);
        }
      });
    });
  }
@param {Doc} doc
@param {number} clientid
@param {string} userDescription
@param {Object} [conf]
@param {function(Transaction, DeleteSet):boolean} [conf.filter]