11/* @flow */
22/* eslint-disable no-use-before-define */
33
4- import { TypeComposer } from 'graphql-compose' ;
5- import { GraphQLNonNull } from 'graphql' ;
4+ import { TypeComposer , InputTypeComposer } from 'graphql-compose' ;
5+ import {
6+ GraphQLNonNull ,
7+ GraphQLInputObjectType ,
8+ GraphQLList ,
9+ getNamedType ,
10+ } from 'graphql' ;
611import getIndexesFromModel from '../../utils/getIndexesFromModel' ;
7- import { toDottedObject } from '../../utils' ;
12+ import { toDottedObject , upperFirst } from '../../utils' ;
813import type {
914 GraphQLFieldConfigArgumentMap ,
1015 ExtendedResolveParams ,
1116 MongooseModelT ,
1217 filterHelperArgsOpts ,
18+ filterOperatorsOpts ,
19+ filterOperatorNames ,
1320} from '../../definition' ;
1421
22+ export const OPERATORS_FIELDNAME = '_operators' ;
23+
24+
1525export const filterHelperArgs = (
1626 typeComposer : TypeComposer ,
27+ model : MongooseModelT ,
1728 opts : filterHelperArgsOpts
1829) : GraphQLFieldConfigArgumentMap => {
1930 if ( ! ( typeComposer instanceof TypeComposer ) ) {
2031 throw new Error ( 'First arg for filterHelperArgs() should be instance of TypeComposer.' ) ;
2132 }
2233
34+ if ( ! model || ! model . modelName || ! model . schema ) {
35+ throw new Error (
36+ 'Second arg for filterHelperArgs() should be instance of Mongoose Model.'
37+ ) ;
38+ }
39+
2340 if ( ! opts || ! opts . filterTypeName ) {
2441 throw new Error ( 'You should provide non-empty `filterTypeName` in options.' ) ;
2542 }
@@ -34,12 +51,7 @@ export const filterHelperArgs = (
3451 }
3552
3653 if ( opts . onlyIndexed ) {
37- if ( ! opts . model ) {
38- throw new Error ( 'You should provide `model` in options with mongoose model '
39- + 'for deriving index fields.' ) ;
40- }
41-
42- const indexedFieldNames = getIndexedFieldNames ( opts . model ) ;
54+ const indexedFieldNames = getIndexedFieldNames ( model ) ;
4355 Object . keys ( typeComposer . getFields ( ) ) . forEach ( fieldName => {
4456 if ( indexedFieldNames . indexOf ( fieldName ) === - 1 ) {
4557 removeFields . push ( fieldName ) ;
@@ -49,12 +61,23 @@ export const filterHelperArgs = (
4961
5062 const filterTypeName : string = opts . filterTypeName ;
5163 const inputComposer = typeComposer . getInputTypeComposer ( ) . clone ( filterTypeName ) ;
64+ inputComposer . makeFieldsOptional ( inputComposer . getFieldNames ( ) ) ;
5265 inputComposer . removeField ( removeFields ) ;
5366
5467 if ( opts . requiredFields ) {
5568 inputComposer . makeFieldsRequired ( opts . requiredFields ) ;
5669 }
5770
71+ if ( ! opts . hasOwnProperty ( 'operators' ) || opts . operators !== false ) {
72+ addFieldsWithOperator (
73+ // $FlowFixMe
74+ `Operators${ opts . filterTypeName } ` ,
75+ inputComposer ,
76+ model ,
77+ opts . operators || { }
78+ ) ;
79+ }
80+
5881 return {
5982 filter : {
6083 name : 'filter' ,
@@ -71,7 +94,30 @@ export const filterHelperArgs = (
7194export function filterHelper ( resolveParams : ExtendedResolveParams ) : void {
7295 const filter = resolveParams . args && resolveParams . args . filter ;
7396 if ( filter && typeof filter === 'object' && Object . keys ( filter ) . length > 0 ) {
74- resolveParams . query = resolveParams . query . where ( toDottedObject ( filter ) ) ; // eslint-disable-line
97+ if ( ! filter [ OPERATORS_FIELDNAME ] ) {
98+ resolveParams . query = resolveParams . query . where ( toDottedObject ( filter ) ) ; // eslint-disable-line
99+ } else {
100+ const operatorFields = Object . assign ( { } , filter [ OPERATORS_FIELDNAME ] ) ;
101+ const simpleFields = Object . assign ( { } , filter ) ;
102+ delete simpleFields [ OPERATORS_FIELDNAME ] ;
103+
104+ if ( Object . keys ( simpleFields ) . length > 0 ) {
105+ resolveParams . query = resolveParams . query . where ( toDottedObject ( simpleFields ) ) ; // eslint-disable-line
106+ }
107+ Object . keys ( operatorFields ) . forEach ( fieldName => {
108+ const fieldOperators = Object . assign ( { } , operatorFields [ fieldName ] ) ;
109+ const criteria = { } ;
110+ Object . keys ( fieldOperators ) . forEach ( operatorName => {
111+ criteria [ `$${ operatorName } ` ] = fieldOperators [ operatorName ] ;
112+ } ) ;
113+ if ( Object . keys ( criteria ) . length > 0 ) {
114+ // $FlowFixMe
115+ resolveParams . query = resolveParams . query . find ( { // eslint-disable-line
116+ [ fieldName ] : criteria ,
117+ } ) ;
118+ }
119+ } ) ;
120+ }
75121 }
76122}
77123
@@ -86,3 +132,67 @@ export function getIndexedFieldNames(model: MongooseModelT): string[] {
86132
87133 return fieldNames ;
88134}
135+
136+ export function addFieldsWithOperator (
137+ typeName : string ,
138+ inputComposer : InputTypeComposer ,
139+ model : MongooseModelT ,
140+ operatorsOpts : filterOperatorsOpts
141+ ) {
142+ const operatorsComposer = new InputTypeComposer ( new GraphQLInputObjectType ( {
143+ name : typeName ,
144+ fields : { } ,
145+ } ) ) ;
146+
147+ const availableOperators : filterOperatorNames [ ]
148+ = [ 'gt' , 'gte' , 'lt' , 'lte' , 'ne' , 'in[]' , 'nin[]' ] ;
149+
150+ // if `opts.resolvers.[resolverName].filter.operators` is empty and not disabled via `false`
151+ // then fill it up with indexed fields
152+ const indexedFields = getIndexedFieldNames ( model ) ;
153+ if ( operatorsOpts !== false && Object . keys ( operatorsOpts ) . length === 0 ) {
154+ indexedFields . forEach ( fieldName => {
155+ operatorsOpts [ fieldName ] = availableOperators ; // eslint-disable-line
156+ } ) ;
157+ }
158+
159+ const existedFields = inputComposer . getFields ( ) ;
160+ Object . keys ( existedFields ) . forEach ( fieldName => {
161+ if ( operatorsOpts [ fieldName ] && operatorsOpts [ fieldName ] !== false ) {
162+ const fields = { } ;
163+ let operators ;
164+ if ( operatorsOpts [ fieldName ] && Array . isArray ( operatorsOpts [ fieldName ] ) ) {
165+ operators = operatorsOpts [ fieldName ] ;
166+ } else {
167+ operators = availableOperators ;
168+ }
169+ operators . forEach ( operatorName => {
170+ if ( operatorName . slice ( - 2 ) === '[]' ) {
171+ fields [ operatorName . slice ( 0 , - 2 ) ] = {
172+ ...existedFields [ fieldName ] ,
173+ // $FlowFixMe
174+ type : new GraphQLList ( getNamedType ( existedFields [ fieldName ] . type ) ) ,
175+ } ;
176+ } else {
177+ fields [ operatorName ] = getNamedType ( existedFields [ fieldName ] ) ;
178+ }
179+ } ) ;
180+ if ( Object . keys ( fields ) . length > 0 ) {
181+ operatorsComposer . addField ( fieldName , {
182+ type : new GraphQLInputObjectType ( {
183+ name : `${ upperFirst ( fieldName ) } ${ typeName } ` ,
184+ fields,
185+ } ) ,
186+ description : 'Filter value by operator(s)' ,
187+ } ) ;
188+ }
189+ }
190+ } ) ;
191+
192+ if ( Object . keys ( operatorsComposer . getFields ( ) ) . length > 0 ) {
193+ inputComposer . addField ( OPERATORS_FIELDNAME , {
194+ type : operatorsComposer . getType ( ) ,
195+ description : 'List of fields that can be filtered via operators' ,
196+ } ) ;
197+ }
198+ }
0 commit comments