opened
smooJitter
username_0
Suppose I have subsets that on a document like so,

friends : [ {userId: string,  meta: {}},... ]

Where meta contains discriminatory for the type of friend

How whould I create a relation on freinds that resolves to friends where meta.mutual = 1, and returning a list of friend Profile objects where the Profile object represents a subset user fields e.g.,
{ username, avatar, userId, ....}?
Sub documents.
issue
created
smooJitter
username_0
One alternative would be to restructure the model

friends: [ userids....]
mutual: [userIds...]
likes: [ userId]
likesMe: [userId...]

This is easier but I still would need the fields to resolve to objects of a type Friend {fields...}
comment
created
nodkz
username_1
You may do something like this:
```js
UserTC.setFields({
  // add new field to User type with name `mutualFriends`
  mutualFriends: {
    // require `friends` field from db, when you use just `mutualFriends` in query
    projection: { friends: true },
    ...UserTC
      // get standard resolver findMany which returns list of Users 
      .getResolver('findMany')
      // wrap it with additional logic, which obtains list of mutual friends from `source.friends`
      .wrapResolve(next => rp => {
        const mutual = rp.source.friends.filter(o => o.meta.mutual).map(o => o.userId) || [];
        if (mutual.length === 0) return [];
        rp.rawQuery = {
          _id: { $in: mutual }, // may be you'll need to convert stringId to MongoId
        };
        return next(rp);
      }),
});
```
comment
created
smooJitter
username_0
I'm sorry perhaps I should have added more clarity. I believe I am asking about a more common use case.  The example above is a bit misleading. 

My user model looks like this

```
const UserPreference = new mongoose.Schema( {
    typeOfPreference: { type: [String], enum: ['FOOD', 'BEVERAGE']
    ...
});

const UserSchema = new mongoose.Schema( {
    username: String,
    preferences: [UserPreference],
    eventsAttended: [{ type: ObjectId, ref: 'Events' }],
  });
```
I can actual use this to highlight 2 common use cases.  

1. FILTERED RELATIONS:  The events field contains an array of ObjectIds. I like for it to resolve to a subset of fields from the events collection (primary) and in an advance case add filters on a subset of the subset fields, e.g., { name, description, typeOfEvent } where type of event has a filter. 

2. FILTER SUB DOCUMENTS:  The preference field contains an array of subdocuments.   I would like  add a filter on the discriminator key (typeOfPreference). Do I need add a resolver (e.g., getUserPreferencesByType)
comment
created
nodkz
username_1
### FILTERED RELATIONS

You may use standard resolver `findMany`, `connection` and extend filter for Events from User data in such manner:
```js
UserTC.addRelation('myEventsWithFilter', () => ({
  resolver: () =>
    EventsTC.getResolver('findMany').wrapResolve(next => rp => {
      // With object-path package set filter by one line 
      // objectPath.set(rp, 'args.filter._ids', rp.source.eventIds);
      // or 
      if (!rp.args.filter) rp.args.filter = {}; // ensure that `filter` exists 
      rp.args.filter._ids = rp.source.eventIds; // set `_ids` from current User doc
      
      // call standard `findMany` resolver with extended filter 
      return next(rp);
    }),
  projection: { eventIds: true },
}));
``` 
PS. Upgrade to graphql-compose-mongoose@1.6.0 where added `filter._ids` field for `findMany` resolver.

### FILTER SUB DOCUMENTS
In such case you need to extend resolver with your custom filters via `addFilterArg`, `addSortArg`. Something like this:

```js
UserTC.setResolver(
  'connection', // providing same name for replacing standard resolver `connection`, or you may set another name for keepeng standard resolver untoched
  UserTC.getResolver('connection')
    .addFilterArg({
      name: 'region',
      type: '[String]',
      description: 'Region, Country, City',
      query: (rawQuery, value) => {
        if (value.length === 1) {
          rawQuery['location.name'] = value[0];
        } else {
          rawQuery['location.name'] = { $in: value };
        }
      },
    })
    .addFilterArg({
      name: 'salaryMax',
      type: CvTC.get('$connection.@filter.salary'),
      description: 'Max salary',
      query: (rawQuery, value) => {
        if (value.total > 0) {
          rawQuery['salary.total'] = { $gte: 1, $lte: value.total };
        }
        if (value.currency) {
          rawQuery['salary.currency'] = value.currency;
        }
      },
    })
    .addFilterArg({
      name: 'specializations',
      type: '[String]',
      description: 'Array of profession areas (any)',
      query: (rawQuery, value) => {
        rawQuery.specializations = { $in: value };
      },
    })
    .addFilterArg({
      name: 'employments',
      type: '[String]',
      description: 'Array of employment (any)',
      query: (rawQuery, value) => {
        rawQuery.employment = { $in: value };
      },
    })
    .addFilterArg({
      name: 'totalExperienceMin',
      type: 'Int',
      description: 'Min expirience in months',
      query: (rawQuery, value) => {
        rawQuery.totalExperience = { $gte: value };
      },
    })
    .addFilterArg({
      name: 'ageRange',
      type: 'input AgeRange { min: Int, max: Int }',
      description: 'Filter by age range (in years)',
      query: (rawQuery, value) => {
        const d = new Date();
        const month = d.getMonth();
        const day = d.getDate();
        const year = d.getFullYear();
        let minAge = value.min || 0;
        let maxAge = value.max || 0;
        if (!minAge && !maxAge) return;
        if (minAge > maxAge && minAge && maxAge) [minAge, maxAge] = [maxAge, minAge];
        rawQuery.birthday = {};
        if (maxAge) {
          rawQuery.birthday.$gte = new Date(year - maxAge - 1, month, day);
        }
        if (minAge) {
          rawQuery.birthday.$lte = new Date(year - minAge, month, day);
        }
      },
    })
    .addFilterArg({
      name: 'periodMaxH',
      type: 'Int',
      description: 'Filter by created date (in hours)',
      query: (rawQuery, value) => {
        if (value > 0 && value < 99999) {
          const curDate = new Date();
          const pastDate = new Date();
          pastDate.setTime(pastDate.getTime() - value * 3600000);
          rawQuery.createdAt = {
            $gt: pastDate,
            $lt: curDate,
          };
        }
      },
    })
    .addFilterArg({
      name: 'langs',
      type: '[String]',
      description: 'Language list (all)',
      query: (rawQuery, value) => {
        if (value.length === 1) {
          rawQuery['languages.ln'] = value[0];
        } else {
          rawQuery['languages.ln'] = { $all: value };
        }
      },
    })
    .addFilterArg({
      name: 'citizenships',
      type: '[String]',
      description: 'Citizenship list (any)',
      query: (rawQuery, value) => {
        if (value.length === 1) {
          rawQuery.citizenship = value[0];
        } else {
          rawQuery.citizenship = { $in: value };
        }
      },
    })
    .addFilterArg({
      name: 'hasPhoto',
      type: 'Boolean',
      query: (rawQuery, value) => {
        rawQuery.avatarUrl = { $exists: value };
      },
    })
    .addSortArg({
      name: 'RELEVANCE',
      description: 'Sort by text score or date',
      value: resolveParams => {
        if (resolveParams.rawQuery && resolveParams.rawQuery.$text) {
          return { score: { $meta: 'textScore' } };
        }
        return { createdAt: -1 };
      },
    })
    .addFilterArg({
      name: 'q',
      type: 'String',
      description: 'Text search',
      query: (rawQuery, value, resolveParams) => {
        rawQuery.$text = { $search: value, $language: 'ru' };
        resolveParams.projection.score = { $meta: 'textScore' };
      },
    })
    .addSortArg({
      name: 'SALARY_ASC',
      value: resolveParams => {
        if (!resolveParams.rawQuery) resolveParams.rawQuery = {};
        resolveParams.rawQuery['salary.total'] = { $gt: 0 };
        return { 'salary.total': 1 };
      },
    })
    .addSortArg({
      name: 'SALARY_DESC',
      value: resolveParams => {
        if (!resolveParams.rawQuery) resolveParams.rawQuery = {};
        resolveParams.rawQuery['salary.total'] = { $gt: 0 };
        return { 'salary.total': -1 };
      },
    })
    .addSortArg({
      name: 'DATE_ASC',
      value: { createdAt: 1 },
    })
    .addSortArg({
      name: 'DATE_DESC',
      value: { createdAt: -1 },
    })
);
```
comment
created
danielmahon
username_2
@username_1 I'm trying to filter relations based on the above and not having any luck, see #96. Any suggestions? I notice my `wrapResolve` and `query` functions never run so I'm sure I'm missing something.
comment