docs: function (callback, term, from, size, sort, specs, ids, brands, categories, prices, dsl) {
          var host = apiSearch
          // proxy will pass XGET
          // var method = 'POST'
          var endpoint = '/items.json'
          var body
  
          if (typeof dsl === 'object' && dsl !== null) {
            // custom Query DSL
            // must be a valid search request body
            // https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html
            body = dsl
          } else {
            /*
            // term is required
            if (typeof term !== 'string') {
              msg = 'Search term is required and must be a string'
              errorHandling(callback, msg)
              return
            }
            */
  
            // handle results sorting
            var order = [
              {
                'available': {
                  'order': 'desc'
                }
              },
              {
                'ad_relevance': {
                  'order': 'desc'
                }
              },
              '_score'
            ]
  
            // defines most common sorting options
            switch (sort) {
              case 1:
                // sort by sales after relevance
                order.splice(2, 0, {
                  'sales': {
                    'order': 'desc'
                  }
                })
                break
  
              case 2:
                // sort by price
                // lowest price -> highest price
                order.splice(1, 0, {
                  'price': {
                    'order': 'asc'
                  }
                })
                break
  
              case 3:
                // sort by price
                // highest price -> lowest price
                order.splice(1, 0, {
                  'price': {
                    'order': 'desc'
                  }
                })
                break
  
              default:
                // default sort by views after preseted sorting options
                order.push({
                  'views': {
                    'order': 'desc'
                  }
                })
            }
  
            body = {
              'query': {
                'bool': {
                  // condition, only visible products
                  'filter': [
                    {
                      'term': {
                        'visible': true
                      }
                    }
                  ]
                }
              },
              'sort': order,
              'aggs': {
                'brands': {
                  'terms': {
                    'field': 'brands.name'
                  }
                },
                'categories': {
                  'terms': {
                    'field': 'categories.name'
                  }
                },
                // ref.: https://github.com/elastic/elasticsearch/issues/5789
                'specs': {
                  'nested': {
                    'path': 'specs'
                  },
                  'aggs': {
                    'grid': {
                      'terms': {
                        'field': 'specs.grid',
                        'size': 30
                      },
                      'aggs': {
                        'text': {
                          'terms': {
                            'field': 'specs.text'
                          }
                        }
                      }
                    }
                  }
                },
                // Metric Aggregations
                'min_price': {
                  'min': {
                    'field': 'price'
                  }
                },
                'max_price': {
                  'max': {
                    'field': 'price'
                  }
                },
                'avg_price': {
                  'avg': {
                    'field': 'price'
                  }
                }
              }
            }
  
            // search term
            if (typeof term === 'string') {
              // https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html
              body.query.bool.must = {
                'multi_match': {
                  'query': term,
                  'fields': [
                    'name',
                    'keywords'
                  ]
                }
              }
  
              // handle terms suggestion
              // 'did you mean?'
              // https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters.html
              body.suggest = {
                'text': term,
                'words': {
                  'term': {
                    'field': 'name'
                  }
                }
              }
            }
  
            // pagination
            // https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-from-size.html
            if (typeof from === 'number' && from > 0) {
              // offset
              body.from = from
            }
            if (typeof size === 'number' && size > 0) {
              // limit
              body.size = size
            } else {
              // default page size
              body.size = 24
            }
  
            // filters
            if (typeof specs === 'object' && specs !== null && Object.keys(specs).length > 0) {
              // nested ELS object
              // http://nocf-www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-nested-query.html
              for (var key in specs) {
                if (Array.isArray(specs[key]) && specs[key].length > 0) {
                  // add filter
                  body.query.bool.filter.push({
                    'nested': {
                      'path': 'specs',
                      'query': {
                        'bool': {
                          'filter': [
                            {
                              'term': {
                                'specs.grid': key
                              }
                            }, {
                              'terms': {
                                'specs.text': specs[key]
                              }
                            }
                          ]
                        }
                      }
                    }
                  })
                }
              }
            }
  
            if (Array.isArray(ids) && ids.length > 0) {
              // add filter
              // search by product Object IDs
              body.query.bool.filter.push({
                'terms': {
                  '_id': ids
                }
              })
            }
  
            // handle filter by categories and brands with IDs or names
            var filterFields = {
              'brands': brands,
              'categories': categories
            }
            for (var field in filterFields) {
              var filterVal = filterFields[field]
              if (Array.isArray(filterVal) && filterVal.length > 0) {
                if (/^[a-f0-9]{24}$/.test(filterVal[0])) {
                  // filtering by ObjectID
                  field += '._id'
                } else {
                  // filtering by name
                  field += '.name'
                }
  
                // add filter
                var filterObj = { 'terms': {} }
                filterObj.terms[field] = filterVal
                body.query.bool.filter.push(filterObj)
              }
            }
  
            // price ranges
            if (typeof prices === 'object' && prices !== null && Object.keys(prices).length > 0) {
              var rangeQuery = {
                'range': {
                  'price': {}
                }
              }
              if (typeof prices.min === 'number' && !isNaN(prices.min)) {
                rangeQuery.range.price.gte = prices.min
              }
              if (typeof prices.max === 'number' && !isNaN(prices.max)) {
                rangeQuery.range.price.lte = prices.max
              }
  
              // add filter
              body.query.bool.filter.push(rangeQuery)
            }
          }
  
          /*
          msg = logHeader('INFO') +
            '\nQuery DSL' +
            '\n' + JSON.stringify(body)
          logger.log(msg)
          */
          runMethod(callback, endpoint, host, body)
        }