readAndApplyDeleteSet = (decoder, transaction, store) => {
  const unappliedDS = new DeleteSet();
  const numClients = decoding.readVarUint(decoder.restDecoder);

  for (let i = 0; i < numClients; i++) {
    decoder.resetDsCurVal();
    const client = decoding.readVarUint(decoder.restDecoder);
    const numberOfDeletes = decoding.readVarUint(decoder.restDecoder);
    const structs = store.clients.get(client) || [];
    const state = getState(store, client);

    for (let i = 0; i < numberOfDeletes; i++) {
      const clock = decoder.readDsClock();
      const clockEnd = clock + decoder.readDsLen();

      if (clock < state) {
        if (state < clockEnd) {
          addToDeleteSet(unappliedDS, client, state, clockEnd - state);
        }

        let index = findIndexSS(structs, clock);
        /**
         * We can ignore the case of GC and Delete structs, because we are going to skip them
         * @type {Item}
         */
        // @ts-ignore

        let struct = structs[index]; // split the first item if necessary

        if (!struct.deleted && struct.id.clock < clock) {
          structs.splice(index + 1, 0, splitItem(transaction, struct, clock - struct.id.clock));
          index++; // increase we now want to use the next struct
        }

        while (index < structs.length) {
          // @ts-ignore
          struct = structs[index++];

          if (struct.id.clock < clockEnd) {
            if (!struct.deleted) {
              if (clockEnd < struct.id.clock + struct.length) {
                structs.splice(index, 0, splitItem(transaction, struct, clockEnd - struct.id.clock));
              }

              struct.delete(transaction);
            }
          } else {
            break;
          }
        }
      } else {
        addToDeleteSet(unappliedDS, client, clock, clockEnd - clock);
      }
    }
  }

  if (unappliedDS.clients.size > 0) {
    const ds = new UpdateEncoderV2();
    encoding.writeVarUint(ds.restEncoder, 0); // encode 0 structs

    writeDeleteSet(ds, unappliedDS);
    return ds.toUint8Array();
  }

  return null;
}
@param {DSDecoderV1 | DSDecoderV2} decoder
@param {Transaction} transaction
@param {StructStore} store
@return {Uint8Array|null} Returns a v2 update containing all deletes that couldn't be applied yet; or null if all deletes were applied successfully.

@private
@function