Skip to content

Commit 194ac5b

Browse files
committed
feat(API): Refactor config opt sort. Remove resolveParams.sort pollution.
See proposal in issue #3 for details BREAKING CHANGE: config option `sort`
1 parent d40d54e commit 194ac5b

9 files changed

+315
-204
lines changed

README.md

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -33,45 +33,77 @@ Example
3333
```js
3434
import composeWithConnection from 'graphql-compose-connection';
3535
import userTypeComposer from './user.js';
36-
const OPERATORS_FIELDNAME = '_operators';
3736

3837
composeWithConnection(userTypeComposer, {
3938
findResolverName: 'findMany',
4039
countResolverName: 'count',
4140
sort: {
41+
// Sorting key, visible for users in GraphQL Schema
42+
_ID_ASC: {
43+
// Sorting value for ORM/Driver
44+
value: { _id: 1 },
45+
46+
// Field names in record, which data will be packed in `cursor`
47+
// edges {
48+
// cursor <- base64(cursorData), for this example `cursorData` = { _id: 334ae453 }
49+
// node <- record from DB
50+
// }
51+
// By this fields MUST be created UNIQUE index in database!
52+
cursorFields: ['_id'],
53+
54+
// If for connection query provided `before` argument with above `cursor`.
55+
// We should construct (`rawQuery`) which will be point to dataset before cursor.
56+
// Unpacked data from `cursor` will be available in (`cursorData`) argument.
57+
// PS. All other filter options provided via GraphQL query will be added automatically.
58+
// ----- [record] ----- sorted dataset, according to above option with `value` name
59+
// ^^^^^ `rawQuery` should filter this set
60+
beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
61+
if (!rawQuery._id) rawQuery._id = {};
62+
rawQuery._id.$lt = cursorData._id;
63+
},
64+
65+
// Constructing `rawQuery` for connection `after` argument.
66+
// ----- [record] ----- sorted dataset
67+
// ^^^^^ `rawQuery` should filter this set
68+
afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
69+
if (!rawQuery._id) rawQuery._id = {};
70+
rawQuery._id.$gt = cursorData._id;
71+
},
72+
},
73+
4274
_ID_DESC: {
43-
uniqueFields: ['_id'],
44-
sortValue: { _id: -1 },
45-
directionFilter: (filter, cursorData, isBefore) => {
46-
filter[OPERATORS_FIELDNAME] = filter[OPERATORS_FIELDNAME] || {};
47-
filter[OPERATORS_FIELDNAME]._id = filter[OPERATORS_FIELDNAME]._id || {};
48-
if (isBefore) {
49-
filter[OPERATORS_FIELDNAME]._id.gt = cursorData._id;
50-
} else {
51-
filter[OPERATORS_FIELDNAME]._id.lt = cursorData._id;
52-
}
53-
return filter;
75+
value: { _id: -1 },
76+
cursorFields: ['_id'],
77+
beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
78+
if (!rawQuery._id) rawQuery._id = {};
79+
rawQuery._id.$gt = cursorData._id;
80+
},
81+
afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
82+
if (!rawQuery._id) rawQuery._id = {};
83+
rawQuery._id.$lt = cursorData._id;
5484
},
5585
},
86+
87+
// More complex sorting parameter with 2 fields
5688
AGE_ID_ASC: {
57-
uniqueFields: ['age', '_id'],
58-
sortValue: { age: 1, _id: -1 },
59-
directionFilter: (filter, cursorData, isBefore) => {
60-
filter[OPERATORS_FIELDNAME] = filter[OPERATORS_FIELDNAME] || {};
61-
filter[OPERATORS_FIELDNAME]._id = filter[OPERATORS_FIELDNAME]._id || {};
62-
filter[OPERATORS_FIELDNAME].age = filter[OPERATORS_FIELDNAME].age || {};
63-
if (isBefore) {
64-
filter[OPERATORS_FIELDNAME].age.lt = cursorData.age;
65-
filter[OPERATORS_FIELDNAME]._id.gt = cursorData._id;
66-
} else {
67-
filter[OPERATORS_FIELDNAME].age.gt = cursorData.age;
68-
filter[OPERATORS_FIELDNAME]._id.lt = cursorData._id;
69-
}
70-
return filter;
89+
value: { age: 1, _id: -1 },
90+
// By these fields MUST be created COMPOUND UNIQUE index in database!
91+
cursorFields: ['age', '_id'],
92+
beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
93+
if (!rawQuery.age) rawQuery.age = {};
94+
if (!rawQuery._id) rawQuery._id = {};
95+
rawQuery.age.$lt = cursorData.age;
96+
rawQuery._id.$gt = cursorData._id;
97+
},
98+
afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
99+
if (!rawQuery.age) rawQuery.age = {};
100+
if (!rawQuery._id) rawQuery._id = {};
101+
rawQuery.age.$gt = cursorData.age;
102+
rawQuery._id.$lt = cursorData._id;
71103
},
72104
}
73105
},
74-
})
106+
});
75107
```
76108
<img width="1249" alt="screen shot 2016-07-20 at 12 20 08" src="https://cloud.githubusercontent.com/assets/1946920/16976899/67a5e0f8-4e74-11e6-87e5-fc4574deaaab.png">
77109

src/__mocks__/userTypeComposer.js

Lines changed: 48 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -58,31 +58,6 @@ const filterArgConfig = {
5858
age: {
5959
type: GraphQLInt,
6060
},
61-
_operators: {
62-
type: new GraphQLInputObjectType({
63-
name: 'OperatorsFilterUserInput',
64-
fields: {
65-
id: {
66-
type: new GraphQLInputObjectType({
67-
name: 'IdOperatorsFilterUserInput',
68-
fields: {
69-
lt: { type: GraphQLInt },
70-
gt: { type: GraphQLInt },
71-
},
72-
}),
73-
},
74-
age: {
75-
type: new GraphQLInputObjectType({
76-
name: 'AgeOperatorsFilterUserInput',
77-
fields: {
78-
lt: { type: GraphQLInt },
79-
gt: { type: GraphQLInt },
80-
},
81-
}),
82-
},
83-
},
84-
}),
85-
},
8661
},
8762
}),
8863
};
@@ -93,22 +68,20 @@ function filteredUserList(list, filter = {}) {
9368
result = result.filter(o => o.gender === filter.gender);
9469
}
9570

96-
if (filter._operators) {
97-
if (filter._operators.id) {
98-
if (filter._operators.id.lt) {
99-
result = result.filter(o => o.id < filter._operators.id.lt);
100-
}
101-
if (filter._operators.id.gt) {
102-
result = result.filter(o => o.id > filter._operators.id.gt);
103-
}
71+
if (filter.id) {
72+
if (filter.id.$lt) {
73+
result = result.filter(o => o.id < filter.id.$lt);
10474
}
105-
if (filter._operators.age) {
106-
if (filter._operators.age.lt) {
107-
result = result.filter(o => o.age < filter._operators.age.lt);
108-
}
109-
if (filter._operators.age.gt) {
110-
result = result.filter(o => o.age > filter._operators.age.gt);
111-
}
75+
if (filter.id.$gt) {
76+
result = result.filter(o => o.id > filter.id.$gt);
77+
}
78+
}
79+
if (filter.age) {
80+
if (filter.age.$lt) {
81+
result = result.filter(o => o.age < filter.age.$lt);
82+
}
83+
if (filter.age.$gt) {
84+
result = result.filter(o => o.age > filter.age.$gt);
11285
}
11386
}
11487

@@ -133,6 +106,17 @@ function sortUserList(list, sortValue = {}) {
133106
return list;
134107
}
135108

109+
function prepareFilterFromArgs(resolveParams = {}) {
110+
const args = resolveParams.args || {};
111+
const filter = Object.assign({}, args.filter);
112+
if (resolveParams.rawQuery) {
113+
Object.keys(resolveParams.rawQuery).forEach((k) => {
114+
filter[k] = resolveParams.rawQuery[k];
115+
});
116+
}
117+
return filter;
118+
}
119+
136120
export const findManyResolver = new Resolver({
137121
name: 'findMany',
138122
kind: 'query',
@@ -157,7 +141,7 @@ export const findManyResolver = new Resolver({
157141

158142
let list = userList.slice();
159143
list = sortUserList(list, sort);
160-
list = filteredUserList(list, filter);
144+
list = filteredUserList(list, prepareFilterFromArgs(resolveParams));
161145

162146
if (skip) {
163147
list = list.slice(skip);
@@ -184,7 +168,7 @@ export const countResolver = new Resolver({
184168
return Promise.resolve(
185169
filteredUserList(
186170
userList,
187-
resolveParams.args && resolveParams.args.filter
171+
prepareFilterFromArgs(resolveParams)
188172
).length
189173
);
190174
},
@@ -194,34 +178,31 @@ userTypeComposer.setResolver('count', countResolver);
194178

195179
export const sortOptions = {
196180
ID_ASC: {
197-
uniqueFields: ['id'],
198-
sortValue: { id: 1 },
199-
directionFilter: (filter, cursorData, isBefore) => {
200-
filter._operators = filter._operators || {};
201-
filter._operators.id = filter._operators.id || {};
202-
if (isBefore) {
203-
filter._operators.id.lt = cursorData.id;
204-
} else {
205-
filter._operators.id.gt = cursorData.id;
206-
}
207-
return filter;
181+
value: { id: 1 },
182+
cursorFields: ['id'],
183+
beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
184+
if (!rawQuery.id) rawQuery.id = {};
185+
rawQuery.id.$lt = cursorData.id;
186+
},
187+
afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
188+
if (!rawQuery.id) rawQuery.id = {};
189+
rawQuery.id.$gt = cursorData.id;
208190
},
209191
},
210192
AGE_ID_DESC: {
211-
uniqueFields: ['age', 'id'],
212-
sortValue: { age: -1, id: -1 },
213-
directionFilter: (filter, cursorData, isBefore) => {
214-
filter._operators = filter._operators || {};
215-
filter._operators.id = filter._operators.id || {};
216-
filter._operators.age = filter._operators.age || {};
217-
if (isBefore) {
218-
filter._operators.age.gt = cursorData.age;
219-
filter._operators.id.gt = cursorData.id;
220-
} else {
221-
filter._operators.age.lt = cursorData.age;
222-
filter._operators.id.lt = cursorData.id;
223-
}
224-
return filter;
193+
value: { age: -1, id: -1 },
194+
cursorFields: ['age', 'id'],
195+
beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
196+
if (!rawQuery.age) rawQuery.age = {};
197+
if (!rawQuery.id) rawQuery.id = {};
198+
rawQuery.age = { $gt: cursorData.age };
199+
rawQuery.id = { $gt: cursorData.id };
200+
},
201+
afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
202+
if (!rawQuery.age) rawQuery.age = {};
203+
if (!rawQuery.id) rawQuery.id = {};
204+
rawQuery.age = { $lt: cursorData.age };
205+
rawQuery.id = { $lt: cursorData.id };
225206
},
226207
},
227208
};

src/__tests__/composeWithConnection-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,6 @@ describe('composeWithRelay', () => {
258258
expect(topResolveParams.findManyResolveParams)
259259
.to.contain.all.keys(['source', 'args', 'context', 'info', 'projection']);
260260
expect(topResolveParams.findManyResolveParams.args)
261-
.deep.equal({ filter: { age: 45 }, limit: 2, sort: { id: 1 } });
261+
.deep.equal({ filter: { age: 45 }, limit: 2, sort: { id: 1 }, first: 1 });
262262
});
263263
});

0 commit comments

Comments
 (0)