docs: function(grouping)
    {
      var by = createPropertyResolver( grouping.by );
      var having = createWhere( grouping.having, grouping.havingValue, grouping.havingEquals );
      var select = grouping.select || {};
      var map = {};
  
      if ( isString( grouping.by ) )
      {
        if ( !(grouping.by in select) )
        {
          select[ grouping.by ] = 'first';
        }
      }
      else if ( isArray( grouping.by ) )
      {
        for (var prop in grouping.by)
        {
          if ( !(prop in select) )
          {
            select[ prop ] = 'first';
          }
        }
      }
  
      for (var i = 0; i < this.length; i++)
      {
        var model = this[ i ];
        var key = by( model );
        var group = map[ key ];
  
        if ( !group )
        {
          group = map[ key ] = this.cloneEmpty();
        }
  
        group.add( model, true );
      }
  
      var groupings = this.cloneEmpty();
  
      groupings.setComparator( grouping.comparator, grouping.comparatorNullsFirst );
  
      for (var key in map)
      {
        var grouped = {};
        var groupArray = map[ key ];
  
        for (var propName in select)
        {
          var aggregator = select[ propName ];
  
          if ( isString( aggregator ) )
          {
            grouped[ propName ] = groupArray[ aggregator ]( propName );
          }
          else if ( isFunction( aggregator ) )
          {
            grouped[ propName ] = aggregator( groupArray, propName );
          }
        }
  
        if ( grouping.track !== false )
        {
          grouped.$group = groupArray;
        }
  
        if ( grouping.count !== false )
        {
          grouped.$count = groupArray.length;
        }
  
        if ( having( grouped, groupArray ) )
        {
          groupings.push( grouped );
        }
      }
  
      groupings.sort();
  
      return groupings;
    }