# mquery

`mquery` is a fluent mongodb query builder designed to run in multiple environments.

[![Build Status](https://travis-ci.org/aheckmann/mquery.svg?branch=master)](https://travis-ci.org/aheckmann/mquery)
[![NPM version](https://badge.fury.io/js/mquery.svg)](http://badge.fury.io/js/mquery)

[![npm](https://nodei.co/npm/mquery.png)](https://www.npmjs.com/package/mquery)

## Features

- fluent query builder api
- custom base query support
- MongoDB 2.4 geoJSON support
- method + option combinations validation
- node.js driver compatibility
- environment detection
- [debug](https://github.com/visionmedia/debug) support
- separated collection implementations for maximum flexibility

## Use

```js
const mongo = require('mongodb');

const client = new mongo.MongoClient(uri);
await client.connect();
// get a collection
const collection = client.collection('artists');

// pass it to the constructor
await mquery(collection).find({...});

// or pass it to the collection method
const docs = await mquery().find({...}).collection(collection);

// or better yet, create a custom query constructor that has it always set
const Artist = mquery(collection).toConstructor();
const docs = await Artist().find(...).where(...);
```

`mquery` requires a collection object to work with. In the example above we just pass the collection object created using the official [MongoDB driver](https://github.com/mongodb/node-mongodb-native).

## Fluent API

- [mquery](#mquery)
  - [Features](#features)
  - [Use](#use)
  - [Fluent API](#fluent-api)
  - [Helpers](#helpers)
    - [find()](#find)
    - [findOne()](#findone)
    - [countDocuments()](#countdocuments)
    - [estimatedDocumentCount()](#estimateddocumentcount)
    - [findOneAndUpdate()](#findoneandupdate)
        - [findOneAndUpdate() options](#findoneandupdate-options)
    - [findOneAndReplace()](#findoneandreplace)
        - [findOneAndReplace() options](#findoneandreplace-options)
    - [findOneAndRemove()](#findoneandremove)
        - [findOneAndRemove() options](#findoneandremove-options)
    - [distinct()](#distinct)
    - [updateMany()](#updatemany)
    - [updateOne()](#updateone)
    - [replaceOne()](#replaceone)
    - [deleteOne()](#deleteone)
    - [deleteMany()](#deletemany)
    - [exec()](#exec)
    - [cursor()](#cursor)
    - [stream()](#stream)
    - [all()](#all)
    - [and()](#and)
    - [box()](#box)
    - [circle()](#circle)
    - [elemMatch()](#elemmatch)
    - [equals()](#equals)
    - [eq()](#eq)
    - [exists()](#exists)
    - [geometry()](#geometry)
    - [gt()](#gt)
    - [gte()](#gte)
    - [in()](#in)
    - [intersects()](#intersects)
    - [lt()](#lt)
    - [lte()](#lte)
    - [maxDistance()](#maxdistance)
    - [mod()](#mod)
    - [ne()](#ne)
    - [nin()](#nin)
    - [nor()](#nor)
    - [near()](#near)
      - [Example](#example)
    - [or()](#or)
    - [polygon()](#polygon)
    - [regex()](#regex)
    - [select()](#select)
        - [String syntax](#string-syntax)
    - [selected()](#selected)
    - [selectedInclusively()](#selectedinclusively)
    - [selectedExclusively()](#selectedexclusively)
    - [size()](#size)
    - [slice()](#slice)
    - [within()](#within)
    - [where()](#where)
    - [$where()](#where-1)
    - [batchSize()](#batchsize)
    - [collation()](#collation)
    - [comment()](#comment)
    - [hint()](#hint)
    - [j()](#j)
    - [limit()](#limit)
    - [maxTime()](#maxtime)
    - [skip()](#skip)
    - [sort()](#sort)
    - [read()](#read)
        - [Preferences:](#preferences)
        - [Preference Tags:](#preference-tags)
    - [readConcern()](#readconcern)
        - [Read Concern Level:](#read-concern-level)
    - [writeConcern()](#writeconcern)
        - [Write Concern:](#write-concern)
    - [slaveOk()](#slaveok)
    - [tailable()](#tailable)
    - [wtimeout()](#wtimeout)
  - [Helpers](#helpers-1)
    - [collection()](#collection)
    - [then()](#then)
    - [merge(object)](#mergeobject)
    - [setOptions(options)](#setoptionsoptions)
        - [setOptions() options](#setoptions-options)
    - [setTraceFunction(func)](#settracefunctionfunc)
    - [mquery.setGlobalTraceFunction(func)](#mquerysetglobaltracefunctionfunc)
    - [mquery.canMerge(conditions)](#mquerycanmergeconditions)
  - [mquery.use$geoWithin](#mqueryusegeowithin)
  - [Custom Base Queries](#custom-base-queries)
  - [Validation](#validation)
  - [Debug support](#debug-support)
  - [General compatibility](#general-compatibility)
      - [ObjectIds](#objectids)
      - [Read Preferences](#read-preferences)
  - [Future goals](#future-goals)
  - [Installation](#installation)
  - [License](#license)

## Helpers

- [collection](#collection)
- [then](#then)
- [merge](#mergeobject)
- [setOptions](#setoptionsoptions)
- [setTraceFunction](#settracefunctionfunc)
- [mquery.setGlobalTraceFunction](#mquerysetglobaltracefunctionfunc)
- [mquery.canMerge](#mquerycanmergeconditions)
- [mquery.use$geoWithin](#mqueryusegeowithin)

### find()

Declares this query a _find_ query. Optionally pass a match clause.

```js
mquery().find()
mquery().find(match)
await mquery().find()
const docs = await mquery().find(match);
assert(Array.isArray(docs));
```

### findOne()

Declares this query a _findOne_ query. Optionally pass a match clause.

```js
mquery().findOne()
mquery().findOne(match)
await mquery().findOne()
const doc = await mquery().findOne(match);
if (doc) {
  // the document may not be found
  console.log(doc);
}
```

### countDocuments()

Declares this query a _countDocuments_ query. Optionally pass a match clause.

```js
mquery().countDocuments()
mquery().countDocuments(match)
await mquery().countDocuments()
const number = await mquery().countDocuments(match);
console.log('we found %d matching documents', number);
```

### estimatedDocumentCount()

Declares this query an _estimatedDocumentCount_ query. Gets an estimated count of documents in a collection using collection metadata.

```js
mquery().estimatedDocumentCount()
const number = await mquery().estimatedDocumentCount();
console.log('estimated documents: %d', number);
```

### findOneAndUpdate()

Declares this query a _findAndModify_ with update query. Optionally pass a match clause, update document, options.

When executed, the first matching document (if found) is modified according to the update document and passed back.

#### findOneAndUpdate() options

Options are passed to the `setOptions()` method.

- `new`: boolean - true to return the modified document rather than the original. defaults to false
- `upsert`: boolean - creates the object if it doesn't exist. defaults to false
- `sort`: if multiple docs are found by the match condition, sets the sort order to choose which doc to update

```js
query.findOneAndUpdate()
query.findOneAndUpdate(updateDocument)
query.findOneAndUpdate(match, updateDocument)
query.findOneAndUpdate(match, updateDocument, options)

// the following all execute the command
await query.findOneAndUpdate()
await query.findOneAndUpdate(updateDocument)
await query.findOneAndUpdate(match, updateDocument)
const doc = await query.findOneAndUpdate(match, updateDocument, options);
if (doc) {
  // the document may not be found
  console.log(doc);
}
```

### findOneAndReplace()

Declares this query a _findOneAndReplace_ query. Finds a matching document, replaces it with the provided replacement, and returns the found document (if any).

#### findOneAndReplace() options

Options are passed to the `setOptions()` method.

- `new`: boolean - true to return the modified document rather than the original. defaults to false
- `upsert`: boolean - creates the object if it doesn't exist. defaults to false
- `sort`: if multiple docs are found by the match condition, sets the sort order to choose which doc to replace

```js
query.findOneAndReplace()
query.findOneAndReplace(replacement)
query.findOneAndReplace(match, replacement)
query.findOneAndReplace(match, replacement, options)

// the following all execute the command
await query.findOneAndReplace()
await query.findOneAndReplace(replacement)
await query.findOneAndReplace(match, replacement)
const doc = await query.findOneAndReplace(match, replacement, options);
if (doc) {
  // the document may not be found
  console.log(doc);
}
```

### findOneAndRemove()

Declares this query a _findAndModify_ with remove query. Alias of findOneAndDelete.
Optionally pass a match clause, options.

When executed, the first matching document (if found) is modified according to the update document, removed from the collection and passed as a result.

#### findOneAndRemove() options

Options are passed to the `setOptions()` method.

- `sort`: if multiple docs are found by the condition, sets the sort order to choose which doc to modify and remove

```js
A.where().findOneAndDelete()
A.where().findOneAndRemove()
A.where().findOneAndRemove(match)
A.where().findOneAndRemove(match, options)

// the following all execute the command
await A.where().findOneAndRemove()
await A.where().findOneAndRemove(match)
const doc = await A.where().findOneAndRemove(match, options);
if (doc) {
  // the document may not be found
  console.log(doc);
}
```

### distinct()

Declares this query a _distinct_ query. Optionally pass the distinct field, a match clause.

```js
mquery().distinct()
mquery().distinct(match)
mquery().distinct(match, field)
mquery().distinct(field)

// the following all execute the command
await mquery().distinct()
await mquery().distinct(field)
await mquery().distinct(match)
const result = await mquery().distinct(match, field);
console.log(result);
```

### updateMany()

Declares this query an _updateMany_ query. Updates all documents that match `criteria`.

When executed, the first argument is the query, and the second argument is the update document.

_All paths passed that are not $atomic operations will become $set ops._

```js
mquery().updateMany({ name: /^match/ }, { field: 'value' })
await mquery().updateMany({ name: /^match/ }, { field: 'value' })
await mquery().where({ name: /^match/ }).updateMany({ field: 'value' })
```

### updateOne()

Declares this query an _updateOne_ query. Updates only the first document that matches `criteria`.

When executed, the first argument is the query, and the second argument is the update document.

_All paths passed that are not $atomic operations will become $set ops._

```js
mquery().updateOne({ name: 'match' }, { field: 'value' })
await mquery().updateOne({ name: 'match' }, { field: 'value' })
await mquery().where({ name: 'match' }).updateOne({ field: 'value' })
```

### replaceOne()

Declares this query a _replaceOne_ query. Replaces the first document that matches `criteria` with the provided replacement document.

Similar to `updateOne()`, except `replaceOne()` is not allowed to use atomic modifiers (`$set`, `$push`, etc.). Calling `replaceOne()` will always replace the existing doc.

```js
mquery().replaceOne({ _id: 1 }, { name: 'new name', age: 25 })
await mquery().replaceOne({ _id: 1 }, { name: 'new name', age: 25 })
```

### deleteOne()

Declares this query a _deleteOne_ query. Deletes the first document that matches `criteria`.

```js
mquery().deleteOne({ name: 'match' })
await mquery().deleteOne({ name: 'match' })
await mquery().where({ name: 'match' }).deleteOne()
```

### deleteMany()

Declares this query a _deleteMany_ query. Deletes all documents that match `criteria`.

```js
mquery().deleteMany({ name: /^match/ })
await mquery().deleteMany({ name: /^match/ })
await mquery().where({ name: /^match/ }).deleteMany()
```

### exec()

Executes the query.

```js
const docs = await mquery().findOne().where('route').intersects(polygon).exec()
```

### cursor()

Returns a cursor for the given `find` query.

```js
const cursor = mquery().find({ name: /^match/ }).cursor();
cursor.on('data', function(doc) {
  console.log(doc);
});
cursor.on('end', function() {
  console.log('done');
});
```

Note: this only works with `find()` operations.

### stream()

Executes the query and returns a stream.

```js
var stream = mquery().find().stream(options);
stream.on('data', cb);
stream.on('close', fn);
```

Note: this only works with `find()` operations.

Note: returns the stream object directly from the node-mongodb-native driver. (currently streams1 type stream). Any options will be passed along to the [driver method](http://mongodb.github.io/node-mongodb-native/api-generated/cursor.html#stream).

---

### all()

Specifies an `$all` query condition

```js
mquery().where('permission').all(['read', 'write'])
```

[MongoDB documentation](http://docs.mongodb.org/manual/reference/operator/all/)

### and()

Specifies arguments for an `$and` condition

```js
mquery().and([{ color: 'green' }, { status: 'ok' }])
```

[MongoDB documentation](http://docs.mongodb.org/manual/reference/operator/and/)

### box()

Specifies a `$box` condition

```js
var lowerLeft = [40.73083, -73.99756]
var upperRight= [40.741404,  -73.988135]

mquery().where('location').within().box(lowerLeft, upperRight)
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/box/)

### circle()

Specifies a `$center` or `$centerSphere` condition.

```js
var area = { center: [50, 50], radius: 10, unique: true }
query.where('loc').within().circle(area)
query.circle('loc', area);

// for spherical calculations
var area = { center: [50, 50], radius: 10, unique: true, spherical: true }
query.where('loc').within().circle(area)
query.circle('loc', area);
```

- [MongoDB Documentation - center](http://docs.mongodb.org/manual/reference/operator/center/)
- [MongoDB Documentation - centerSphere](http://docs.mongodb.org/manual/reference/operator/centerSphere/)

### elemMatch()

Specifies an `$elemMatch` condition

```js
query.where('comment').elemMatch({ author: 'autobot', votes: {$gte: 5}})

query.elemMatch('comment', function (elem) {
  elem.where('author').equals('autobot');
  elem.where('votes').gte(5);
})
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/elemMatch/)

### equals()

Specifies the complementary comparison value for the path specified with `where()`.

```js
mquery().where('age').equals(49);

// is the same as

mquery().where({ 'age': 49 });
```

### eq()

Alias of `equals()`. Specifies the complementary comparison value for the path specified with `where()`.

```js
mquery().where('age').eq(49);

// is the same as

mquery().where('age').equals(49);

// is the same as

mquery().where({ 'age': 49 });
```

### exists()

Specifies an `$exists` condition

```js
// { name: { $exists: true }}
mquery().where('name').exists()
mquery().where('name').exists(true)
mquery().exists('name')

// { name: { $exists: false }}
mquery().where('name').exists(false);
mquery().exists('name', false);
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/exists/)

### geometry()

Specifies a `$geometry` condition

```js
var polyA = [[[ 10, 20 ], [ 10, 40 ], [ 30, 40 ], [ 30, 20 ]]]
query.where('loc').within().geometry({ type: 'Polygon', coordinates: polyA })

// or
var polyB = [[ 0, 0 ], [ 1, 1 ]]
query.where('loc').within().geometry({ type: 'LineString', coordinates: polyB })

// or
var polyC = [ 0, 0 ]
query.where('loc').within().geometry({ type: 'Point', coordinates: polyC })

// or
query.where('loc').intersects().geometry({ type: 'Point', coordinates: polyC })

// or
query.where('loc').near().geometry({ type: 'Point', coordinates: [3,5] })
```

`geometry()` **must** come after `intersects()`, `within()`, or `near()`.

The `object` argument must contain `type` and `coordinates` properties.

- type `String`
- coordinates `Array`

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/geometry/)

### gt()

Specifies a `$gt` query condition.

```js
mquery().where('clicks').gt(999)
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/gt/)

### gte()

Specifies a `$gte` query condition.

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/gte/)

```js
mquery().where('clicks').gte(1000)
```

### in()

Specifies an `$in` query condition.

```js
mquery().where('author_id').in([3, 48901, 761])
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/in/)

### intersects()

Declares an `$geoIntersects` query for `geometry()`.

```js
query.where('path').intersects().geometry({
    type: 'LineString'
  , coordinates: [[180.0, 11.0], [180, 9.0]]
})

// geometry arguments are supported
query.where('path').intersects({
    type: 'LineString'
  , coordinates: [[180.0, 11.0], [180, 9.0]]
})
```

**Must** be used after `where()`.

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/geoIntersects/)

### lt()

Specifies a `$lt` query condition.

```js
mquery().where('clicks').lt(50)
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/lt/)

### lte()

Specifies a `$lte` query condition.

```js
mquery().where('clicks').lte(49)
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/lte/)

### maxDistance()

Specifies a `$maxDistance` query condition.

```js
mquery().where('location').near({ center: [139, 74.3] }).maxDistance(5)
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/maxDistance/)

### mod()

Specifies a `$mod` condition

```js
mquery().where('count').mod(2, 0)
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/mod/)

### ne()

Specifies a `$ne` query condition.

```js
mquery().where('status').ne('ok')
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/ne/)

### nin()

Specifies an `$nin` query condition.

```js
mquery().where('author_id').nin([3, 48901, 761])
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/nin/)

### nor()

Specifies arguments for an `$nor` condition.

```js
mquery().nor([{ color: 'green' }, { status: 'ok' }])
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/nor/)

### near()

Specifies arguments for a `$near` or `$nearSphere` condition.

These operators return documents sorted by distance.

#### Example

```js
query.where('loc').near({ center: [10, 10] });
query.where('loc').near({ center: [10, 10], maxDistance: 5 });
query.near('loc', { center: [10, 10], maxDistance: 5 });

// GeoJSON
query.where('loc').near({ center: { type: 'Point', coordinates: [10, 10] }});
query.where('loc').near({ center: { type: 'Point', coordinates: [10, 10] }, maxDistance: 5, spherical: true });
query.where('loc').near().geometry({ type: 'Point', coordinates: [10, 10] });

// For a $nearSphere condition, pass the `spherical` option.
query.near({ center: [10, 10], maxDistance: 5, spherical: true });
```

[MongoDB Documentation](http://www.mongodb.org/display/DOCS/Geospatial+Indexing)

### or()

Specifies arguments for an `$or` condition.

```js
mquery().or([{ color: 'red' }, { status: 'emergency' }])
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/or/)

### polygon()

Specifies a `$polygon` condition

```js
mquery().where('loc').within().polygon([10,20], [13, 25], [7,15])
mquery().polygon('loc', [10,20], [13, 25], [7,15])
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/polygon/)

### regex()

Specifies a `$regex` query condition.

```js
mquery().where('name').regex(/^sixstepsrecords/)
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/regex/)

### select()

Specifies which document fields to include or exclude

```js
// 1 means include, 0 means exclude
mquery().select({ name: 1, address: 1, _id: 0 })

// or

mquery().select('name address -_id')
```

#### String syntax

When passing a string, prefixing a path with `-` will flag that path as excluded. When a path does not have the `-` prefix, it is included.

```js
// include a and b, exclude c
query.select('a b -c');

// or you may use object notation, useful when
// you have keys already prefixed with a "-"
query.select({a: 1, b: 1, c: 0});
```

_Cannot be used with `distinct()`._

### selected()

Determines if the query has selected any fields.

```js
var query = mquery();
query.selected() // false
query.select('-name');
query.selected() // true
```

### selectedInclusively()

Determines if the query has selected any fields inclusively.

```js
var query = mquery().select('name');
query.selectedInclusively() // true

var query = mquery();
query.selected() // false
query.select('-name');
query.selectedInclusively() // false
query.selectedExclusively() // true
```

### selectedExclusively()

Determines if the query has selected any fields exclusively.

```js
var query = mquery().select('-name');
query.selectedExclusively() // true

var query = mquery();
query.selected() // false
query.select('name');
query.selectedExclusively() // false
query.selectedInclusively() // true
```

### size()

Specifies a `$size` query condition.

```js
mquery().where('someArray').size(6)
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/size/)

### slice()

Specifies a `$slice` projection for a `path`

```js
mquery().where('comments').slice(5)
mquery().where('comments').slice(-5)
mquery().where('comments').slice([-10, 5])
```

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/projection/slice/)

### within()

Sets a `$geoWithin` or `$within` argument for geo-spatial queries.

```js
mquery().within().box()
mquery().within().circle()
mquery().within().geometry()

mquery().where('loc').within({ center: [50,50], radius: 10, unique: true, spherical: true });
mquery().where('loc').within({ box: [[40.73, -73.9], [40.7, -73.988]] });
mquery().where('loc').within({ polygon: [[],[],[],[]] });

mquery().where('loc').within([], [], []) // polygon
mquery().where('loc').within([], []) // box
mquery().where('loc').within({ type: 'LineString', coordinates: [...] }); // geometry
```

As of mquery 2.0, `$geoWithin` is used by default. This impacts you if running MongoDB < 2.4. To alter this behavior, see [mquery.use$geoWithin](#mqueryusegeowithin).

**Must** be used after `where()`.

[MongoDB Documentation](http://docs.mongodb.org/manual/reference/operator/geoWithin/)

### where()

Specifies a `path` for use with chaining

```js
// instead of writing:
mquery().find({age: {$gte: 21, $lte: 65}});

// we can instead write:
mquery().where('age').gte(21).lte(65);

// passing query conditions is permitted too
mquery().find().where({ name: 'vonderful' })

// chaining
await mquery()
  .where('age').gte(21).lte(65)
  .where({ 'name': /^vonderful/i })
  .where('friends').slice(10)
  .exec()
```

### $where()

Specifies a `$where` condition.

Use `$where` when you need to select documents using a JavaScript expression.

```js
await query.$where('this.comments.length > 10 || this.name.length > 5').exec()

query.$where(function () {
  return this.comments.length > 10 || this.name.length > 5;
})
```

Only use `$where` when you have a condition that cannot be met using other MongoDB operators like `$lt`. Be sure to read about all of [its caveats](http://docs.mongodb.org/manual/reference/operator/where/) before using.

---

### batchSize()

Specifies the batchSize option.

```js
query.batchSize(100)
```

_Cannot be used with `distinct()`._

[MongoDB documentation](http://docs.mongodb.org/manual/reference/method/cursor.batchSize/)

### collation()

Specifies the collation option.

```js
query.collation({ locale: "en_US", strength: 1 })
```

[MongoDB documentation](https://docs.mongodb.com/manual/reference/method/cursor.collation/#cursor.collation)

### comment()

Specifies the comment option.

```js
query.comment('login query');
```

_Cannot be used with `distinct()`._

[MongoDB documentation](http://docs.mongodb.org/manual/reference/operator/)

### hint()

Sets query hints.

```js
mquery().hint({ indexA: 1, indexB: -1 })
```

_Cannot be used with `distinct()`._

[MongoDB documentation](http://docs.mongodb.org/manual/reference/operator/hint/)

### j()

Requests acknowledgement that this operation has been persisted to MongoDB's on-disk journal.

This option is only valid for operations that write to the database:

- `deleteOne()`
- `deleteMany()`
- `findOneAndDelete()`
- `findOneAndUpdate()`
- `updateOne()`
- `updateMany()`

Defaults to the `j` value if it is specified in [writeConcern](#writeconcern)

```js
mquery().j(true);
```

### limit()

Specifies the limit option.

```js
query.limit(20)
```

_Cannot be used with `distinct()`._

[MongoDB documentation](http://docs.mongodb.org/manual/reference/method/cursor.limit/)

### maxTime()

Specifies the maxTimeMS option.

```js
query.maxTime(100)
query.maxTimeMS(100)
```

[MongoDB documentation](http://docs.mongodb.org/manual/reference/method/cursor.maxTimeMS/)

### skip()

Specifies the skip option.

```js
query.skip(100).limit(20)
```

_Cannot be used with `distinct()`._

[MongoDB documentation](http://docs.mongodb.org/manual/reference/method/cursor.skip/)

### sort()

Sets the query sort order.

If an object is passed, key values allowed are `asc`, `desc`, `ascending`, `descending`, `1`, and `-1`.

If a string is passed, it must be a space delimited list of path names. The sort order of each path is ascending unless the path name is prefixed with `-` which will be treated as descending.

```js
// these are equivalent
query.sort({ field: 'asc', test: -1 });
query.sort('field -test');
```

_Cannot be used with `distinct()`._

[MongoDB documentation](http://docs.mongodb.org/manual/reference/method/cursor.sort/)

### read()

Sets the readPreference option for the query.

```js
mquery().read('primary')
mquery().read('p')  // same as primary

mquery().read('primaryPreferred')
mquery().read('pp') // same as primaryPreferred

mquery().read('secondary')
mquery().read('s')  // same as secondary

mquery().read('secondaryPreferred')
mquery().read('sp') // same as secondaryPreferred

mquery().read('nearest')
mquery().read('n')  // same as nearest

mquery().setReadPreference('primary') // alias of .read()
```

#### Preferences:

- `primary` - (default) Read from primary only. Operations will produce an error if primary is unavailable. Cannot be combined with tags.
- `secondary` - Read from secondary if available, otherwise error.
- `primaryPreferred` - Read from primary if available, otherwise a secondary.
- `secondaryPreferred` - Read from a secondary if available, otherwise read from the primary.
- `nearest` - All operations read from among the nearest candidates, but unlike other modes, this option will include both the primary and all secondaries in the random selection.

Aliases

- `p`   primary
- `pp`  primaryPreferred
- `s`   secondary
- `sp`  secondaryPreferred
- `n`   nearest

#### Preference Tags:

To keep the separation of concerns between `mquery` and your driver
clean, `mquery#read()` no longer handles specifying a second `tags` argument as of version 0.5.
If you need to specify tags, pass any non-string argument as the first argument.
`mquery` will pass this argument untouched to your collections methods later.
For example:

```js
// example of specifying tags using the Node.js driver
var ReadPref = require('mongodb').ReadPreference;
var preference = new ReadPref('secondary', [{ dc:'sf', s: 1 },{ dc:'ma', s: 2 }]);
mquery(...).read(preference).exec();
```

Read more about how to use read preferences [here](http://docs.mongodb.org/manual/applications/replication/#read-preference) and [here](http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences).

### readConcern()

Sets the readConcern option for the query.

```js
// local
mquery().readConcern('local')
mquery().readConcern('l')
mquery().r('l')

// available
mquery().readConcern('available')
mquery().readConcern('a')
mquery().r('a')

// majority
mquery().readConcern('majority')
mquery().readConcern('m')
mquery().r('m')

// linearizable
mquery().readConcern('linearizable')
mquery().readConcern('lz')
mquery().r('lz')

// snapshot
mquery().readConcern('snapshot')
mquery().readConcern('s')
mquery().r('s')
```

#### Read Concern Level:

- `local` - The query returns from the instance with no guarantee guarantee that the data has been written to a majority of the replica set members (i.e. may be rolled back). (MongoDB 3.2+)
- `available` - The query returns from the instance with no guarantee guarantee that the data has been written to a majority of the replica set members (i.e. may be rolled back). (MongoDB 3.6+)
- `majority` - The query returns the data that has been acknowledged by a majority of the replica set members. The documents returned by the read operation are durable, even in the event of failure. (MongoDB 3.2+)
- `linearizable` - The query returns data that reflects all successful majority-acknowledged writes that completed prior to the start of the read operation. The query may wait for concurrently executing writes to propagate to a majority of replica set members before returning results. (MongoDB 3.4+)
- `snapshot` - Only available for operations within multi-document transactions. Upon transaction commit with write concern "majority", the transaction operations are guaranteed to have read from a snapshot of majority-committed data. (MongoDB 4.0+)

Aliases

- `l`   local
- `a`   available
- `m`   majority
- `lz`  linearizable
- `s`   snapshot

Read more about how to use read concern [here](https://docs.mongodb.com/manual/reference/read-concern/).

### writeConcern()

Sets the writeConcern option for the query.

This option is only valid for operations that write to the database:

- `deleteOne()`
- `deleteMany()`
- `findOneAndDelete()`
- `findOneAndUpdate()`
- `updateOne()`
- `updateMany()`

```js
mquery().writeConcern(0)
mquery().writeConcern(1)
mquery().writeConcern({ w: 1, j: true, wtimeout: 2000 })
mquery().writeConcern('majority')
mquery().writeConcern('m') // same as majority
mquery().writeConcern('tagSetName') // if the tag set is 'm', use .writeConcern({ w: 'm' }) instead
mquery().w(1) // w is alias of writeConcern
```

#### Write Concern:

writeConcern({ w: `<value>`, j: `<boolean>`, wtimeout: `<number>` }`)

- the w option to request acknowledgement that the write operation has propagated to a specified number of mongod instances or to mongod instances with specified tags
- the j option to request acknowledgement that the write operation has been written to the journal
- the wtimeout option to specify a time limit to prevent write operations from blocking indefinitely

Can be break down to use the following syntax:

mquery().w(`<value>`).j(`<boolean>`).wtimeout(`<number>`)

Read more about how to use write concern [here](https://docs.mongodb.com/manual/reference/write-concern/)

### slaveOk()

Sets the slaveOk option. `true` allows reading from secondaries.

**deprecated** use [read()](#read) preferences instead if on mongodb >= 2.2

```js
query.slaveOk() // true
query.slaveOk(true)
query.slaveOk(false)
```

[MongoDB documentation](http://docs.mongodb.org/manual/reference/method/rs.slaveOk/)

### tailable()

Sets tailable option.

```js
mquery().tailable() <== true
mquery().tailable(true)
mquery().tailable(false)
```

_Cannot be used with `distinct()`._

[MongoDB Documentation](http://docs.mongodb.org/manual/tutorial/create-tailable-cursor/)

### wtimeout()

Specifies a time limit, in milliseconds, for the write concern. If `w > 1`, it is maximum amount of time to
wait for this write to propagate through the replica set before this operation fails. The default is `0`, which means no timeout.

This option is only valid for operations that write to the database:

- `deleteOne()`
- `deleteMany()`
- `findOneAndDelete()`
- `findOneAndUpdate()`
- `updateOne()`
- `updateMany()`

Defaults to `wtimeout` value if it is specified in [writeConcern](#writeconcern)

```js
mquery().wtimeout(2000)
mquery().wTimeout(2000)
```

## Helpers

### collection()

Sets the querys collection.

```js
mquery().collection(aCollection)
```

### then()

Executes the query and returns a promise which will be resolved with the query results or rejected if the query responds with an error.

```js
mquery().find(..).then(success, error);
```

This is very useful when combined with [co](https://github.com/visionmedia/co) or [koa](https://github.com/koajs/koa), which automatically resolve promise-like objects for you.

```js
co(function*(){
  var doc = yield mquery().findOne({ _id: 499 });
  console.log(doc); // { _id: 499, name: 'amazing', .. }
})();
```

_NOTE_:
The returned promise is a [bluebird](https://github.com/petkaantonov/bluebird/) promise but this is customizable. If you want to
use your favorite promise library, simply set `mquery.Promise = YourPromiseConstructor`.
Your `Promise` must be [promises A+](http://promisesaplus.com/) compliant.

### merge(object)

Merges other mquery or match condition objects into this one. When an mquery instance is passed, its match conditions, field selection and options are merged.

```js
const drum = mquery({ type: 'drum' }).collection(instruments);
const redDrum = mquery({ color: 'red' }).merge(drum);
const n = await redDrum.count();
console.log('there are %d red drums', n);
```

Internally uses `mquery.canMerge` to determine validity.

### setOptions(options)

Sets query options.

```js
mquery().setOptions({ collection: coll, limit: 20 })
```

#### setOptions() options

- [tailable](#tailable) *
- [sort](#sort) *
- [limit](#limit) *
- [skip](#skip) *
- [maxTime](#maxtime) *
- [batchSize](#batchsize) *
- [comment](#comment) *
- [hint](#hint) *
- [collection](#collection): the collection to query against

_* denotes a query helper method is also available_

### setTraceFunction(func)

Set a function to trace this query. Useful for profiling or logging.

```js
function traceFunction (method, queryInfo, query) {
  console.log('starting ' + method + ' query');

  return function (err, result, millis) {
    console.log('finished ' + method + ' query in ' + millis + 'ms');
  };
}

mquery().setTraceFunction(traceFunction).findOne({name: 'Joe'}, cb);
```

The trace function is passed (method, queryInfo, query)

- method is the name of the method being called (e.g. findOne)
- queryInfo contains information about the query:
  - conditions: query conditions/criteria
  - options: options such as sort, fields, etc
  - doc: document being updated
- query is the query object

The trace function should return a callback function which accepts:
- err: error, if any
- result: result, if any
- millis: time spent waiting for query result

NOTE: stream requests are not traced.

### mquery.setGlobalTraceFunction(func)

Similar to `setTraceFunction()` but automatically applied to all queries.

```js
mquery.setTraceFunction(traceFunction);
```

### mquery.canMerge(conditions)

Determines if `conditions` can be merged using `mquery().merge()`.

```js
var query = mquery({ type: 'drum' });
var okToMerge = mquery.canMerge(anObject)
if (okToMerge) {
  query.merge(anObject);
}
```

## mquery.use$geoWithin

MongoDB 2.4 introduced the `$geoWithin` operator which replaces and is 100% backward compatible with `$within`. As of mquery 0.2, we default to using `$geoWithin` for all `within()` calls.

If you are running MongoDB < 2.4 this will be problematic. To force `mquery` to be backward compatible and always use `$within`, set the `mquery.use$geoWithin` flag to `false`.

```js
mquery.use$geoWithin = false;
```

## Custom Base Queries

Often times we want custom base queries that encapsulate predefined criteria. With `mquery` this is easy. First create the query you want to reuse and call its `toConstructor()` method which returns a new subclass of `mquery` that retains all options and criteria of the original.

```js
var greatMovies = mquery(movieCollection).where('rating').gte(4.5).toConstructor();

// use it!
const n = await greatMovies().count();
console.log('There are %d great movies', n);

const docs = await greatMovies().where({ name: /^Life/ }).select('name').find();
console.log(docs);
```

## Validation

Method and options combinations are checked for validity at runtime to prevent creation of invalid query constructs. For example, a `distinct` query does not support specifying options like `hint` or field selection. In this case an error will be thrown so you can catch these mistakes in development.

## Debug support

Debug mode is provided through the use of the [debug](https://github.com/visionmedia/debug) module. To enable:

```sh
DEBUG=mquery node yourprogram.js
```

Read the debug module documentation for more details.

## General compatibility

### ObjectIds

`mquery` clones query arguments before passing them to a `collection` method for execution.
This prevents accidental side-affects to the objects you pass.
To clone `ObjectIds` we need to make some assumptions.

First, to check if an object is an `ObjectId`, we check its constructors name. If it matches either
`ObjectId` or `ObjectID` we clone it.

To clone `ObjectIds`, we call its optional `clone` method. If a `clone` method does not exist, we fall
back to calling `new obj.constructor(obj.id)`. We assume, for compatibility with the
Node.js driver, that the `ObjectId` instance has a public `id` property and that
when creating an `ObjectId` instance we can pass that `id` as an argument.

#### Read Preferences

`mquery` supports specifying [Read Preferences](https://www.mongodb.com/docs/manual/core/read-preference/) to control from which MongoDB node your query will read.
The Read Preferences spec also support specifying tags. To pass tags, some
drivers (Node.js driver) require passing a special constructor that handles both the read preference and its tags.
If you need to specify tags, pass an instance of your drivers ReadPreference constructor or roll your own. `mquery` will store whatever you provide and pass later to your collection during execution.

## Future goals

- mongo shell compatibility
- browser compatibility

## Installation

```sh
npm install mquery
```

## License

[MIT](https://github.com/aheckmann/mquery/blob/master/LICENSE)
