├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENCE.md ├── Makefile ├── README.md ├── SUMMARY.md ├── assets ├── favicon.ico └── monk.png ├── book.json ├── docs ├── Debugging.md ├── GETTING_STARTED.md ├── collection │ ├── README.md │ ├── aggregate.md │ ├── bulkWrite.md │ ├── count.md │ ├── createIndex.md │ ├── distinct.md │ ├── drop.md │ ├── dropIndex.md │ ├── dropIndexes.md │ ├── find.md │ ├── findOne.md │ ├── findOneAndDelete.md │ ├── findOneAndUpdate.md │ ├── geoHaystackSearch.md │ ├── geoNear.md │ ├── group.md │ ├── indexes.md │ ├── insert.md │ ├── mapReduce.md │ ├── remove.md │ ├── stats.md │ └── update.md ├── id.md ├── manager │ ├── README.md │ ├── addMiddleware.md │ ├── close.md │ ├── create.md │ └── get.md ├── middlewares.md └── writing-middleware.md ├── index.d.ts ├── lib ├── applyMiddlewares.js ├── collection.js ├── compose.js ├── helpers.js ├── manager.js └── monk.js ├── middlewares ├── castIds │ ├── README.md │ ├── index.js │ └── package.json ├── fields │ ├── README.md │ ├── index.js │ └── package.json ├── handle-callback │ ├── README.md │ ├── index.js │ └── package.json ├── options │ ├── README.md │ ├── index.js │ └── package.json ├── query │ ├── README.md │ ├── index.js │ └── package.json └── wait-for-connection │ ├── README.md │ ├── index.js │ └── package.json ├── package.json └── test ├── casting.js ├── collection.js └── monk.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | 5 | # Change these settings to your own preference 6 | indent_style = space 7 | indent_size = 2 8 | 9 | # We recommend you to keep these unchanged 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | 15 | [*.md] 16 | insert_final_newline = false 17 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["standard", "plugin:ava/recommended"], 3 | "ignorePath": ".gitignore", 4 | "plugins": [ 5 | "ava" 6 | ], 7 | "rules": { 8 | "ava/prefer-async-await": 0 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | package-lock.json 17 | 18 | coverage 19 | .nyc_output 20 | 21 | _book 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | - 9 5 | - 10 6 | - 11 7 | - 12 8 | services: mongodb 9 | cache: 10 | directories: 11 | - node_modules 12 | after_success: npm run coverage 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 7.3.3 / 2021-04-15 2 | 3 | - Fixes bug in .d.ts 4 | 5 | # 7.3.3 / 2021-01-27 6 | 7 | - Fixes bug in .d.ts where all queries are assumed to be rawCursor queries (Thanks @zkldi!) 8 | 9 | # 7.3.2 / 2020-08-31 10 | 11 | - Adjust some TypeScript definitions (Thanks @cupcakearmy!) 12 | 13 | # 7.3.1 / 2020-07-30 14 | 15 | - Adjust some TypeScript definitions (Thanks @cupcakearmy!) 16 | 17 | # 7.3.0 / 2020-04-28 18 | 19 | - Adjust some TypeScript definitions 20 | - Update docs for projections (Thanks @mpatino117!) 21 | - Update the `count` method and add a new `estimate` option (Thanks @bit-cmdr!) 22 | 23 | # 7.2.0 / 2020-04-16 24 | 25 | - Add `replace` option to update methods (Thanks @lakshmipriyamukundan!) 26 | 27 | # 7.1.1 / 2019-10-11 28 | 29 | - Allow for `mongo+srv` uri (Thanks @s-zimm!) 30 | 31 | # 7.1.0 / 2019-09-30 32 | 33 | - Fix `insert` deprecation warning (Thanks @RobertoMachorro!) 34 | - Default to using `useUnifiedTopology` option (Thanks @bobmoff!) 35 | - `collection.findOneAndUpdate` now _requires_ an atomic operator in the update object (Thanks @mpangrazzi!) 36 | 37 | # 7.0.0 / 2019-05-13 38 | 39 | - Update mongo to v3 (Thanks @mpangrazzi!) 40 | - Remove `collection.geoNear` (Thanks @mpangrazzi!) 41 | - `collection.findOneAndUpdate` now _requires_ an atomic operator in the update object (Thanks @mpangrazzi!) 42 | 43 | # 6.0.6 / 2018-05-10 44 | 45 | - Change existing function declarations from properties to functions in order to be able to supply additional typescript definitions to handle a callback scenario 46 | 47 | # 6.0.5 / 2017-10-06 48 | 49 | - Make `monk.get('collection')` a typescript template so that every methods have more specific types 50 | - Make some types more specifics (`createIndex`, `drop`, `dropIndex`, `dropIndexes`, `indexes`, `remove`, `stats`, `update`) 51 | 52 | # 6.0.4 / 2017-09-11 53 | 54 | - Fix typescript types for `find` (fix #224) 55 | 56 | # 6.0.3 / 2017-07-31 57 | 58 | - Really export Manager as `default` as well 59 | 60 | # 6.0.2 / 2017-07-31 61 | 62 | - Fix typescript types 63 | - Export Manager as `default` as well 64 | 65 | # 6.0.1 / 2017-06-22 66 | 67 | - Fix typo in `update` method name 68 | 69 | # 6.0.0 / 2017-06-04 70 | 71 | - Add typescript definition (fix #204) 72 | - return null when findOneAnd... find nothing (fix #175) 73 | - `remove` now uses `deleteOne` or `deleteMany` under the hood (fix #178) 74 | - Add `mapReduce` method (fix #167) 75 | - Add `geoHaystackSearch` method 76 | - Add `geoNear` method 77 | - Add `stats` method (fix #191) 78 | - Remove monk specific options used by middleware (fix #203) 79 | - The only option set globally is `castIds`. `safe` is not `true` by default anymore. 80 | - Return the cursor in a promise (when using the `rawCursor` on the `find` method) so that the entire API has the same return type (a promise) 81 | 82 | # 5.0.2 / 2017-05-22 83 | 84 | - Emit event from the manager from the underlying db emits an event (fix #189) 85 | 86 | # 5.0.1 / 2017-05-21 87 | 88 | - Fix typo on requesting middlewares 89 | 90 | # 5.0.0 / 2017-05-21 91 | 92 | - Remove deprecated methods 93 | - Middleware architecture! See https://automattic.github.io/monk/docs/middlewares.html for more information 94 | 95 | # 4.1.0 / 2017-05-20 96 | 97 | - Update dev dependencies - Thanks @ratson(◕ᴥ◕) 98 | - Add `collection.createIndex` and deprecate `collection.index` and `collection.ensureIndex` 99 | 100 | # 4.0.0 / 2017-02-13 101 | 102 | - Remove default `safe` option (fix #180) 103 | 104 | # 3.1.4 / 2017-01-30 105 | 106 | - delete wrong options for ensureIndex (fix #174) Thanks @kos984 107 | 108 | # 3.1.3 / 2016-10-12 109 | 110 | - Added a check to ensure no crash in ` cast()`` when `\_id` is undefined (fix #165) Thanks @JoelParke 111 | 112 | # 3.1.2 / 2016-08-22 113 | 114 | - Fix `collection.count` and `collection.distinct` are ignoring options (fix #159) 115 | 116 | # 3.1.1 / 2016-07-29 117 | 118 | - Provide option to not cache collections (fix #21) 119 | 120 | # 3.1.0 / 2016-07-22 121 | 122 | - Add `Collection.group` (fix #63) 123 | - Add `Collection.bulkWrite` (fix #85) 124 | - Pipe `mongodb.Logger` to `debug` (fix #143) 125 | 126 | # 3.0.7 / 2016-07-14 127 | 128 | - Wait for the last 'each' (in `find`) to `resume` or `close` before resolving the promise 129 | 130 | # 3.0.6 / 2016-07-11 131 | 132 | - Fix when casting `null` 133 | 134 | # 3.0.5 / 2016-07-11 135 | 136 | - Fix when updating with `0` 137 | 138 | # 3.0.4 / 2016-07-07 139 | 140 | - Do not fail when inserting empty array 141 | 142 | # 3.0.3 / 2016-07-06 143 | 144 | - Cast `_id` recursively (fix #3) 145 | 146 | # 3.0.2 / 2016-07-06 147 | 148 | - Fix find cursor close when already paused 149 | 150 | # 3.0.1 / 2016-07-06 151 | 152 | - Fix find cursor pause and resume 153 | 154 | # 3.0.0 / 2016-07-06 155 | 156 | - remove Mongoskin dependency 157 | - new documentation using gitbook 158 | - add `opts` arg to `Collection.count` and `collection.distinct` 159 | - deprecate `Collection.removeById`, `Collection.findById`, `Collection.updateById` in favor of using `remove`, `findOne` and `update` directly 160 | - deprecate `collection.id` and `manager.id` in favor of `monk.id` 161 | - `monk('localhost')` can be used as a promise which resolves when the connection opens and rejects when it throws an error (fix #24, fix #10) 162 | - deprecate `Collection.findAndModify` in favor of `Collection.findOneAndDelete` and `Collection.findOneAndUpdate` (fix #74) 163 | - add `Manager.create` (fix #50) 164 | - add option `rawCursor` to `Collection.find` to return the raw cursor (fix #103) 165 | 166 | # 2.1.0 / 2016-06-24 167 | 168 | - add aggregate method (#56) 169 | - add dropIndex and dropIndexes methods (#113) 170 | - use `util.inherits` instead of `__proto__` 171 | - Add `castIds` option to disable the automatic id casting (#1, #72) 172 | 173 | # 2.0.1 / 2016-06-24 174 | 175 | - Safer insert (#137) 176 | 177 | # 2.0.0 / 2016-06-23 178 | 179 | complete rewrite of monk: 180 | 181 | - return real promises (#104) 182 | - update mongoskin and mongodb (#111) 183 | - auto binding of the methods 184 | - eslint 185 | - test coverage 186 | - Support $ne, $in, \$nin for id casting 187 | - Make the sort option behave like fields 188 | - `Collection.update` now return an object: 189 | 190 | ``` 191 | { 192 | n: number of matched documents, 193 | nModified: number of modified documents, 194 | nUpserted: number of upserted documents 195 | } 196 | ``` 197 | 198 | # 1.0.1 / 2015-03-25 199 | 200 | - upgrade; mongoskin to 1.4.13 201 | 202 | # 0.9.2 / 2015-02-28 203 | 204 | - mongoskin: bump to 1.4.11 205 | - Inserting an array returns an array 206 | - Cast oids inside of \$nor queries 207 | - Cast object ids inside of $or and $and queries 208 | - Cast object ids inside of \$not queries 209 | - Added a missing test for updateById 210 | - Added removeById 211 | - Use `setImmediate` on node 0.10.x 212 | 213 | # 0.9.1 / 2014-11-15 214 | 215 | - update mongoskin to 1.4.4 216 | 217 | # 0.9.0 / 2014-05-09 218 | 219 | - addition of `close()` method 220 | - updaet mongoskin 1.4.1 221 | - fixed URL parsing of replsets 222 | - freezed mpromise version 223 | - fixed collection distinct after rebase 224 | - reimplemented Monk.Promise with MPromise. 225 | 226 | # 0.8.1 / 2014-03-01 227 | 228 | - fix for parameter handling in `findAndModify` 229 | - check for `uri` parameter or throw 230 | 231 | # 0.8.0 / 2014-03-01 232 | 233 | - added `distinct` support (fixes #52) 234 | - added `Promise#then` 235 | 236 | # 0.7.1 / 2013-03-03 237 | 238 | - promise: expose `query` 239 | 240 | # 0.7.0 / 2012-10-30 241 | 242 | \*: bumped `mongoskin` and therefore `node-mongodb-native` 243 | 244 | # 0.6.0 / 2012-10-29 245 | 246 | - collection: added cursor closing support 247 | - promise: introduce #destroy 248 | - test: added cursor destroy test 249 | 250 | # 0.5.0 / 2012-10-03 251 | 252 | - promise: added opts to constructor 253 | - util: fix field negation 254 | - test: added test for promise options 255 | - collection: pass options to promises 256 | 257 | # 0.4.0 / 2012-10-03 258 | 259 | - added travis 260 | - manager: added Manager#id and Manager#oid 261 | - collection: introduced Collection#oid 262 | - manager: added Manager#col 263 | 264 | # 0.3.0 / 2012-09-06 265 | 266 | - collection: make `findAndModify` accept an oid as the query 267 | 268 | # 0.2.1 / 2012-07-14 269 | 270 | - collection: fixed streaming when options are not supplied 271 | 272 | # 0.2.0 / 2012-07-14 273 | 274 | - collection: added `count` 275 | 276 | # 0.1.15 / 2012-07-14 277 | 278 | - collection: avoid mongoskin warn when buffering commands 279 | 280 | # 0.1.14 / 2012-07-09 281 | 282 | - Use any debug. [visionmedia] 283 | - Use any mocha. [visionmedia] 284 | 285 | # 0.1.13 / 2012-05-28 286 | 287 | - Fixed string-based field selection. 288 | 289 | # 0.1.12 / 2012-05-25 290 | 291 | - Added package.json tags. 292 | - Added support for update with ids (fixes #4) 293 | 294 | # 0.1.11 / 2012-05-22 295 | 296 | - Added support for new objectids through `Collection#id` 297 | 298 | # 0.1.10 / 2012-05-21 299 | 300 | - Enhanced findAndModify default behavior for upserts. 301 | - Fixed findAndModify. 302 | 303 | # 0.1.9 / 2012-05-16 304 | 305 | - Bumped mongoskin 306 | 307 | # 0.1.8 / 2012-05-12 308 | 309 | - Fixed mongoskin version 310 | - Improved options docs section. 311 | 312 | # 0.1.7 / 2012-05-08 313 | 314 | - Added global and collection-level options. 315 | - Enabled safe mode by default. 316 | - Improved error handling in tests. 317 | - Fixed `update` callback with safe: false. 318 | 319 | # 0.1.6 / 2012-05-06 320 | 321 | - Added tests for `findById`. 322 | 323 | # 0.1.5 / 2012-05-06 324 | 325 | - Added `Collection` references to `Promise`s. 326 | - Fixed `findAndModify`. 327 | 328 | # 0.1.4 / 2012-05-06 329 | 330 | - Ensured insert calls back with a single object. 331 | - Ensured `insert` resolves promise in next tick. 332 | 333 | # 0.1.3 / 2012-05-03 334 | 335 | - Exposed `util` 336 | 337 | # 0.1.2 / 2012-05-03 338 | 339 | - Make `Collection` inherit from `EventEmitter`. 340 | 341 | # 0.1.1 / 2012-04-27 342 | 343 | - Added `updateById`. 344 | 345 | # 0.1.0 / 2012-04-23 346 | 347 | - Initial release. 348 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012 Guillermo Rauch <guillermo@learnboost.com> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN_DIR ?= node_modules/.bin 2 | P="\\033[34m[+]\\033[0m" 3 | 4 | SRC_DIR ?= src 5 | TEST_TARGET ?= test/ 6 | 7 | lint: 8 | echo " $(P) Linting" 9 | $(BIN_DIR)/eslint lib && $(BIN_DIR)/eslint test && $(BIN_DIR)/eslint middlewares 10 | 11 | test: lint 12 | echo " $(P) Testing" 13 | NODE_ENV=test $(BIN_DIR)/nyc --all $(BIN_DIR)/ava 14 | 15 | test-watch: 16 | echo " $(P) Testing forever" 17 | NODE_ENV=test $(BIN_DIR)/ava --watch 18 | 19 | docs-clean: 20 | echo " $(P) Cleaning gitbook" 21 | rm -rf _book 22 | 23 | docs-prepare: docs-clean 24 | echo " $(P) Preparing gitbook" 25 | $(BIN_DIR)/gitbook install 26 | 27 | docs-build: docs-prepare 28 | echo " $(P) Building gitbook" 29 | $(BIN_DIR)/gitbook build -g Automattic/monk 30 | 31 | docs-watch: docs-prepare 32 | echo " $(P) Watching gitbook" 33 | $(BIN_DIR)/gitbook serve 34 | 35 | docs-publish: docs-build 36 | echo " $(P) Publishing gitbook" 37 | cd _book && \ 38 | git init && \ 39 | git commit --allow-empty -m 'update book' && \ 40 | git checkout -b gh-pages && \ 41 | touch .nojekyll && \ 42 | git add . && \ 43 | git commit -am 'update book' && \ 44 | git push https://github.com/Automattic/monk gh-pages --force 45 | 46 | .PHONY: lint test test-watch docs-clean docs-prepare docs-build docs-watch docs-publish 47 | .SILENT: lint test test-watch docs-clean docs-prepare docs-build docs-watch docs-publish 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Monk

2 | 3 |
4 | 5 |
6 |
7 |
8 | A tiny layer that provides simple yet substantial usability 9 | improvements for MongoDB usage within Node.JS. 10 |
11 |
12 | 13 | [![build status](https://secure.travis-ci.org/Automattic/monk.svg?branch=master)](https://secure.travis-ci.org/Automattic/monk) 14 | [![codecov](https://codecov.io/gh/Automattic/monk/branch/master/graph/badge.svg)](https://codecov.io/gh/Automattic/monk) 15 | [![Join the chat at https://gitter.im/Automattic/monk](https://badges.gitter.im/Automattic/monk.svg)](https://gitter.im/Automattic/monk?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 16 | 17 | *note*: monk 2.x drop the support for node < 0.12. If you are still using an earlier version, stick to monk 1.x 18 | 19 | ```js 20 | const db = require('monk')('localhost/mydb') 21 | // or 22 | // const db = require('monk')('user:pass@localhost:port/mydb') 23 | 24 | const users = db.get('users') 25 | 26 | users.index('name last') 27 | users.insert({ name: 'Tobi', bigdata: {} }) 28 | users.find({ name: 'Loki' }, '-bigdata').then(function () { 29 | // exclude bigdata field 30 | }) 31 | users.find({}, {sort: {name: 1}}).then(function () { 32 | // sorted by name field 33 | }) 34 | users.remove({ name: 'Loki' }) 35 | 36 | db.close() 37 | ``` 38 | 39 | ## Features 40 | 41 | - Well-designed API signatures 42 | - Easy connections / configuration 43 | - Command buffering. You can start querying right away 44 | - Promises built-in for all queries. Easy interoperability with modules 45 | - Auto-casting of `_id` in queries 46 | - Allows to set global options or collection-level options for queries. (eg: 47 | `castIds` is `true` by default for all queries) 48 | 49 | ## Middlewares 50 | 51 | Most of the Monk's features are implemented as [middleware](https://automattic.github.io/monk/docs/middlewares.html). 52 | 53 | There are a bunch of third-parties middlewares that add even more functionalities to Monk: 54 | - [monk-middleware-wrap-non-dollar-update](https://github.com/monk-middlewares/monk-middleware-wrap-non-dollar-update) 55 | - [monk-middleware-debug](https://github.com/monk-middlewares/monk-middleware-debug) 56 | - [monk-middleware-dereference](https://github.com/monk-middlewares/monk-middleware-dereference) 57 | 58 | *Created an nice middleware? Send a PR to add to the list!* 59 | 60 | ## How to use 61 | 62 | [Documentation](https://Automattic.github.io/monk) 63 | 64 | ## License 65 | 66 | MIT 67 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | ## Monk 2 | 3 | * [Introduction](/README.md) 4 | * [Getting Started](/docs/GETTING_STARTED.md) 5 | 6 | ## Guides 7 | * [Middlewares](/docs/middlewares.md) 8 | * [Debugging](/docs/Debugging.md) 9 | * [Writing a middleware](/docs/writing-middleware.md) 10 | 11 | ## API Reference 12 | * [Manager](/docs/manager/README.md) 13 | * [close](/docs/manager/close.md) 14 | * [create](/docs/manager/create.md) 15 | * [get](/docs/manager/get.md) 16 | * [addMiddleware](/docs/manager/addMiddleware.md) 17 | * [Collection](/docs/collection/README.md) 18 | * [aggregate](/docs/collection/aggregate.md) 19 | * [bulkWrite](/docs/collection/bulkWrite.md) 20 | * [count](/docs/collection/count.md) 21 | * [createIndex](/docs/collection/createIndex.md) 22 | * [distinct](/docs/collection/distinct.md) 23 | * [drop](/docs/collection/drop.md) 24 | * [dropIndex](/docs/collection/dropIndex.md) 25 | * [dropIndexes](/docs/collection/dropIndexes.md) 26 | * [find](/docs/collection/find.md) 27 | * [findOne](/docs/collection/findOne.md) 28 | * [findOneAndDelete](/docs/collection/findOneAndDelete.md) 29 | * [findOneAndUpdate](/docs/collection/findOneAndUpdate.md) 30 | * [geoHaystackSearch](/docs/collection/geoHaystackSearch.md) 31 | * [geoNear](/docs/collection/geoNear.md) 32 | * [group](/docs/collection/group.md) 33 | * [indexes](/docs/collection/indexes.md) 34 | * [insert](/docs/collection/insert.md) 35 | * [mapReduce](/docs/collection/mapReduce.md) 36 | * [remove](/docs/collection/remove.md) 37 | * [stats](/docs/collection/stats.md) 38 | * [update](/docs/collection/update.md) 39 | * [id](/docs/id.md) 40 | 41 | --- 42 | 43 | * [Change Log](/CHANGELOG.md) 44 | * [Contributors](https://github.com/Automattic/monk/graphs/contributors) 45 | -------------------------------------------------------------------------------- /assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/monk/5505e62ce616682ea2a6acec0cd9af01afa69069/assets/favicon.ico -------------------------------------------------------------------------------- /assets/monk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/monk/5505e62ce616682ea2a6acec0cd9af01afa69069/assets/monk.png -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "gitbook": ">=3.2.2", 3 | "title": "Monk", 4 | "description": "The wise MongoDB API", 5 | "plugins": ["edit-link", "prism", "-highlight", "github", "anker-enable", "custom-favicon"], 6 | "pluginsConfig": { 7 | "github": { 8 | "url": "https://github.com/Automattic/monk/" 9 | }, 10 | "edit-link": { 11 | "base": "https://github.com/Automattic/monk/redux/tree/master", 12 | "label": "Edit This Page" 13 | }, 14 | "favicon": "assets/favicon.ico" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/Debugging.md: -------------------------------------------------------------------------------- 1 | # Debugging 2 | 3 | To get some information on what monk is doing, use the `monk-middleware-debug`: 4 | 5 | ``` 6 | npm install --save monk-middleware-debug 7 | ``` 8 | 9 | ```js 10 | db.addMiddleware(require('monk-middleware-debug')) 11 | ``` 12 | 13 | * If you wish to see what queries `monk` passes to the driver, simply leverage 14 | [debug](http://github.com/visionmedia/debug): 15 | 16 | ```bash 17 | DEBUG="monk:query" npm start 18 | ``` 19 | 20 | * To see all debugging output from monk: 21 | 22 | ```bash 23 | DEBUG="monk:*" npm start 24 | ``` 25 | 26 | * You can also see the output from the mongo driver using `debug`: 27 | 28 | ```bash 29 | DEBUG="mongo:*" npm start 30 | ``` 31 | 32 | There are several separated features available on this namespace: 33 | 34 | * Db: The Db instance log statements 35 | * Server: A server instance (either standalone, a mongos or replicaset member) 36 | * ReplSet: Replicaset related log statements 37 | * Mongos: Mongos related log statements 38 | * Cursor: Cursor log statements 39 | * Pool: Connection Pool specific log statements 40 | * Connection: Singular connection specific log statements 41 | * Ping: Replicaset ping inquiry log statements 42 | 43 | To see the output of only one of those features: 44 | 45 | ```bash 46 | DEBUG="mongo:Cursor" npm start // will only print the Cursor log statements 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/GETTING_STARTED.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | The quick start guide will show you how to setup a simple application using node.js and MongoDB. Its scope is only how to set up the driver and perform the simple crud operations. 4 | 5 | Installing monk 6 | --------------------------- 7 | Use **NPM** to install `monk`. 8 | 9 | ``` 10 | npm install --save monk 11 | ``` 12 | 13 | Booting up a MongoDB Server 14 | --------------------------- 15 | Let's boot up a MongoDB server instance. Download the right MongoDB version from [MongoDB](http://www.mongodb.org), open a new shell or command line and ensure the **mongod** command is in the shell or command line path. Now let's create a database directory (in our case under **/data**). 16 | 17 | ``` 18 | mongod --dbpath=/data --port 27017 19 | ``` 20 | 21 | You should see the **mongod** process start up and print some status information. 22 | 23 | Connecting to MongoDB 24 | --------------------- 25 | Let's create a new **app.js** file that we will use to show the basic CRUD operations using monk. 26 | 27 | First let's add code to connect to the server and the database **myproject**. 28 | 29 | ```js 30 | const monk = require('monk') 31 | 32 | // Connection URL 33 | const url = 'localhost:27017/myproject'; 34 | 35 | const db = monk(url); 36 | 37 | db.then(() => { 38 | console.log('Connected correctly to server') 39 | }) 40 | ``` 41 | 42 | Given that you booted up the **mongod** process earlier the application should connect successfully and print **Connected correctly to server** to the console. 43 | 44 | If you are not sure what the `then` is or not up to speed with `Promises`, you might want to check out some tutorials first. 45 | 46 | Let's Add some code to show the different CRUD operations available. 47 | 48 | Inserting a Document 49 | -------------------- 50 | Let's insert some documents in the `document` collection. 51 | 52 | ```js 53 | const url = 'localhost:27017/myproject'; // Connection URL 54 | const db = require('monk')(url); 55 | 56 | const collection = db.get('document') 57 | 58 | collection.insert([{a: 1}, {a: 2}, {a: 3}]) 59 | .then((docs) => { 60 | // docs contains the documents inserted with added **_id** fields 61 | // Inserted 3 documents into the document collection 62 | }).catch((err) => { 63 | // An error happened while inserting 64 | }).then(() => db.close()) 65 | ``` 66 | 67 | You can notice that we are not waiting for the connection to be opened before doing the operation. That's because behind the scene, monk will queue all the operations until the connection is opened and then send them. 68 | 69 | We can now run the updated **app.js** file. 70 | 71 | ``` 72 | node app.js 73 | ``` 74 | 75 | You should see the following output after running the **app.js** file. 76 | 77 | ``` 78 | Inserted 3 documents into the document collection 79 | ``` 80 | 81 | Updating a document 82 | ------------------- 83 | Let's look at how to do a simple document update by adding a new field **b** to the document that has the field **a** set to **2**. 84 | 85 | ```js 86 | const url = 'localhost:27017/myproject'; // Connection URL 87 | const db = require('monk')(url); 88 | 89 | const collection = db.get('document') 90 | 91 | collection.insert([{a: 1}, {a: 2}, {a: 3}]) 92 | .then((docs) => { 93 | // Inserted 3 documents into the document collection 94 | }) 95 | .then(() => { 96 | 97 | return collection.update({ a: 2 }, { $set: { b: 1 } }) 98 | 99 | }) 100 | .then((result) => { 101 | // Updated the document with the field a equal to 2 102 | }) 103 | .then(() => db.close()) 104 | ``` 105 | 106 | The method will update the first document where the field **a** is equal to **2** by adding a new field **b** to the document set to **1**. 107 | 108 | Delete a document 109 | ----------------- 110 | Next let's delete the document where the field **a** equals to **3**. 111 | 112 | ```js 113 | const url = 'localhost:27017/myproject'; // Connection URL 114 | const db = require('monk')(url); 115 | 116 | const collection = db.get('document') 117 | 118 | collection.insert([{a: 1}, {a: 2}, {a: 3}]) 119 | .then((docs) => { 120 | // Inserted 3 documents into the document collection 121 | }) 122 | .then(() => collection.update({ a: 2 }, { $set: { b: 1 } })) 123 | .then((result) => { 124 | // Updated the document with the field a equal to 2 125 | }) 126 | .then(() => { 127 | 128 | return collection.remove({ a: 3}) 129 | 130 | }).then((result) => { 131 | // Deleted the document with the field a equal to 3 132 | }) 133 | .then(() => db.close()) 134 | ``` 135 | 136 | This will delete the first document where the field **a** equals to **3**. 137 | 138 | Find All Documents 139 | ------------------ 140 | We will finish up the CRUD methods by performing a simple query that returns all the documents matching the query. 141 | 142 | ```js 143 | const url = 'localhost:27017/myproject'; // Connection URL 144 | const db = require('monk')(url); 145 | 146 | const collection = db.get('document') 147 | 148 | collection.insert([{a: 1}, {a: 2}, {a: 3}]) 149 | .then((docs) => { 150 | // Inserted 3 documents into the document collection 151 | }) 152 | .then(() => collection.update({ a: 2 }, { $set: { b: 1 } })) 153 | .then((result) => { 154 | // Updated the document with the field a equal to 2 155 | }) 156 | .then(() => collection.remove({ a: 3})) 157 | .then((result) => { 158 | // Deleted the document with the field a equal to 3 159 | }) 160 | .then(() => { 161 | 162 | return collection.find() 163 | 164 | }) 165 | .then((docs) => { 166 | // docs === [{ a: 1 }, { a: 2, b: 1 }] 167 | }) 168 | .then(() => db.close()) 169 | ``` 170 | 171 | This query will return all the documents in the 'document' collection. Since we deleted a document the total 172 | documents returned is **2**. 173 | 174 | This concludes the Getting Started of connecting and performing some Basic operations using monk. 175 | -------------------------------------------------------------------------------- /docs/collection/README.md: -------------------------------------------------------------------------------- 1 | # Collection 2 | 3 | Object representing a mongo collection. Create it using [`manager.get`](../manager/get.md). 4 | 5 | A Collection instance has the following methods: 6 | * [aggregate](/aggregate.md) 7 | * [bulkWrite](/bulkWrite.md) 8 | * [count](/count.md) 9 | * [distinct](/distinct.md) 10 | * [drop](/drop.md) 11 | * [dropIndex](/dropIndex.md) 12 | * [dropIndexes](/dropIndexes.md) 13 | * [ensureIndex](/ensureIndex.md) 14 | * [find](/find.md) 15 | * [findOne](/findOne.md) 16 | * [findOneAndDelete](/findOneAndDelete.md) 17 | * [findOneAndUpdate](/findOneAndUpdate.md) 18 | * [geoHaystackSearch](/geoHaystackSearch.md) 19 | * [geoNear](/geoNear.md) 20 | * [group](/group.md) 21 | * [indexes](/indexes.md) 22 | * [insert](/insert.md) 23 | * [mapReduce](/mapReduce.md) 24 | * [remove](/remove.md) 25 | * [stats](/stats.md) 26 | * [update](/update.md) 27 | 28 | #### Example 29 | 30 | ```js 31 | const users = db.get('users', options) 32 | ``` 33 | 34 | #### Options 35 | 36 | You can set options to pass to every queries of the collection. 37 | ```js 38 | users.options = { 39 | castIds: true 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/collection/aggregate.md: -------------------------------------------------------------------------------- 1 | # `collection.aggregate` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#aggregate) 4 | 5 | Calculates aggregate values for the data in a collection. 6 | 7 | #### Arguments 8 | 9 | 1. `pipeline` *(Array)*: A sequence of data aggregation operations or stages. 10 | 11 | 2. [`options`] *(object)* 12 | 13 | 3. [`callback`] *(function)* 14 | 15 | #### Returns 16 | 17 | A promise 18 | 19 | #### Example 20 | 21 | ```js 22 | users.aggregate([ 23 | { $project : { 24 | author : 1, 25 | tags : 1 26 | }}, 27 | { $unwind : "$tags" }, 28 | { $group : { 29 | _id : {tags : "$tags"}, 30 | authors : { $addToSet : "$author" } 31 | }} 32 | ]).then((res) => {}) 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/collection/bulkWrite.md: -------------------------------------------------------------------------------- 1 | # `collection.bulkWrite` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#bulkWrite) 4 | 5 | Perform a bulkWrite operation without a fluent API 6 | 7 | Legal operation types are 8 | ``` 9 | { insertOne: { document: { a: 1 } } } 10 | { updateOne: { filter: {a:2}, update: {$set: {a:2}}, upsert:true } } 11 | { updateMany: { filter: {a:2}, update: {$set: {a:2}}, upsert:true } } 12 | { deleteOne: { filter: {c:1} } } 13 | { deleteMany: { filter: {c:1} } } 14 | { replaceOne: { filter: {c:3}, replacement: {c:4}, upsert:true}} 15 | ``` 16 | 17 | #### Arguments 18 | 19 | 1. `operations` *(Array)* - Bulk operations to perform. 20 | 21 | 2. [`options`] *(Object)* 22 | 23 | 3. [`callback`] *(function)* 24 | 25 | #### Returns 26 | 27 | A promise. 28 | 29 | #### Example 30 | 31 | ```js 32 | users.bulkWrite([ 33 | { insertOne: { document: { a: 1 } } } 34 | , { updateOne: { filter: {a:2}, update: {$set: {a:2}}, upsert:true } } 35 | , { updateMany: { filter: {a:2}, update: {$set: {a:2}}, upsert:true } } 36 | , { deleteOne: { filter: {c:1} } } 37 | , { deleteMany: { filter: {c:1} } } 38 | , { replaceOne: { filter: {c:3}, replacement: {c:4}, upsert:true}} 39 | ]) 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/collection/count.md: -------------------------------------------------------------------------------- 1 | # `collection.count` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#countDocuments) 4 | 5 | Returns the count of documents that would match a `find()` query. The `collection.count()` method does not perform the `find()` operation but instead counts and returns the number of results that match a query. The method points to `collection.countDocuments()` in the mongo driver. 6 | 7 | #### Arguments 8 | 9 | 1. `query` *(String|ObjectId|Object)*: The query for the count. 10 | 11 | 2. [`options`] *(object)* 12 | 13 | 3. [`callback`] *(function)* 14 | 15 | #### Returns 16 | 17 | A promise 18 | 19 | #### Example 20 | 21 | ```js 22 | users.count({name: 'foo'}) 23 | users.count('id') // a bit useless but consistent with the rest of the API 24 | users.count() 25 | ``` 26 | 27 | ## Getting an estimated count 28 | 29 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#estimatedDocumentCount) 30 | 31 | If you need to get a fast order of magnitude of the count of *all* documents in your collection, you can use the `estimate` option. 32 | 33 | > ⚠️ The `query` argument will be ignored. 34 | 35 | ```js 36 | users.count({}, { estimate: true }) 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/collection/createIndex.md: -------------------------------------------------------------------------------- 1 | # `collection.createIndex` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#createIndex) 4 | 5 | Creates an index on the db and collection (will not create if already exists) 6 | 7 | #### Arguments 8 | 9 | 1. `fieldOrSpec` *(String|Array|Object)*: Defines the index. 10 | 11 | 2. [`options`] *(object)* 12 | 13 | 3. [`callback`] *(function)* 14 | 15 | #### Returns 16 | 17 | A promise 18 | 19 | #### Example 20 | 21 | ```js 22 | 23 | users.createIndex('name.first') 24 | users.createIndex('name last') 25 | users.createIndex(['nombre', 'apellido']) 26 | users.createIndex({ up: 1, down: -1 }) 27 | users.createIndex({ woot: 1 }, { unique: true }) 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/collection/distinct.md: -------------------------------------------------------------------------------- 1 | # `collection.distinct` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#distinct) 4 | 5 | Finds the distinct values for a specified field across a single collection and returns the results in an array. 6 | 7 | #### Arguments 8 | 9 | 1. `field` *(String)*: The field for which to return distinct values. 10 | 11 | 2. [`query`] *(String|ObjectId|Object)*: A query that specifies the documents from which to retrieve the distinct values. 12 | 13 | 3. [`options`] *(object)* 14 | 15 | 4. [`callback`] *(function)* 16 | 17 | #### Returns 18 | 19 | A promise 20 | 21 | #### Example 22 | 23 | ```js 24 | users.distinct('name') 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/collection/drop.md: -------------------------------------------------------------------------------- 1 | # `collection.drop` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#drop) 4 | 5 | Drop the collection from the database, removing it permanently. New accesses will create a new collection. 6 | 7 | #### Arguments 8 | 9 | 1. [`callback`] *(function)* 10 | 11 | #### Returns 12 | 13 | A promise 14 | 15 | #### Example 16 | 17 | ```js 18 | users.drop() 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/collection/dropIndex.md: -------------------------------------------------------------------------------- 1 | # `collection.dropIndex` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#dropIndex) 4 | 5 | Drops indexes from this collection. 6 | 7 | #### Arguments 8 | 9 | 1. `fields` *(String|Object|Array)*: Defines the index (or indexes) to drop. 10 | 11 | 2. [`options`] *(object)* 12 | 13 | 3. [`callback`] *(function)* 14 | 15 | #### Returns 16 | 17 | A promise 18 | 19 | #### Example 20 | 21 | ```js 22 | users.dropIndex('name.first') 23 | users.dropIndex('name last') 24 | users.dropIndex(['nombre', 'apellido']) 25 | users.dropIndex({ up: 1, down: -1 }) 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/collection/dropIndexes.md: -------------------------------------------------------------------------------- 1 | # `collection.dropIndexes` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#dropIndexes) 4 | 5 | Drops all indexes from this collection. 6 | 7 | #### Arguments 8 | 9 | 1. [`callback`] *(function)* 10 | 11 | #### Returns 12 | 13 | A promise 14 | 15 | #### Example 16 | 17 | ```js 18 | users.dropIndexes() 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/collection/find.md: -------------------------------------------------------------------------------- 1 | # `collection.find` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#find) 4 | 5 | Selects documents in a collection and return them. 6 | 7 | #### Arguments 8 | 9 | 1. `query` *(String|ObjectId|Object)* 10 | 11 | 2. [`options`] *(Object|String|Array)*: If the `options` is a string, it will be parsed as the fields to select. 12 | In addition to the mongo options, you can pass the option `rawCursor` in order to get the raw [mongo cursor](http://mongodb.github.io/node-mongodb-native/3.2/api/Cursor.html) when the promise resolve. 13 | 14 | 3. [`callback`] *(function)* 15 | 16 | #### Returns 17 | 18 | A promise with a `each` method to stream the query. 19 | The `each` method expects a function which will receive two arguments: 20 | 1. `doc` *(Object)*: current document of the stream 21 | 2. `cursor` *(Object)*: 22 | * `close` *(function)*: close the stream. The promise will be resolved. 23 | * `pause` *(function)*: pause the stream. 24 | * `resume` *(function)*: resume the stream. 25 | 26 | #### Example 27 | 28 | ```js 29 | users.find({}).then((docs) => {}) 30 | ``` 31 | ```js 32 | users.find({}, 'name').then((docs) => { 33 | // only the name field will be selected 34 | }) 35 | users.find({}, { fields: { name: 1 } }) // equivalent 36 | 37 | users.find({}, '-name').then((docs) => { 38 | // all the fields except the name field will be selected 39 | }) 40 | users.find({}, { fields: { name: 0 } }) // equivalent 41 | ``` 42 | ```js 43 | users.find({}, { rawCursor: true }).then((cursor) => { 44 | // raw mongo cursor 45 | }) 46 | ``` 47 | ```js 48 | users.find({}).each((user, {close, pause, resume}) => { 49 | // the users are streaming here 50 | // call `close()` to stop the stream 51 | }).then(() => { 52 | // stream is over 53 | }) 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/collection/findOne.md: -------------------------------------------------------------------------------- 1 | # `collection.findOne` 2 | 3 | [Mongo documentation ](https://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#findOne) 4 | 5 | Returns one document that satisfies the specified query criteria on the collection or view. If multiple documents satisfy the query, this method returns the first document according to the natural order which reflects the order of documents on the disk. In capped collections, natural order is the same as insertion order. If no document satisfies the query, the method returns null. 6 | 7 | #### Arguments 8 | 9 | 1. `query` *(String|ObjectId|Object)* 10 | 11 | 2. [`options`] *(Object|String|Array)*: If the `options` is a string, it will be parsed as the projections to select. 12 | 13 | 3. [`callback`] *(function)* 14 | 15 | #### Returns 16 | 17 | A promise. 18 | 19 | #### Example 20 | 21 | ```js 22 | users.findOne({name: 'foo'}).then((doc) => {}); 23 | ``` 24 | 25 | ```js 26 | users.findOne({name: 'foo'}, 'name').then((doc) => { 27 | // only the name projection will be selected 28 | }); 29 | users.findOne({name: 'foo'}, { projection: { name: 1 } }); // equivalent 30 | 31 | users.findOne({name: 'foo'}, '-name').then((doc) => { 32 | // all the fields except the name projection will be selected 33 | }); 34 | users.findOne({name: 'foo'}, { projection: {name: -1} }); // equivalent 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/collection/findOneAndDelete.md: -------------------------------------------------------------------------------- 1 | # `collection.findOneAndDelete` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#findOneAndDelete) 4 | 5 | Find a document and delete it in one atomic operation, requires a write lock for the duration of the operation. 6 | 7 | #### Arguments 8 | 9 | 1. `query` *(String|ObjectId|Object)* 10 | 11 | 2. [`options`] *(Object|String|Array)*: If the `options` is a string, it will be parsed as the fields to select. 12 | 13 | 3. [`callback`] *(function)* 14 | 15 | #### Returns 16 | 17 | A promise. 18 | 19 | #### Example 20 | 21 | ```js 22 | users.findOneAndDelete({name: 'foo'}).then((doc) => {}) 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/collection/findOneAndUpdate.md: -------------------------------------------------------------------------------- 1 | # `collection.findOneAndUpdate` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#findOneAndUpdate) 4 | 5 | Find a document and update it in one atomic operation, requires a write lock for the duration of the operation. 6 | 7 | #### Arguments 8 | 9 | 1. `query` *(String|ObjectId|Object)* 10 | 11 | 2. `update` *(Object)*: Update operations to be performed on the document. As [written in MongoDB docs](https://docs.mongodb.com/manual/reference/operator/update/), you need to specify an atomic operator here (like a `$set`, `$unset`, or `$rename`). 12 | 13 | 3. [`options`] *(Object|String|Array)*: If the `options` is a string, it will be parsed as the fields to select. 14 | 15 | `options.returnOriginal` is default to `false`, while `mongodb` set it to `true` for `undefined`. 16 | 17 | if `options.replaceOne` is `true`, it will work like findOneAndReplace. 18 | 19 | 4. [`callback`] *(function)* 20 | 21 | #### Returns 22 | 23 | A promise. 24 | 25 | #### Example 26 | 27 | ```js 28 | users.findOneAndUpdate({name: 'foo'}, { $set: { name: 'bar'} }).then((updatedDoc) => {}) 29 | ``` 30 | 31 | Note that you can also use the [monk-middleware-wrap-non-dollar-update](https://github.com/monk-middlewares/monk-middleware-wrap-non-dollar-update) middleware, which will automatically put the `$set` operator on the `update` argument: 32 | 33 | ```js 34 | db.addMiddleware(require('monk-middleware-wrap-non-dollar-update')) 35 | 36 | users.findOneAndUpdate({name: 'foo'}, { name: 'bar'}).then((updatedDoc) => {}) 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/collection/geoHaystackSearch.md: -------------------------------------------------------------------------------- 1 | # `collection.geoHaystackSearch` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#geoHaystackSearch) 4 | 5 | Execute a geo search using a geo haystack index on a collection. 6 | 7 | #### Arguments 8 | 9 | 1. `x` *(number)* - Point to search on the x axis, ensure the indexes are ordered in the same order. 10 | 11 | 1. `y` *(number)* - Point to search on the y axis, ensure the indexes are ordered in the same order. 12 | 13 | 1. `options` *(Object)* - Need to specify at least the `maxDistance` and `search` options. 14 | 15 | 1. [`callback`] *(function)* 16 | 17 | #### Returns 18 | 19 | A promise. 20 | 21 | #### Example 22 | 23 | ```js 24 | users.geoHaystackSearch(50, 50, {search:{a:1}, limit:1, maxDistance:100}).then((results) => {}) 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/collection/geoNear.md: -------------------------------------------------------------------------------- 1 | # `collection.geoNear` 2 | 3 | [Mongo documentation ](https://docs.mongodb.com/manual/reference/command/geoNear) 4 | 5 | Node.js MongDB native driver 3.x [has removed the support of this command](https://github.com/mongodb/node-mongodb-native/blob/master/CHANGES_3.0.0.md#geonear-command-helper), since there are already alternatives like `$near` and `$nearSphere` query operators (see the documentation link above for more info). 6 | -------------------------------------------------------------------------------- /docs/collection/group.md: -------------------------------------------------------------------------------- 1 | # `collection.group` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#group) 4 | 5 | Run a group command across a collection. 6 | 7 | #### Arguments 8 | 9 | 1. `keys` *(object | array | function)* - An object, array or function expressing the keys to group by. 10 | 11 | 2. `condition` *(Object)* - An optional condition that must be true for a row to be considered. 12 | 13 | 3. `initial` *(Object)* - Initial value of the aggregation counter object. 14 | 15 | 4. `reduce` *(Function)* - The reduce function aggregates (reduces) the objects iterated. 16 | 17 | 5. [`finalize`] *(Function)* - An optional function to be run on each item in the result set just before the item is returned. 18 | 19 | 6. [`command`] *(Boolean)* - Specify if you wish to run using the internal group command or using eval, default is true. 20 | 21 | 7. [`options`] *(Object)* 22 | 23 | 8. [`callback`] *(function)* 24 | 25 | #### Returns 26 | 27 | A promise. 28 | 29 | #### Example 30 | 31 | ```js 32 | users.group( 33 | { a: true }, 34 | {}, 35 | { count: 0 }, 36 | function (obj, prev) { 37 | prev.count++ 38 | }, 39 | true 40 | ) 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/collection/indexes.md: -------------------------------------------------------------------------------- 1 | # `collection.indexes` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#indexes) 4 | 5 | Returns an array that holds a list of documents that identify and describe the existing indexes on the collection. 6 | 7 | #### Arguments 8 | 9 | 1. [`callback`] *(function)* 10 | 11 | #### Returns 12 | 13 | A promise. 14 | 15 | #### Example 16 | 17 | ```js 18 | users.indexes().then((indexes) => {}) 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/collection/insert.md: -------------------------------------------------------------------------------- 1 | # `collection.insert` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#insert) 4 | 5 | Inserts a single document or a an array of documents into MongoDB. 6 | 7 | #### Arguments 8 | 9 | 1. `docs` *(Object|Array)* 10 | 11 | 2. [`options`] *(Object)* 12 | 13 | Use `castIds: false` to disable autocasting of '_id' properties to ObjectIds objects. 14 | 15 | 3. [`callback`] *(function)* 16 | 17 | #### Returns 18 | 19 | A promise. 20 | 21 | #### Example 22 | 23 | ```js 24 | users.insert({ woot: 'foo' }) 25 | users.insert([{ woot: 'bar' }, { woot: 'baz' }]) 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/collection/mapReduce.md: -------------------------------------------------------------------------------- 1 | # `collection.mapReduce` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#mapReduce) 4 | 5 | Run Map Reduce across a collection. Be aware that the inline option for out will return an array of results not a collection. 6 | 7 | #### Arguments 8 | 9 | 1. `map` *(function | string)* - The mapping function. 10 | 11 | 1. `reduce` *(function | string)* - The reduce function. 12 | 13 | 2. `options` *(Object)* - Need to specify at least the `out` option. 14 | 15 | 3. [`callback`] *(function)* 16 | 17 | #### Returns 18 | 19 | A promise. 20 | 21 | #### Example 22 | 23 | ```js 24 | // Map function 25 | var map = function () { emit(this.user_id, 1) } 26 | // Reduce function 27 | var reduce = function (k, vals) { return 1 } 28 | users.mapReduce(map, reduce, {out: {replace: 'tempCollection'}}).then((collection) => {}) 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/collection/remove.md: -------------------------------------------------------------------------------- 1 | # `collection.remove` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#remove) 4 | 5 | Remove documents. Set the `multi` option to false remove only the first document that match the query criteria. 6 | 7 | #### Arguments 8 | 9 | 1. `query` *(Object|ObjectId|String)* 10 | 11 | 2. [`options`] *(Object)* 12 | 13 | 3. [`callback`] *(function)* 14 | 15 | #### Returns 16 | 17 | A promise. 18 | 19 | #### Example 20 | 21 | ```js 22 | users.remove({ woot: 'foo' }) 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/collection/stats.md: -------------------------------------------------------------------------------- 1 | # `collection.stats` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#stats) 4 | 5 | Get all the collection statistics. 6 | 7 | #### Arguments 8 | 9 | 1. [`options`] *(Object)* 10 | 11 | 2. [`callback`] *(function)* 12 | 13 | #### Returns 14 | 15 | A promise. 16 | 17 | #### Example 18 | 19 | ```js 20 | users.stats().then((stats) => {}) 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/collection/update.md: -------------------------------------------------------------------------------- 1 | # `collection.update` 2 | 3 | [Mongo documentation ](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#update) 4 | 5 | Modifies an existing document or documents in a collection. The method can modify specific fields of an existing document or documents or replace an existing document entirely, depending on the update parameter. By default, the update() method updates a single document. Set the `multi` option to update all documents that match the query criteria. 6 | 7 | #### Arguments 8 | 9 | 1. `query` *(String|ObjectId|Object)* 10 | 11 | 2. `update` *(Object)*: Update operations to be performed on the document 12 | 13 | 3. [`options`] *(Object)* 14 | 15 | if `options.replaceOne` is `true`, it will work like replaceOne. 16 | 17 | 4. [`callback`] *(function)* 18 | 19 | #### Returns 20 | 21 | A promise. 22 | 23 | #### Example 24 | 25 | ```js 26 | users.update({name: 'foo'}, {name: 'bar'}) 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/id.md: -------------------------------------------------------------------------------- 1 | # `monk.id` 2 | 3 | Casts a string to (or create) an [ObjectId](https://docs.mongodb.com/manual/reference/method/ObjectId/). 4 | 5 | In order to use custom id, `castIds` needs to be explicitly set to false as an option in collection operations. 6 | 7 | #### Arguments 8 | 9 | 1. [`string`] *(string)*: optional hex id to cast to ObjectId. If not provided, a random ObjectId will be created. 10 | 11 | #### Returns 12 | 13 | An [ObjectId](https://docs.mongodb.com/manual/reference/method/ObjectId/). 14 | 15 | #### Example 16 | 17 | ```js 18 | const monk = require('monk') 19 | const id = monk.id('4ee0fd75d6bd52107c000118') 20 | const newId = monk.id() 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/manager/README.md: -------------------------------------------------------------------------------- 1 | # Manager 2 | 3 | Monk constructor. 4 | 5 | #### Arguments 6 | 7 | 1. `uri` *(string or Array)*: A [mongo connection string URI](https://docs.mongodb.com/manual/reference/connection-string/). Replica sets can be an array or comma separated. 8 | 9 | 2. [`options`] *(Object)*: You may optionally specify [options](http://mongodb.github.io/node-mongodb-native/3.2/reference/connecting/connection-settings/). 10 | 11 | 3. [`callback`] *(Function)*: You may optionally specify a callback which will be called once the connection to the mongo database is opened or throws an error. 12 | 13 | #### Returns 14 | 15 | A Manager instance with the following methods: 16 | * [close](/close.md) 17 | * [create](/create.md) 18 | * [get](/get.md) 19 | 20 | #### Example 21 | 22 | ```js 23 | const db = require('monk')('localhost/mydb', options) 24 | ``` 25 | 26 | ```js 27 | const db = require('monk')('localhost/mydb,192.168.1.1') // replica set 28 | ``` 29 | 30 | ```js 31 | require('monk')('localhost/mydb,192.168.1.1').then((db) => { 32 | // db is the connected instance of the Manager 33 | }).catch((err) => { 34 | // error connecting to the database 35 | }) 36 | ``` 37 | 38 | #### Options 39 | 40 | You can set options to pass to every query. By default, monk doesn't set any options. 41 | 42 | Set options like this: 43 | 44 | ```js 45 | db.options = { 46 | poolSize: 7 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/manager/addMiddleware.md: -------------------------------------------------------------------------------- 1 | # `manager.addMiddleware` 2 | 3 | Add a middleware to the middlewares chain. 4 | 5 | #### Arguments 6 | 7 | 1. `middleware` *(function)*: the middleware to add the the chain 8 | 9 | #### Example 10 | 11 | ```js 12 | db.addMiddleware(require('monk-plugin-dereference')) 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/manager/close.md: -------------------------------------------------------------------------------- 1 | # `manager.close` 2 | 3 | Closes the connection. 4 | 5 | #### Arguments 6 | 7 | 1. [`force`] *(Boolean)*: Force close, emitting no events 8 | 9 | 2. [`callback`] *(Function)*: You may optionally specify a callback which will be called once the connection to the mongo database is closed. 10 | 11 | #### Returns 12 | 13 | A Promise 14 | 15 | #### Example 16 | 17 | ```js 18 | db.close() 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/manager/create.md: -------------------------------------------------------------------------------- 1 | # `manager.create` 2 | 3 | Create a collection. 4 | 5 | #### Arguments 6 | 7 | 1. `name` *(string)*: name of the mongo collection 8 | 9 | 2. [`creationOptions`] *(object)*: options to create the collection 10 | 11 | 3. [`options`] *(object)*: collection level options 12 | 13 | #### Returns 14 | 15 | A [Collection](/docs/collection/README.md) instance. 16 | 17 | #### Example 18 | 19 | ```js 20 | const users = db.create('users', { capped: true, size: n }) 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/manager/get.md: -------------------------------------------------------------------------------- 1 | # `manager.get` 2 | 3 | Gets a collection. 4 | 5 | #### Arguments 6 | 7 | 1. `name` *(string)*: name of the mongo collection 8 | 9 | 2. [`options`] *(object)*: collection level options 10 | 11 | #### Returns 12 | 13 | A [Collection](../collection/README.md) instance. 14 | 15 | #### Example 16 | 17 | ```js 18 | const users = db.get('users', options) 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/middlewares.md: -------------------------------------------------------------------------------- 1 | # Middleware 2 | 3 | *If you are familiar with [Redux](http://redux.js.org/) and Redux middlewares, you are familiar with Monk middlewares. They use a very similar signature and architecture.* 4 | 5 | If you've used server-side libraries like [Express](http://expressjs.com/) and [Koa](http://koajs.com/), you were also probably already familiar with the concept of *middleware*. In these frameworks, middleware is some code you can put between the framework receiving a request, and the framework generating a response. For example, Express or Koa middleware may add CORS headers, logging, compression, and more. The best feature of middleware is that it's composable in a chain. You can use multiple independent third-party middleware in a single project. 6 | 7 | Monk middleware solves different problems than Express or Koa middleware, but in a conceptually similar way. **It provides a third-party extension point between calling a method, and the moment it reaches the mongo driver.** Most of the Monk features are implemented as Monk middleware: logging, handling callbacks or promises, casting the `_id`s, waiting for the database connection to open, and more. 8 | 9 | ## Understanding Middleware 10 | 11 | While middleware can be used for a variety of things, including deferencing, it's really important that you understand where it comes from. We'll guide you through the thought process leading to middleware, by using logging and crash reporting as examples. 12 | 13 | ### Problem: Logging 14 | 15 | Wouldn't it be nice if we logged every query that happens in the app, together with the result after it? When something goes wrong, we can look back at our log, and figure out which query broke. 16 | 17 | How do we approach this with Monk? 18 | 19 | ### Attempt #1: Logging Manually 20 | 21 | The most naïve solution is just to log the query and the result yourself every time you call a method ([`db.collection.insert(item)`](./collection/insert.md) for example). It's not really a solution, but just a first step towards understanding the problem. 22 | 23 | Say, you call this when creating a todo: 24 | 25 | ```js 26 | db.get('todos').insert({text: 'Use Monk'})) 27 | ``` 28 | 29 | To log the query and result, you can change it to something like this: 30 | 31 | ```js 32 | let todo = {text: 'Use Monk'} 33 | 34 | console.log('inserting', todo) 35 | db.get('todos').insert(todo).then((res) => { 36 | console.log('inserting result', res) 37 | return res 38 | }) 39 | ``` 40 | 41 | This produces the desired effect, but you wouldn't want to do it every time. 42 | 43 | ### Attempt #2: Wrapping Method 44 | 45 | You can extract logging into a function: 46 | 47 | ```js 48 | function queryAndLog(collection, method, ...args) { 49 | console.log(method, ...args) 50 | collection[method](...args).then((res) => { 51 | console.log(method + ' result', res) 52 | return res 53 | }) 54 | } 55 | ``` 56 | 57 | You can then use it everywhere instead of `db.get(collection).method()`: 58 | 59 | ```js 60 | queryAndLog(db.get('todos'), 'insert', {text: 'Use Monk'}) 61 | ``` 62 | 63 | We could end this here, but it's not very convenient to import a special function every time. 64 | 65 | ### Attempt #3: Monkeypatching Method 66 | 67 | What if we just replace the `insert` function on the store instance? We're writing JavaScript, so we can just monkeypatch the `insert` implementation: 68 | 69 | ```js 70 | let next = db.get('todos').insert 71 | db.get('todos').insert = function insertAndLog(...args) { 72 | console.log('insert', ...args) 73 | return next(...args).then((res) => { 74 | console.log('insert result', res) 75 | return res 76 | }) 77 | } 78 | ``` 79 | 80 | This is already closer to what we want! No matter where we insert, it is guaranteed to be logged. Monkeypatching never feels right, but we can live with this for now. We would need to do that for each method of every collections tho. But let's say we only need to for a couple of methods, we could still live with this. 81 | 82 | ### Problem: Crash Reporting 83 | 84 | What if we want to apply **more than one** such transformation to `insert`? 85 | 86 | A different useful transformation that comes to my mind is reporting JavaScript errors in production. 87 | 88 | Wouldn't it be useful if, any time an error is thrown as a result of a mongo query, we would send it to a crash reporting service like [Sentry](https://getsentry.com/welcome/) with the query and the current state? This way it's much easier to reproduce the error in development. 89 | 90 | However, it is important that we keep logging and crash reporting separate. Ideally we want them to be different modules, potentially in different packages. Otherwise we can't have an ecosystem of such utilities. (Hint: we're slowly getting to what middleware is!) 91 | 92 | If logging and crash reporting are separate utilities, they might look like this: 93 | 94 | ```js 95 | function patchMethodToAddLogging(db, collection, method) { 96 | let next = db.get(collection)[method] 97 | db.get(collection)[method] = function methodAndLog(...args) { 98 | console.log(method, ...args) 99 | return next(...args).then((res) => { 100 | console.log(method + ' result', res) 101 | return res 102 | }) 103 | } 104 | } 105 | 106 | function patchMethodToAddCrashReporting(db, collection, method) { 107 | let next = db.get(collection)[method] 108 | db.get(collection)[method] = function methodAndReportErrors(...args) { 109 | console.log(method, ...args) 110 | return next(...args).catch((err) => { 111 | console.error('Caught an exception!', err) 112 | Raven.captureException(err, { 113 | extra: { 114 | method, 115 | args 116 | } 117 | }) 118 | throw err 119 | }) 120 | } 121 | } 122 | ``` 123 | 124 | If these functions are published as separate modules, we can later use them to patch our collection: 125 | 126 | ```js 127 | patchMethodToAddLogging(db, 'todos', 'insert') 128 | patchMethodToAddCrashReporting(db, 'todos', 'insert') 129 | ``` 130 | 131 | Still, this isn't nice. 132 | 133 | ### Attempt #4: Hiding Monkeypatching 134 | 135 | Monkeypatching is a hack. “Replace any method you like”, what kind of API is that? Let's figure out the essence of it instead. Previously, our functions replaced `db.collection.insert`. What if they *returned* the new `insert` function instead? 136 | 137 | ```js 138 | function logger(db, collection, method) { 139 | let next = db.get(collection)[method] 140 | 141 | // Previously: 142 | // db.get(collection)[method] = function methodAndLog(...args) { 143 | 144 | return function methodAndLog(...args) { 145 | console.log(method, ...args) 146 | return next(...args).then((res) => { 147 | console.log(method + ' result', res) 148 | return res 149 | }) 150 | } 151 | } 152 | ``` 153 | 154 | We could provide a helper inside Redux that would apply the actual monkeypatching as an implementation detail: 155 | 156 | ```js 157 | function applyMiddlewareByMonkeypatching(db, collection, method, middlewares) { 158 | middlewares = middlewares.slice() 159 | middlewares.reverse() 160 | 161 | // Transform dispatch function with each middleware. 162 | middlewares.forEach(middleware => 163 | db.get(collection)[method] = middleware(db, collection, method) 164 | ) 165 | } 166 | ``` 167 | 168 | We could use it to apply multiple middleware like this: 169 | 170 | ```js 171 | applyMiddlewareByMonkeypatching(db, 'todos', 'insert', [logger, crashReporter]) 172 | ``` 173 | 174 | However, it is still monkeypatching. 175 | The fact that we hide it inside the library doesn't alter this fact. 176 | 177 | ### Attempt #5: Removing Monkeypatching 178 | 179 | Why do we even overwrite `insert`? Of course, to be able to call it later, but there's also another reason: so that every middleware can access (and call) the previously wrapped `collection.method`: 180 | 181 | ```js 182 | function logger(db, collection, method) { 183 | // Must point to the function returned by the previous middleware: 184 | let next = db.get(collection)[method] 185 | 186 | return function methodAndLog(...args) { 187 | console.log(method, ...args) 188 | return next(...args).then((res) => { 189 | console.log(method + ' result', res) 190 | return res 191 | }) 192 | } 193 | } 194 | ``` 195 | 196 | It is essential to chaining middleware! 197 | 198 | If `applyMiddlewareByMonkeypatching` doesn't assign `collection.method` immediately after processing the first middleware, `collection.method` will keep pointing to the original `method` function. Then the second middleware will also be bound to the original `method` function. 199 | 200 | But there's also a different way to enable chaining. The middleware could accept the `next()` insert function as a parameter instead of reading it from the `collection` instance. 201 | 202 | ```js 203 | function logger(context) { 204 | return function wrapMethodToAddLogging(next) { 205 | return function methodAndLog(args, method) { 206 | console.log(method, ...args) 207 | return next(args, method).then((res) => { 208 | console.log(method + ' result', res) 209 | return res 210 | }) 211 | } 212 | } 213 | } 214 | ``` 215 | 216 | It's a [“we need to go deeper”](http://knowyourmeme.com/memes/we-need-to-go-deeper) kind of moment, so it might take a while for this to make sense. The function cascade feels intimidating. ES6 arrow functions make this [currying](https://en.wikipedia.org/wiki/Currying) easier on eyes: 217 | 218 | ```js 219 | const logger = context => next => (...args) => { 220 | console.log(method, ...args) 221 | return next(...args).then((res) => { 222 | console.log(method + ' result', res) 223 | return res 224 | }) 225 | } 226 | 227 | const crashReporter = context => next => (...args) => { 228 | return next(...args).catch((err) => { 229 | console.error('Caught an exception!', err) 230 | Raven.captureException(err, { 231 | extra: { 232 | method, 233 | args 234 | } 235 | }) 236 | throw err 237 | }) 238 | } 239 | ``` 240 | 241 | **This is exactly what Monk middleware looks like.** 242 | 243 | Now middleware takes the `next()` dispatch function, and returns a dispatch function, which in turn serves as `next()` to the middleware to the left, and so on. It's still useful to have access to some context like the collection and the Monk instance, so `{collection, monkInstance}` stays available as the top-level argument. 244 | 245 | ### Attempt #6: Naïvely Applying the Middleware 246 | 247 | Instead of `applyMiddlewareByMonkeypatching()`, we could write `applyMiddleware()` that first obtains the final, fully wrapped `method()` function, and returns a copy of the method: 248 | 249 | ```js 250 | // Warning: Naïve implementation! 251 | // That's *not* Monk API. 252 | function applyMiddleware(db, collection, method, middlewares) { 253 | middlewares = middlewares.slice() 254 | middlewares.reverse() 255 | let next = collection[method] 256 | middlewares.forEach(middleware => 257 | next = middleware({monkInstance: db, collection})(next) 258 | ) 259 | return next 260 | } 261 | ``` 262 | 263 | The implementation of `applyMiddleware()` that ships with Monk is similar, but **different in a very important aspect**: 264 | 265 | It is called when first getting a collection so that the collection can automatically call the middlewares chain on every method. 266 | 267 | As a result, instead of the method being in the first argument of the middleware, it is in the last. 268 | 269 | ### The Final Approach 270 | 271 | Given this middleware we just wrote: 272 | 273 | ```js 274 | const logger = context => next => (args, method) => { 275 | console.log(method, args) 276 | return next(args, method).then((res) => { 277 | console.log(method + ' result', res) 278 | return res 279 | }) 280 | } 281 | 282 | const crashReporter = context => next => (args, method) => { 283 | return next(args, method).catch((err) => { 284 | console.error('Caught an exception!', err) 285 | Raven.captureException(err, { 286 | extra: { 287 | method, 288 | args 289 | } 290 | }) 291 | throw err 292 | }) 293 | } 294 | ``` 295 | 296 | Here's how to apply it to a Monk instance: 297 | 298 | ```js 299 | db.addMiddleware(logger) 300 | db.addMiddleware(crashReporter) 301 | ``` 302 | 303 | That's it! Now any method called by the monk instance will flow through `logger` and `crashReporter`: 304 | 305 | ```js 306 | // Will flow through both logger and crashReporter middleware! 307 | db.get('todos').insert({text: 'Use Monk'})) 308 | ``` 309 | -------------------------------------------------------------------------------- /docs/writing-middleware.md: -------------------------------------------------------------------------------- 1 | # Writing middleware 2 | 3 | If you haven't already, check out the [middleware article](./middlewares.md) to make sense of the middleware's API. 4 | 5 | ## API 6 | 7 | ```js 8 | const middleware = ({collection, monkInstance}) => next => (args, method) => { 9 | // do something before the call 10 | 11 | /* 12 | * args: { 13 | * options?: {} 14 | * query?: any 15 | * fields?: any 16 | * field?: any 17 | * update?: any 18 | * some other fields for aggregate and group: see the documentation for those methods 19 | * } 20 | */ 21 | 22 | // Always, always return `next` 23 | return next(args, method).then((res) => { 24 | // do something after the call 25 | }) 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "monk" { 2 | import { 3 | CollectionAggregationOptions, 4 | MongoCountPreferences, 5 | BulkWriteOpResultObject, 6 | FilterQuery, 7 | FindOneOptions, 8 | UpdateQuery, 9 | UpdateOneOptions, 10 | UpdateManyOptions, 11 | ReplaceOneOptions, 12 | CollectionInsertOneOptions, 13 | Cursor, 14 | FindOneAndDeleteOption, 15 | FindOneAndUpdateOption, 16 | FindOneAndReplaceOption, 17 | GeoHaystackSearchOptions, 18 | CollectionMapFunction, 19 | CollectionReduceFunction, 20 | MapReduceOptions, 21 | CommonOptions, 22 | DeleteWriteOpResultObject, 23 | ClientSession, 24 | CollStats, 25 | UpdateWriteOpResult, 26 | IndexOptions, 27 | CollectionBulkWriteOptions, 28 | BulkWriteOperation, 29 | MongoDistinctPreferences, 30 | CollectionCreateOptions, 31 | MongoClientOptions, 32 | } from "mongodb"; 33 | 34 | // Utils 35 | type SingleOrArray = T | Array; 36 | type WithID = { _id: IObjectID } & T; 37 | type Callback = (err: Error | null, data: T) => void; 38 | 39 | // Inputs 40 | type SingleMulti = { single?: boolean; multi?: boolean }; 41 | type CreateIndexInput = string | { [key in keyof T]?: 1 | -1 }; 42 | type CollectionInsertOneOptionsMonk = CollectionInsertOneOptions & { 43 | castIds: boolean; 44 | }; 45 | type DropIndexInput = CreateIndexInput & string[]; 46 | type DropIndexOptions = CommonOptions & { maxTimeMS?: number }; 47 | type FindOptions = FindOneOptions & { rawCursor?: boolean }; 48 | type RemoveOptions = CommonOptions & SingleMulti; 49 | type StatsOptions = { scale: number; session?: ClientSession }; 50 | 51 | // Returns 52 | type DropResult = "ns not found" | true; 53 | type DropIndexResult = { nIndexesWas: number; ok: 1 | 0 }; 54 | type DropIndexesResult = DropIndexResult & { msg?: string }; 55 | type FindRawResult = Cursor>; 56 | type FindResult = WithID[] & { 57 | readonly each: ( 58 | listener: ( 59 | record: T, 60 | cursor: { 61 | readonly close: () => void; 62 | readonly resume: () => void; 63 | readonly pause: () => void; 64 | } 65 | ) => any 66 | ) => any; 67 | }; 68 | type FindOneResult = WithID | null; 69 | type GeoHaystackSearchResult = T[]; 70 | type InsertResult = WithID; 71 | type IndexesResult = { 72 | [name: string]: [keyof T, 1 | -1][]; 73 | }; 74 | type UpdateResult = UpdateWriteOpResult["result"]; 75 | 76 | export type TMiddleware = ({ 77 | collection, 78 | monkInstance, 79 | }: { 80 | collection: ICollection; 81 | monkInstance: IMonkManager; 82 | }) => ( 83 | next: (args: Object, method: string) => Promise 84 | ) => (args: Object, method: string) => Promise; 85 | 86 | type CollectionOptions = { 87 | middlewares?: TMiddleware[]; 88 | }; 89 | 90 | export class IMonkManager { 91 | readonly _state: "closed" | "opening" | "open"; 92 | 93 | readonly on: (event: string, handler: (event: any) => any) => void; 94 | readonly addListener: (event: string, handler: (event: any) => any) => void; 95 | readonly once: (event: string, handler: (event: any) => any) => void; 96 | readonly removeListener: ( 97 | event: string, 98 | handler: (event: any) => any 99 | ) => void; 100 | 101 | readonly close: () => Promise; 102 | readonly listCollections: (query?: Object) => Promise>; 103 | 104 | get(name: string, options?: CollectionOptions): ICollection; 105 | create( 106 | name: string, 107 | creationOption?: CollectionCreateOptions, 108 | options?: CollectionOptions 109 | ): ICollection; 110 | 111 | readonly setDefaultCollectionOptions: ( 112 | collectionOptions?: CollectionOptions 113 | ) => void; 114 | readonly addMiddleware: (middleware: TMiddleware) => void; 115 | } 116 | 117 | type TQuery = string | Object; 118 | type TFields = string | Array; 119 | 120 | export class ICollection { 121 | readonly manager: IMonkManager; 122 | readonly name: string; 123 | options: Object; 124 | readonly middlewares: Array; 125 | 126 | aggregate( 127 | pipeline: Object[], 128 | options?: CollectionAggregationOptions 129 | ): Promise; 130 | aggregate( 131 | stages: Object[], 132 | options: CollectionAggregationOptions, 133 | callback: Callback 134 | ): void; 135 | 136 | bulkWrite( 137 | operations: BulkWriteOperation[], 138 | options?: CollectionBulkWriteOptions 139 | ): Promise; 140 | bulkWrite( 141 | operations: BulkWriteOperation[], 142 | options: CollectionBulkWriteOptions, 143 | callback: Callback 144 | ): void; 145 | 146 | /** 147 | * http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#countDocuments 148 | */ 149 | count( 150 | query?: FilterQuery, 151 | options?: MongoCountPreferences 152 | ): Promise; 153 | count( 154 | query: FilterQuery, 155 | options: MongoCountPreferences, 156 | callback?: Callback 157 | ): void; 158 | 159 | createIndex( 160 | fields: CreateIndexInput, 161 | options?: IndexOptions 162 | ): Promise; 163 | createIndex( 164 | fields: CreateIndexInput, 165 | options: IndexOptions, 166 | callback: Callback 167 | ): void; 168 | 169 | distinct( 170 | field: K, 171 | query?: FilterQuery, 172 | options?: MongoDistinctPreferences 173 | ): Promise; 174 | distinct( 175 | field: K, 176 | query: FilterQuery, 177 | options: MongoDistinctPreferences, 178 | callback: Callback 179 | ): void; 180 | 181 | drop(): Promise; 182 | drop(callback: Callback): void; 183 | 184 | dropIndex( 185 | fields: DropIndexInput, 186 | options?: DropIndexOptions 187 | ): Promise; 188 | dropIndex( 189 | fields: DropIndexInput, 190 | options: DropIndexOptions, 191 | callback: Callback 192 | ): void; 193 | 194 | dropIndexes(): Promise; 195 | dropIndexes(callback?: Callback): void; 196 | 197 | // Raw 198 | find( 199 | query: FilterQuery, 200 | options: FindOptions & { rawCursor: true } 201 | ): Promise>; 202 | find( 203 | query: FilterQuery, 204 | options: FindOneOptions & { rawCursor: true }, 205 | callback: Callback> 206 | ): void; 207 | // Normal 208 | find( 209 | query?: FilterQuery, 210 | options?: FindOptions 211 | ): Promise>; 212 | find( 213 | query: FilterQuery, 214 | options: FindOneOptions, 215 | callback: Callback> 216 | ): void; 217 | 218 | findOne( 219 | query?: FilterQuery, 220 | options?: FindOneOptions 221 | ): Promise>; 222 | findOne( 223 | query: FilterQuery, 224 | options: FindOneOptions, 225 | callback: Callback> 226 | ): void; 227 | 228 | findOneAndDelete( 229 | query: FilterQuery, 230 | options?: FindOneAndDeleteOption 231 | ): Promise>; 232 | findOneAndDelete( 233 | query: FilterQuery, 234 | options: FindOneAndDeleteOption, 235 | callback: Callback> 236 | ): void; 237 | 238 | // Update 239 | findOneAndUpdate( 240 | query: FilterQuery, 241 | update: UpdateQuery | Partial, 242 | options?: FindOneAndUpdateOption & { replace?: false } 243 | ): Promise>; 244 | findOneAndUpdate( 245 | query: FilterQuery, 246 | update: UpdateQuery | Partial, 247 | options?: FindOneAndUpdateOption & { replace?: false }, 248 | callback?: Callback> 249 | ): void; 250 | // Replace 251 | findOneAndUpdate( 252 | query: FilterQuery, 253 | update: T, 254 | options?: FindOneAndReplaceOption & { replace: true } 255 | ): Promise>; 256 | findOneAndUpdate( 257 | query: FilterQuery, 258 | update: T, 259 | options: FindOneAndReplaceOption & { replace: true }, 260 | callback: Callback> 261 | ): void; 262 | 263 | geoHaystackSearch( 264 | x: number, 265 | y: number, 266 | options?: GeoHaystackSearchOptions 267 | ): Promise>; 268 | geoHaystackSearch( 269 | x: number, 270 | y: number, 271 | options: GeoHaystackSearchOptions, 272 | callback: Callback> 273 | ): void; 274 | 275 | /** @deprecated MongoDB 3.6 or higher no longer supports the group command. We recommend rewriting using the aggregation framework. */ 276 | group( 277 | keys: any, 278 | condition: Object, 279 | initial: Object, 280 | reduce: Function, 281 | finalize: Function, 282 | command: Boolean, 283 | options?: Object 284 | ): Promise; 285 | group( 286 | keys: any, 287 | condition: Object, 288 | initial: Object, 289 | reduce: Function, 290 | finalize: Function, 291 | command: Boolean, 292 | options?: Object, 293 | callback?: Callback 294 | ): void; 295 | 296 | indexes(): Promise>; 297 | indexes(callback: Callback>): void; 298 | 299 | insert( 300 | data: SingleOrArray, 301 | options?: CollectionInsertOneOptionsMonk 302 | ): Promise>; 303 | insert( 304 | data: SingleOrArray, 305 | options: CollectionInsertOneOptionsMonk, 306 | callback: Callback> 307 | ): void; 308 | 309 | mapReduce( 310 | map: CollectionMapFunction, 311 | reduce: CollectionReduceFunction, 312 | options: MapReduceOptions 313 | ): Promise; 314 | mapReduce( 315 | map: CollectionMapFunction, 316 | reduce: CollectionReduceFunction, 317 | options: MapReduceOptions, 318 | callback: Callback 319 | ): void; 320 | 321 | remove( 322 | query?: FilterQuery, 323 | options?: RemoveOptions 324 | ): Promise; 325 | remove( 326 | query: FilterQuery, 327 | options: RemoveOptions, 328 | callback: Callback 329 | ): void; 330 | 331 | stats(options?: StatsOptions): Promise; 332 | stats(options: StatsOptions, callback: Callback): void; 333 | 334 | // single 335 | update( 336 | query: FilterQuery, 337 | update: UpdateQuery | Partial, 338 | options?: UpdateOneOptions & { single?: true, multi?: false, replace?: false} 339 | ): Promise; 340 | update( 341 | query: FilterQuery, 342 | update: UpdateQuery | Partial, 343 | options: UpdateOneOptions & { single?: true, multi?: false, replace?: false}, 344 | callback: Callback 345 | ): void; 346 | // multi 347 | update( 348 | query: FilterQuery, 349 | update: UpdateQuery | Partial, 350 | options?: UpdateManyOptions & ({ single?: false, multi: true, replace?: false} | { single: false, multi?: true, replace?: false}) 351 | ): Promise; 352 | update( 353 | query: FilterQuery, 354 | update: UpdateQuery | Partial, 355 | options: UpdateOneOptions & ({ single?: false, multi: true, replace?: false} | { single: false, multi?: true, replace?: false}), 356 | callback: Callback 357 | ): void; 358 | // replace 359 | update( 360 | query: FilterQuery, 361 | update: UpdateQuery | Partial, 362 | options?: ReplaceOneOptions & { single?: true, multi?: false, replace: true} 363 | ): Promise; 364 | update( 365 | query: FilterQuery, 366 | update: UpdateQuery | Partial, 367 | options: ReplaceOneOptions & { single?: true, multi?: false, replace: true}, 368 | callback: Callback 369 | ): void; 370 | } 371 | 372 | export interface IObjectID { 373 | readonly toHexString: () => string; 374 | readonly toString: () => string; 375 | } 376 | 377 | export function id(hexstring: string): IObjectID; // returns ObjectId 378 | export function id(obj: IObjectID): IObjectID; // returns ObjectId 379 | export function id(): IObjectID; // returns new generated ObjectId 380 | export function cast(obj?: Object | Array | any): any; 381 | 382 | export default function ( 383 | database: string | Array, 384 | options?: MongoClientOptions & { 385 | collectionOptions?: CollectionOptions; 386 | } 387 | ): Promise & IMonkManager; 388 | } 389 | -------------------------------------------------------------------------------- /lib/applyMiddlewares.js: -------------------------------------------------------------------------------- 1 | var compose = require('./compose') 2 | 3 | /** 4 | * Creates a store enhancer that applies middleware to the dispatch method 5 | * of the Redux store. This is handy for a variety of tasks, such as expressing 6 | * asynchronous actions in a concise manner, or logging every action payload. 7 | * 8 | * See `redux-thunk` package as an example of the Redux middleware. 9 | * 10 | * Because middleware is potentially asynchronous, this should be the first 11 | * store enhancer in the composition chain. 12 | * 13 | * Note that each middleware will be given the `dispatch` and `getState` functions 14 | * as named arguments. 15 | * 16 | * @param {...Function} middlewares The middleware chain to be applied. 17 | * @returns {Function} A store enhancer applying the middleware. 18 | */ 19 | module.exports = function applyMiddleware (middlewares) { 20 | return function (monkInstance, collection) { 21 | var chain = [] 22 | 23 | var middlewareAPI = { 24 | monkInstance, 25 | collection 26 | } 27 | chain = middlewares.map(function (middleware) { 28 | return middleware(middlewareAPI) 29 | }) 30 | return compose(chain) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/collection.js: -------------------------------------------------------------------------------- 1 | var applyMiddlewares = require('./applyMiddlewares') 2 | 3 | module.exports = Collection 4 | 5 | function Collection (manager, name, options) { 6 | this.manager = manager 7 | this.name = name 8 | this.options = options 9 | 10 | this.middlewares = this.options.middlewares || [] 11 | delete this.options.middlewares 12 | 13 | this.createIndex = this.createIndex.bind(this) 14 | this.index = this.ensureIndex = this.ensureIndex.bind(this) 15 | this.dropIndex = this.dropIndex.bind(this) 16 | this.indexes = this.indexes.bind(this) 17 | this.dropIndexes = this.dropIndexes.bind(this) 18 | this.update = this.update.bind(this) 19 | this.remove = this.remove.bind(this) 20 | this.findOneAndUpdate = this.findOneAndUpdate.bind(this) 21 | this.findOneAndDelete = this.findOneAndDelete.bind(this) 22 | this.insert = this.insert.bind(this) 23 | this.find = this.find.bind(this) 24 | this.distinct = this.distinct.bind(this) 25 | this.count = this.count.bind(this) 26 | this.findOne = this.findOne.bind(this) 27 | this.aggregate = this.aggregate.bind(this) 28 | this.drop = this.drop.bind(this) 29 | 30 | this._dispatch = applyMiddlewares(this.middlewares)(manager, this) 31 | } 32 | 33 | Collection.prototype.aggregate = function (stages, opts, fn) { 34 | if (typeof opts === 'function') { 35 | fn = opts 36 | opts = {} 37 | } 38 | 39 | return this._dispatch(function aggregate (args) { 40 | return args.col.aggregate(args.stages, args.options).toArray() 41 | })({options: opts, stages: stages, callback: fn}, 'aggregate') 42 | } 43 | 44 | Collection.prototype.bulkWrite = function (operations, opts, fn) { 45 | if (typeof opts === 'function') { 46 | fn = opts 47 | opts = {} 48 | } 49 | 50 | return this._dispatch(function bulkWrite (args) { 51 | return args.col.bulkWrite(args.operations, args.options) 52 | })({options: opts, operations: operations, callback: fn}, 'bulkWrite') 53 | } 54 | 55 | Collection.prototype.count = function (query, opts, fn) { 56 | if (typeof opts === 'function') { 57 | fn = opts 58 | opts = {} 59 | } 60 | 61 | if (typeof query === 'function') { 62 | fn = query 63 | query = {} 64 | } 65 | 66 | return this._dispatch(function count (args) { 67 | const {estimate, ...options} = args.options 68 | if (estimate) { 69 | return args.col.estimatedDocumentCount(options) 70 | } 71 | return args.col.countDocuments(args.query, options) 72 | })({options: opts, query: query, callback: fn}, 'count') 73 | } 74 | 75 | Collection.prototype.createIndex = function (fields, opts, fn) { 76 | if (typeof opts === 'function') { 77 | fn = opts 78 | opts = {} 79 | } 80 | 81 | return this._dispatch(function createIndex (args) { 82 | return args.col.createIndex(args.fields, args.options) 83 | })({options: opts, fields: fields, callback: fn}, 'createIndex') 84 | } 85 | 86 | Collection.prototype.distinct = function (field, query, opts, fn) { 87 | if (typeof opts === 'function') { 88 | fn = opts 89 | opts = {} 90 | } 91 | 92 | if (typeof query === 'function') { 93 | fn = query 94 | query = {} 95 | } 96 | 97 | return this._dispatch(function distinct (args) { 98 | return args.col.distinct(args.field, args.query, args.options) 99 | })({options: opts, query: query, field: field, callback: fn}, 'distinct') 100 | } 101 | 102 | Collection.prototype.drop = function (fn) { 103 | return this._dispatch(function drop (args) { 104 | return args.col.drop().catch(function (err) { 105 | if (err && err.message === 'ns not found') { 106 | return 'ns not found' 107 | } else { 108 | throw err 109 | } 110 | }) 111 | })({callback: fn}, 'drop') 112 | } 113 | 114 | Collection.prototype.dropIndex = function (fields, opts, fn) { 115 | if (typeof opts === 'function') { 116 | fn = opts 117 | opts = {} 118 | } 119 | 120 | return this._dispatch(function dropIndex (args) { 121 | return args.col.dropIndex(args.fields, args.options) 122 | })({options: opts, fields: fields, callback: fn}, 'dropIndex') 123 | } 124 | 125 | Collection.prototype.dropIndexes = function (fn) { 126 | return this._dispatch(function dropIndexes (args) { 127 | return args.col.dropIndexes() 128 | })({callback: fn}, 'dropIndexes') 129 | } 130 | 131 | Collection.prototype.ensureIndex = function (fields, opts, fn) { 132 | if (typeof opts === 'function') { 133 | fn = opts 134 | opts = {} 135 | } 136 | 137 | console.warn('DEPRECATED (collection.ensureIndex): use collection.createIndex instead (see https://Automattic.github.io/monk/docs/collection/createIndex.html)') 138 | 139 | return this._dispatch(function ensureIndex (args) { 140 | return args.col.ensureIndex(args.fields, args.options) 141 | })({options: opts, fields: fields, callback: fn}, 'ensureIndex') 142 | } 143 | 144 | Collection.prototype.find = function (query, opts, fn) { 145 | if (typeof opts === 'function') { 146 | fn = opts 147 | opts = {} 148 | } 149 | 150 | if ((opts || {}).rawCursor) { 151 | delete opts.rawCursor 152 | return this._dispatch(function find (args) { 153 | return Promise.resolve(args.col.find(args.query, args.options)) 154 | })({options: opts, query: query, callback: fn}, 'find') 155 | } 156 | 157 | var promise = this._dispatch(function find (args) { 158 | var cursor = args.col.find(args.query, args.options) 159 | 160 | if (!(opts || {}).stream && !promise.eachListener) { 161 | return cursor.toArray() 162 | } 163 | 164 | if (typeof (opts || {}).stream === 'function') { 165 | promise.eachListener = (opts || {}).stream 166 | } 167 | 168 | var didClose = false 169 | var didFinish = false 170 | var processing = 0 171 | 172 | function close () { 173 | didClose = true 174 | processing -= 1 175 | cursor.close() 176 | } 177 | 178 | function pause () { 179 | processing += 1 180 | cursor.pause() 181 | } 182 | 183 | return new Promise(function (resolve, reject) { 184 | cursor.on('data', function (doc) { 185 | if (!didClose) { 186 | promise.eachListener(doc, { 187 | close: close, 188 | pause: pause, 189 | resume: resume 190 | }) 191 | } 192 | }) 193 | 194 | function resume () { 195 | processing -= 1 196 | cursor.resume() 197 | if (processing === 0 && didFinish) { 198 | done() 199 | } 200 | } 201 | 202 | function done () { 203 | didFinish = true 204 | if (processing <= 0) { 205 | if (fn) { 206 | fn() 207 | } 208 | resolve() 209 | } 210 | } 211 | 212 | cursor.on('close', done) 213 | cursor.on('end', done) 214 | 215 | cursor.on('error', function (err) { 216 | if (fn) { 217 | fn(err) 218 | } 219 | reject(err) 220 | }) 221 | }) 222 | })({options: opts, query: query, callback: fn}, 'find') 223 | 224 | promise.each = function (eachListener) { 225 | promise.eachListener = eachListener 226 | return promise 227 | } 228 | 229 | return promise 230 | } 231 | 232 | Collection.prototype.findOne = function (query, opts, fn) { 233 | if (typeof opts === 'function') { 234 | fn = opts 235 | opts = {} 236 | } 237 | 238 | return this._dispatch(function findOne (args) { 239 | return args.col.find(args.query, args.options).limit(1).toArray() 240 | .then(function (docs) { 241 | return (docs && docs[0]) || null 242 | }) 243 | })({options: opts, query: query, callback: fn}, 'findOne') 244 | } 245 | 246 | Collection.prototype.findOneAndDelete = function (query, opts, fn) { 247 | if (typeof opts === 'function') { 248 | fn = opts 249 | opts = {} 250 | } 251 | return this._dispatch(function findOneAndDelete (args) { 252 | return args.col.findOneAndDelete(args.query, args.options) 253 | .then(function (doc) { 254 | if (doc && typeof doc.value !== 'undefined') { 255 | return doc.value 256 | } 257 | if (doc.ok && doc.lastErrorObject && doc.lastErrorObject.n === 0) { 258 | return null 259 | } 260 | return doc 261 | }) 262 | })({options: opts, query: query, callback: fn}, 'findOneAndDelete') 263 | } 264 | 265 | Collection.prototype.findOneAndUpdate = function (query, update, opts, fn) { 266 | if (typeof opts === 'function') { 267 | fn = opts 268 | opts = {} 269 | } 270 | return this._dispatch(function findOneAndUpdate (args) { 271 | var method = 'findOneAndUpdate' 272 | if (typeof (args.options || {}).returnOriginal === 'undefined') { 273 | args.options.returnOriginal = false 274 | } 275 | if (args.options.replaceOne | args.options.replace) { 276 | method = 'findOneAndReplace' 277 | } 278 | return args.col[method](args.query, args.update, args.options) 279 | .then(function (doc) { 280 | if (doc && typeof doc.value !== 'undefined') { 281 | return doc.value 282 | } 283 | if (doc.ok && doc.lastErrorObject && doc.lastErrorObject.n === 0) { 284 | return null 285 | } 286 | return doc 287 | }) 288 | })({options: opts, query: query, update: update, callback: fn}, 'findOneAndUpdate') 289 | } 290 | 291 | Collection.prototype.geoHaystackSearch = function (x, y, opts, fn) { 292 | return this._dispatch(function update (args) { 293 | return args.col.geoHaystackSearch(args.x, args.y, args.options).then(function (doc) { 294 | return (doc && doc.results) || doc 295 | }) 296 | })({x: x, y: y, options: opts, callback: fn}, 'geoHaystackSearch') 297 | } 298 | 299 | Collection.prototype.geoNear = function () { 300 | throw new Error('geoNear command is not supported anymore (see https://docs.mongodb.com/manual/reference/command/geoNear)') 301 | } 302 | 303 | Collection.prototype.group = function (keys, condition, initial, reduce, finalize, command, opts, fn) { 304 | if (typeof opts === 'function') { 305 | fn = opts 306 | opts = {} 307 | } 308 | 309 | console.warn('DEPRECATED (collection.group): MongoDB 3.6 or higher no longer supports the group command. We recommend rewriting using the aggregation framework.') 310 | 311 | return this._dispatch(function group (args) { 312 | return args.col.group(args.keys, args.condition, args.initial, args.reduce, args.finalize, args.command, args.options) 313 | })({options: opts, keys: keys, condition: condition, initial: initial, reduce: reduce, finalize: finalize, command: command, callback: fn}, 'group') 314 | } 315 | 316 | Collection.prototype.indexes = function (fn) { 317 | return this._dispatch(function indexes (args) { 318 | return args.col.indexInformation() 319 | })({callback: fn}, 'indexes') 320 | } 321 | 322 | Collection.prototype.insert = function (data, opts, fn) { 323 | if (typeof opts === 'function') { 324 | fn = opts 325 | opts = {} 326 | } 327 | 328 | return this._dispatch(function insert (args) { 329 | var arrayInsert = Array.isArray(args.data) 330 | 331 | if (arrayInsert && args.data.length === 0) { 332 | return Promise.resolve([]) 333 | } 334 | const insertop = arrayInsert 335 | ? args.col.insertMany(args.data, args.options) 336 | : args.col.insertOne(args.data, args.options) 337 | return insertop.then(function (docs) { 338 | var res = (docs || {}).ops 339 | if (res && !arrayInsert) { 340 | res = docs.ops[0] 341 | } 342 | return res 343 | }) 344 | })({data: data, options: opts, callback: fn}, 'insert') 345 | } 346 | 347 | Collection.prototype.mapReduce = function (map, reduce, opts, fn) { 348 | return this._dispatch(function update (args) { 349 | return args.col.mapReduce(args.map, args.reduce, args.options) 350 | })({map: map, reduce: reduce, options: opts, callback: fn}, 'mapReduce') 351 | } 352 | 353 | Collection.prototype.remove = function (query, opts, fn) { 354 | if (typeof opts === 'function') { 355 | fn = opts 356 | opts = {} 357 | } 358 | return this._dispatch(function remove (args) { 359 | var options = args.options || {} 360 | var method = options.single || options.multi === false ? 'deleteOne' : 'deleteMany' 361 | return args.col[method](args.query, args.options) 362 | })({query: query, options: opts, callback: fn}, 'remove') 363 | } 364 | 365 | Collection.prototype.stats = function (opts, fn) { 366 | if (typeof opts === 'function') { 367 | fn = opts 368 | opts = {} 369 | } 370 | return this._dispatch(function remove (args) { 371 | return args.col.stats(args.options) 372 | })({options: opts, callback: fn}, 'stats') 373 | } 374 | 375 | Collection.prototype.update = function (query, update, opts, fn) { 376 | if (typeof opts === 'function') { 377 | fn = opts 378 | opts = {} 379 | } 380 | 381 | return this._dispatch(function update (args) { 382 | var options = args.options || {} 383 | var method = options.multi || options.single === false ? 'updateMany' : 'updateOne' 384 | if (options.replace || options.replaceOne) { 385 | if (options.multi || options.single === false) { 386 | throw new Error('The `replace` option is only available for single updates.') 387 | } 388 | method = 'replaceOne' 389 | } 390 | return args.col[method](args.query, args.update, args.options).then(function (doc) { 391 | return (doc && doc.result) || doc 392 | }) 393 | })({update: update, query: query, options: opts, callback: fn}, 'update') 394 | } 395 | -------------------------------------------------------------------------------- /lib/compose.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Composes single-argument functions from right to left. The rightmost 3 | * function can take multiple arguments as it provides the signature for 4 | * the resulting composite function. 5 | * 6 | * @param {...Function} funcs The functions to compose. 7 | * @returns {Function} A function obtained by composing the argument functions 8 | * from right to left. For example, compose(f, g, h) is identical to doing 9 | * (args) => f(g(h(args))). 10 | */ 11 | 12 | module.exports = function compose (funcs) { 13 | if (funcs.length === 0) { 14 | return function (args) { return args } 15 | } 16 | 17 | if (funcs.length === 1) { 18 | return funcs[0] 19 | } 20 | 21 | return funcs.reduce(function (a, b) { 22 | return function (args) { 23 | return a(b(args)) 24 | } 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | var ObjectId = require('mongodb').ObjectID 2 | 3 | /** 4 | * Casts to objectid 5 | * 6 | * @param {Mixed} str - hex id or ObjectId 7 | * @return {ObjectId} 8 | * @api public 9 | */ 10 | 11 | exports.id = function (str) { 12 | if (str == null) return ObjectId() 13 | return typeof str === 'string' ? ObjectId.createFromHexString(str) : str 14 | } 15 | 16 | /** 17 | * Applies ObjectId casting to _id fields. 18 | * 19 | * @param {Object} optional, query 20 | * @return {Object} query 21 | * @private 22 | */ 23 | 24 | exports.cast = function cast (obj) { 25 | if (Array.isArray(obj)) { 26 | return obj.map(cast) 27 | } 28 | 29 | if (obj && typeof obj === 'object') { 30 | Object.keys(obj).forEach(function (k) { 31 | if (k === '_id' && obj._id) { 32 | if (obj._id.$in) { 33 | obj._id.$in = obj._id.$in.map(exports.id) 34 | } else if (obj._id.$nin) { 35 | obj._id.$nin = obj._id.$nin.map(exports.id) 36 | } else if (obj._id.$ne) { 37 | obj._id.$ne = exports.id(obj._id.$ne) 38 | } else { 39 | obj._id = exports.id(obj._id) 40 | } 41 | } else { 42 | obj[k] = cast(obj[k]) 43 | } 44 | }) 45 | } 46 | 47 | return obj 48 | } 49 | -------------------------------------------------------------------------------- /lib/manager.js: -------------------------------------------------------------------------------- 1 | var mongo = require('mongodb') 2 | var Debug = require('debug') 3 | var objectAssign = require('object-assign') 4 | var monkDebug = Debug('monk:manager') 5 | var Collection = require('./collection') 6 | var ObjectId = mongo.ObjectID 7 | var MongoClient = mongo.MongoClient 8 | var EventEmitter = require('events').EventEmitter 9 | var inherits = require('util').inherits 10 | 11 | var STATE = { 12 | CLOSED: 'closed', 13 | OPENING: 'opening', 14 | OPEN: 'open' 15 | } 16 | 17 | var FIELDS_TO_CAST = ['operations', 'query', 'data', 'update'] 18 | 19 | var DEFAULT_OPTIONS = { 20 | castIds: true, 21 | middlewares: [ 22 | require('monk-middleware-query'), 23 | require('monk-middleware-options'), 24 | require('monk-middleware-cast-ids')(FIELDS_TO_CAST), 25 | require('monk-middleware-fields'), 26 | require('monk-middleware-handle-callback'), 27 | require('monk-middleware-wait-for-connection') 28 | ] 29 | } 30 | 31 | /* 32 | * Module exports. 33 | */ 34 | 35 | module.exports = Manager 36 | 37 | /** 38 | * Monk constructor. 39 | * 40 | * @param {Array|String} uri replica sets can be an array or 41 | * comma-separated 42 | * @param {Object|Function} opts or connect callback 43 | * @param {Function} fn connect callback 44 | * @return {Promise} resolve when the connection is opened 45 | */ 46 | 47 | function Manager (uri, opts, fn) { 48 | if (!uri) { 49 | throw Error('No connection URI provided.') 50 | } 51 | 52 | if (!(this instanceof Manager)) { 53 | return new Manager(uri, opts, fn) 54 | } 55 | 56 | if (typeof opts === 'function') { 57 | fn = opts 58 | opts = {} 59 | } 60 | 61 | opts = opts || {} 62 | 63 | if (!opts.hasOwnProperty('useNewUrlParser')) { 64 | opts.useNewUrlParser = true 65 | } 66 | if (!opts.hasOwnProperty('useUnifiedTopology')) { 67 | opts.useUnifiedTopology = true 68 | } 69 | 70 | this._collectionOptions = objectAssign({}, DEFAULT_OPTIONS, opts.collectionOptions || {}) 71 | this._collectionOptions.middlewares = this._collectionOptions.middlewares.slice(0) 72 | delete opts.collectionOptions 73 | 74 | if (Array.isArray(uri)) { 75 | if (!opts.database) { 76 | for (var i = 0, l = uri.length; i < l; i++) { 77 | if (!opts.database) { 78 | opts.database = uri[i].replace(/([^\/])+\/?/, '') // eslint-disable-line 79 | } 80 | uri[i] = uri[i].replace(/\/.*/, '') 81 | } 82 | } 83 | uri = uri.join(',') + '/' + opts.database 84 | monkDebug('repl set connection "%j" to database "%s"', uri, opts.database) 85 | } 86 | 87 | if (typeof uri === 'string') { 88 | if (!/^mongodb(\+srv)?:\/\//.test(uri)) { 89 | uri = 'mongodb://' + uri 90 | } 91 | } 92 | 93 | this._state = STATE.OPENING 94 | 95 | this._queue = [] 96 | this.on('open', function (db) { 97 | monkDebug('connection opened') 98 | monkDebug('emptying queries queue (%s to go)', this._queue.length) 99 | this._queue.forEach(function (cb) { 100 | cb(db) 101 | }) 102 | }.bind(this)) 103 | 104 | this._connectionURI = uri 105 | this._connectionOptions = opts 106 | 107 | this.open(uri, opts, fn && function (err) { 108 | fn(err, this) 109 | }.bind(this)) 110 | 111 | this.helper = { 112 | id: ObjectId 113 | } 114 | 115 | this.collections = {} 116 | 117 | this.open = this.open.bind(this) 118 | this.close = this.close.bind(this) 119 | this.executeWhenOpened = this.executeWhenOpened.bind(this) 120 | this.collection = this.col = this.get = this.get.bind(this) 121 | this.oid = this.id 122 | this.setDefaultCollectionOptions = this.setDefaultCollectionOptions.bind(this) 123 | this.addMiddleware = this.addMiddleware.bind(this) 124 | } 125 | 126 | /* 127 | * Inherits from EventEmitter. 128 | */ 129 | 130 | inherits(Manager, EventEmitter) 131 | 132 | /** 133 | * Open the connection 134 | * @private 135 | */ 136 | Manager.prototype.open = function (uri, opts, fn) { 137 | MongoClient.connect(uri, opts, function (err, client) { 138 | if (err) { 139 | this._state = STATE.CLOSED 140 | this.emit('error-opening', err) 141 | } else { 142 | this._state = STATE.OPEN 143 | 144 | this._client = client 145 | this._db = client.db() 146 | 147 | // set up events 148 | var self = this 149 | ;['authenticated', 'close', 'error', 'fullsetup', 'parseError', 'reconnect', 'timeout'].forEach(function (eventName) { 150 | self._db.on(eventName, function (e) { 151 | self.emit(eventName, e) 152 | }) 153 | }) 154 | self.on('reconnect', function () { 155 | self._state = STATE.OPEN 156 | }) 157 | 158 | this.emit('open', this._db) 159 | } 160 | if (fn) { 161 | fn(err, this) 162 | } 163 | }.bind(this)) 164 | } 165 | 166 | /** 167 | * Execute when connection opened. 168 | * @private 169 | */ 170 | 171 | Manager.prototype.executeWhenOpened = function () { 172 | switch (this._state) { 173 | case STATE.OPEN: 174 | return Promise.resolve(this._db) 175 | case STATE.OPENING: 176 | return new Promise(function (resolve) { 177 | this._queue.push(resolve) 178 | }.bind(this)) 179 | case STATE.CLOSED: 180 | default: 181 | return new Promise(function (resolve) { 182 | this._queue.push(resolve) 183 | this.open(this._connectionURI, this._connectionOptions) 184 | }.bind(this)) 185 | } 186 | } 187 | 188 | /** 189 | * Then 190 | * 191 | * @param {Function} [fn] - callback 192 | */ 193 | 194 | Manager.prototype.then = function (fn) { 195 | return new Promise(function (resolve, reject) { 196 | this.once('open', resolve) 197 | this.once('error-opening', reject) 198 | }.bind(this)).then(fn.bind(null, this)) 199 | } 200 | 201 | /** 202 | * Catch 203 | * 204 | * @param {Function} [fn] - callback 205 | */ 206 | 207 | Manager.prototype.catch = function (fn) { 208 | return new Promise(function (resolve) { 209 | this.once('error-opening', resolve) 210 | }.bind(this)).then(fn.bind(null)) 211 | } 212 | 213 | /** 214 | * Closes the connection. 215 | * 216 | * @param {Boolean} [force] - Force close, emitting no events 217 | * @param {Function} [fn] - callback 218 | * @return {Promise} 219 | */ 220 | 221 | Manager.prototype.close = function (force, fn) { 222 | if (typeof force === 'function') { 223 | fn = force 224 | force = false 225 | } 226 | 227 | var self = this 228 | function close (resolve) { 229 | self._client.close(force, function () { 230 | self._state = STATE.CLOSED 231 | if (fn) { 232 | fn() 233 | } 234 | resolve() 235 | }) 236 | } 237 | 238 | switch (this._state) { 239 | case STATE.CLOSED: 240 | if (fn) { 241 | fn() 242 | } 243 | return Promise.resolve() 244 | case STATE.OPENING: 245 | return new Promise(function (resolve) { 246 | self._queue.push(function () { 247 | close(resolve) 248 | }) 249 | }) 250 | case STATE.OPEN: 251 | default: 252 | return new Promise(function (resolve) { 253 | close(resolve) 254 | }) 255 | } 256 | } 257 | 258 | /** 259 | * Lists all collections. 260 | * 261 | * @param {Object} [query] - A query expression to filter the list of collections. 262 | * @return {Array} array of all collections 263 | */ 264 | 265 | Manager.prototype.listCollections = function (query) { 266 | var self = this 267 | return this.executeWhenOpened().then(function (db) { 268 | return db.listCollections(query).toArray().then(x => x.map( 269 | x => self.get(x.name) 270 | )) 271 | }) 272 | } 273 | 274 | /** 275 | * Gets a collection. 276 | * 277 | * @param {String} name - name of the mongo collection 278 | * @param {Object} [options] - options to pass to the collection 279 | * @return {Collection} collection to query against 280 | */ 281 | 282 | Manager.prototype.get = function (name, options) { 283 | if ((options || {}).cache === false || !this.collections[name]) { 284 | delete (options || {}).cache 285 | this.collections[name] = new Collection(this, name, objectAssign({}, this._collectionOptions || {}, options || {})) 286 | } 287 | 288 | return this.collections[name] 289 | } 290 | 291 | /** 292 | * Create a collection. 293 | * 294 | * @param {String} name - name of the mongo collection 295 | * @param {Object} [creationOptions] - options used when creating the collection 296 | * @param {Object} [options] - options to pass to the collection 297 | * @return {Collection} collection to query against 298 | */ 299 | 300 | Manager.prototype.create = function (name, creationOptions, options) { 301 | this.executeWhenOpened().then(function (db) { 302 | db.createCollection(name, creationOptions) 303 | }).catch(function (err) { 304 | this.emit('error', err) 305 | }) 306 | 307 | return this.get(name, options) 308 | } 309 | 310 | Manager.prototype.setDefaultCollectionOptions = function (options) { 311 | this._collectionOptions = options 312 | } 313 | 314 | Manager.prototype.addMiddleware = function (middleware) { 315 | if (!this._collectionOptions) { 316 | this._collectionOptions = {} 317 | } 318 | if (!this._collectionOptions.middlewares) { 319 | this._collectionOptions.middlewares = [] 320 | } 321 | this._collectionOptions.middlewares.push(middleware) 322 | } 323 | 324 | Manager.prototype.id = function (str) { 325 | return require('./helpers').id(str) 326 | } 327 | 328 | Manager.prototype.cast = function (obj) { 329 | return require('./helpers').cast(obj) 330 | } 331 | -------------------------------------------------------------------------------- /lib/monk.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Module exports 4 | */ 5 | var manager = require('./manager') 6 | module.exports = exports = manager 7 | exports.default = manager 8 | exports.manager = manager 9 | 10 | /* 11 | * Expose Collection 12 | */ 13 | 14 | exports.Collection = require('./collection') 15 | 16 | /* 17 | * Expose helpers at the top level 18 | */ 19 | 20 | var helpers = require('./helpers') 21 | 22 | for (var key in helpers) { 23 | exports[key] = helpers[key] 24 | } 25 | -------------------------------------------------------------------------------- /middlewares/castIds/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/monk/5505e62ce616682ea2a6acec0cd9af01afa69069/middlewares/castIds/README.md -------------------------------------------------------------------------------- /middlewares/castIds/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function castIdsMiddleware (fieldsToCast) { 2 | return function (context) { 3 | return function (next) { 4 | return function (args, method) { 5 | if ((args.options || {}).castIds === false) { 6 | delete args.options.castIds 7 | return next(args, method) 8 | } 9 | 10 | if ((args.options || {}).castIds) { 11 | delete args.options.castIds 12 | } 13 | 14 | fieldsToCast.forEach(function (k) { 15 | if (args[k]) { 16 | args[k] = context.monkInstance.cast(args[k]) 17 | } 18 | }) 19 | 20 | return next(args, method) 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /middlewares/castIds/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monk-middleware-cast-ids", 3 | "description": "A monk middleware to parse the ids", 4 | "version": "0.2.1", 5 | "main": "index.js", 6 | "keywords": [ 7 | "monk", 8 | "middleware", 9 | "ids" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/Automattic/monk.git" 14 | }, 15 | "license": "MIT" 16 | } 17 | -------------------------------------------------------------------------------- /middlewares/fields/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/monk/5505e62ce616682ea2a6acec0cd9af01afa69069/middlewares/fields/README.md -------------------------------------------------------------------------------- /middlewares/fields/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function fieldsMiddleware (context) { 2 | return function (next) { 3 | return function (args, method) { 4 | if (!args.fields) { 5 | return next(args, method) 6 | } 7 | 8 | if (!Array.isArray(args.fields) && typeof args.fields === 'object') { 9 | return next(args, method) 10 | } 11 | 12 | var fields = {} 13 | args.fields = typeof args.fields === 'string' ? args.fields.split(' ') : (args.fields || []) 14 | 15 | for (var i = 0, l = args.fields.length; i < l; i++) { 16 | fields[args.fields[i]] = 1 17 | } 18 | 19 | args.fields = fields 20 | return next(args, method) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /middlewares/fields/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monk-middleware-fields", 3 | "description": "A monk middleware to parse the fields", 4 | "version": "0.2.0", 5 | "main": "index.js", 6 | "keywords": [ 7 | "monk", 8 | "middleware", 9 | "fields" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/Automattic/monk.git" 14 | }, 15 | "license": "MIT" 16 | } 17 | -------------------------------------------------------------------------------- /middlewares/handle-callback/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/monk/5505e62ce616682ea2a6acec0cd9af01afa69069/middlewares/handle-callback/README.md -------------------------------------------------------------------------------- /middlewares/handle-callback/index.js: -------------------------------------------------------------------------------- 1 | function thenFn (fn) { 2 | return function (res) { 3 | if (fn && typeof fn === 'function') { 4 | setTimeout(fn, 0, null, res) 5 | } 6 | return res 7 | } 8 | } 9 | 10 | function catchFn (fn) { 11 | return function (err) { 12 | if (fn && typeof fn === 'function') { 13 | setTimeout(fn, 0, err) 14 | return 15 | } 16 | throw err 17 | } 18 | } 19 | 20 | module.exports = function handleCallback (context) { 21 | return function (next) { 22 | return function (args, method) { 23 | return next(args, method).then(thenFn(args.callback)).catch(catchFn(args.callback)) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /middlewares/handle-callback/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monk-middleware-handle-callback", 3 | "description": "A monk middleware to handle callback", 4 | "version": "0.2.2", 5 | "main": "index.js", 6 | "keywords": [ 7 | "monk", 8 | "middleware", 9 | "callback" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/Automattic/monk.git" 14 | }, 15 | "license": "MIT" 16 | } 17 | -------------------------------------------------------------------------------- /middlewares/options/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/monk/5505e62ce616682ea2a6acec0cd9af01afa69069/middlewares/options/README.md -------------------------------------------------------------------------------- /middlewares/options/index.js: -------------------------------------------------------------------------------- 1 | function fields (obj, numberWhenMinus) { 2 | if (!Array.isArray(obj) && typeof obj === 'object') { 3 | return obj 4 | } 5 | 6 | var fields = {} 7 | obj = typeof obj === 'string' ? obj.split(' ') : (obj || []) 8 | 9 | for (var i = 0, l = obj.length; i < l; i++) { 10 | if (obj[i][0] === '-') { 11 | fields[obj[i].substr(1)] = numberWhenMinus 12 | } else { 13 | fields[obj[i]] = 1 14 | } 15 | } 16 | 17 | return fields 18 | } 19 | 20 | module.exports = function optionsMiddleware (context) { 21 | return function (next) { 22 | return function (args, method) { 23 | var collection = context.collection 24 | if (typeof args.options === 'string' || Array.isArray(args.options)) { 25 | args.options = { fields: fields(args.options) } 26 | return next(args, method) 27 | } 28 | args.options = args.options || {} 29 | if (args.options.fields) { 30 | args.options.fields = fields(args.options.fields, 0) 31 | } 32 | if (args.options.sort) { 33 | args.options.sort = fields(args.options.sort, -1) 34 | } 35 | 36 | for (var j in collection.options) { 37 | if (!(j in args.options)) { 38 | args.options[j] = collection.options[j] 39 | } 40 | } 41 | return next(args, method) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /middlewares/options/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monk-middleware-options", 3 | "description": "A monk middleware to parse the options", 4 | "version": "0.2.1", 5 | "main": "index.js", 6 | "keywords": [ 7 | "monk", 8 | "middleware", 9 | "options" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/Automattic/monk.git" 14 | }, 15 | "license": "MIT" 16 | } 17 | -------------------------------------------------------------------------------- /middlewares/query/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/monk/5505e62ce616682ea2a6acec0cd9af01afa69069/middlewares/query/README.md -------------------------------------------------------------------------------- /middlewares/query/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function queryMiddleware (context) { 2 | return function (next) { 3 | return function (args, method) { 4 | if (!args.query) { 5 | return next(args, method) 6 | } 7 | 8 | if (typeof args.query === 'string' || typeof args.query.toHexString === 'function') { 9 | args.query = {_id: args.query} 10 | } 11 | 12 | return next(args, method) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /middlewares/query/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monk-middleware-query", 3 | "description": "A monk middleware to parse the query", 4 | "version": "0.2.0", 5 | "main": "index.js", 6 | "keywords": [ 7 | "monk", 8 | "middleware", 9 | "query" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/Automattic/monk.git" 14 | }, 15 | "license": "MIT" 16 | } 17 | -------------------------------------------------------------------------------- /middlewares/wait-for-connection/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/monk/5505e62ce616682ea2a6acec0cd9af01afa69069/middlewares/wait-for-connection/README.md -------------------------------------------------------------------------------- /middlewares/wait-for-connection/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function waitForConnection (context) { 2 | return function (next) { 3 | return function (args, method) { 4 | return context.monkInstance.executeWhenOpened().then(function (db) { 5 | return db.collection(context.collection.name) 6 | }).then(function (col) { 7 | args.col = col 8 | return next(args, method) 9 | }) 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /middlewares/wait-for-connection/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monk-middleware-wait-for-connection", 3 | "description": "A monk middleware to wait for the connection", 4 | "version": "0.2.0", 5 | "main": "index.js", 6 | "keywords": [ 7 | "monk", 8 | "middleware", 9 | "wait", 10 | "connection" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/Automattic/monk.git" 15 | }, 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monk", 3 | "description": "The wise MongoDB API", 4 | "version": "7.3.4", 5 | "main": "lib/monk.js", 6 | "types": "./index.d.ts", 7 | "keywords": [ 8 | "monk", 9 | "mongodb", 10 | "mongo", 11 | "driver" 12 | ], 13 | "files": [ 14 | "lib", 15 | "index.d.ts" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/Automattic/monk.git" 20 | }, 21 | "dependencies": { 22 | "@types/mongodb": "^3.5.25", 23 | "debug": "*", 24 | "mongodb": "^3.2.3", 25 | "monk-middleware-cast-ids": "^0.2.1", 26 | "monk-middleware-fields": "^0.2.0", 27 | "monk-middleware-handle-callback": "^0.2.0", 28 | "monk-middleware-options": "^0.2.1", 29 | "monk-middleware-query": "^0.2.0", 30 | "monk-middleware-wait-for-connection": "^0.2.0", 31 | "object-assign": "^4.1.1" 32 | }, 33 | "devDependencies": { 34 | "ava": "^0.19.1", 35 | "codecov": "^2.2.0", 36 | "eslint": "^3.19.0", 37 | "eslint-config-standard": "^10.2.1", 38 | "eslint-plugin-ava": "^4.2.0", 39 | "eslint-plugin-import": "2.2.0", 40 | "eslint-plugin-node": "4.2.2", 41 | "eslint-plugin-promise": "^3.5.0", 42 | "eslint-plugin-standard": "^3.0.1", 43 | "gitbook-cli": "^2.3.0", 44 | "gitbook-plugin-anker-enable": "^0.0.4", 45 | "gitbook-plugin-custom-favicon": "0.0.4", 46 | "gitbook-plugin-edit-link": "2.0.2", 47 | "gitbook-plugin-github": "^2.0.0", 48 | "gitbook-plugin-prism": "^2.2.0", 49 | "monk-middleware-debug": "^0.2.0", 50 | "nyc": "^15.0.0" 51 | }, 52 | "license": "MIT", 53 | "scripts": { 54 | "test": "make test", 55 | "coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov" 56 | }, 57 | "nyc": { 58 | "include": [ 59 | "lib/**" 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/casting.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const monk = require('../lib/monk') 4 | const util = require('../lib/helpers') 5 | 6 | test('string -> id', (t) => { 7 | const oid = monk.id('4ee0fd75d6bd52107c000118') 8 | t.is(oid.toHexString(), '4ee0fd75d6bd52107c000118') 9 | }) 10 | 11 | test('id -> id', (t) => { 12 | const oid = monk.id(monk.id('4ee0fd75d6bd52107c000118')) 13 | t.is(oid.toHexString(), '4ee0fd75d6bd52107c000118') 14 | }) 15 | 16 | test('new id', (t) => { 17 | const oid = monk.id() 18 | t.is(typeof oid.toHexString(), 'string') 19 | }) 20 | 21 | test('should cast ids inside $and', (t) => { 22 | const cast = util.cast({ 23 | $and: [{_id: '4ee0fd75d6bd52107c000118'}] 24 | }) 25 | 26 | const oid = monk.id(cast.$and[0]._id) 27 | t.is(oid.toHexString(), '4ee0fd75d6bd52107c000118') 28 | }) 29 | 30 | test('should cast ids inside $nor', (t) => { 31 | const cast = util.cast({ 32 | $nor: [{_id: '4ee0fd75d6bd52107c000118'}] 33 | }) 34 | 35 | const oid = monk.id(cast.$nor[0]._id) 36 | t.is(oid.toHexString(), '4ee0fd75d6bd52107c000118') 37 | }) 38 | 39 | test('should cast ids inside $not queries', (t) => { 40 | const cast = util.cast({$not: {_id: '4ee0fd75d6bd52107c000118'}}) 41 | 42 | const oid = monk.id(cast.$not._id) 43 | t.is(oid.toHexString(), '4ee0fd75d6bd52107c000118') 44 | }) 45 | 46 | test('should cast ids inside $ne queries', (t) => { 47 | const cast = util.cast({_id: {$ne: '4ee0fd75d6bd52107c000118'}}) 48 | 49 | const oid = monk.id(cast._id.$ne) 50 | t.is(oid.toHexString(), '4ee0fd75d6bd52107c000118') 51 | }) 52 | 53 | test('should cast ids inside $in queries', (t) => { 54 | const cast = util.cast({_id: {$in: ['4ee0fd75d6bd52107c000118']}}) 55 | 56 | const oid = monk.id(cast._id.$in[0]) 57 | t.is(oid.toHexString(), '4ee0fd75d6bd52107c000118') 58 | }) 59 | 60 | test('should cast ids inside $nin queries', (t) => { 61 | const cast = util.cast({_id: {$nin: ['4ee0fd75d6bd52107c000118']}}) 62 | 63 | const oid = monk.id(cast._id.$nin[0]) 64 | t.is(oid.toHexString(), '4ee0fd75d6bd52107c000118') 65 | }) 66 | 67 | test('should cast ids inside $set queries', (t) => { 68 | const cast = util.cast({$set: {_id: '4ee0fd75d6bd52107c000118'}}) 69 | 70 | const oid = monk.id(cast.$set._id) 71 | t.is(oid.toHexString(), '4ee0fd75d6bd52107c000118') 72 | }) 73 | 74 | test('should cast ids inside $or', (t) => { 75 | const cast = util.cast({ 76 | $or: [{_id: '4ee0fd75d6bd52107c000118'}] 77 | }) 78 | 79 | const oid = monk.id(cast.$or[0]._id) 80 | t.is(oid.toHexString(), '4ee0fd75d6bd52107c000118') 81 | }) 82 | 83 | test('should cast nested ids', (t) => { 84 | const cast = util.cast({ 85 | $pull: { items: [{ _id: '4ee0fd75d6bd52107c000118' }] } 86 | }) 87 | 88 | const oid = monk.id(cast.$pull.items[0]._id) 89 | t.is(oid.toHexString(), '4ee0fd75d6bd52107c000118') 90 | }) 91 | 92 | test('should not fail when casting 0', (t) => { 93 | const cast = util.cast(0) 94 | 95 | t.is(cast, 0) 96 | }) 97 | 98 | test('should not fail when casting null', (t) => { 99 | const cast = util.cast(null) 100 | 101 | t.is(cast, null) 102 | }) 103 | -------------------------------------------------------------------------------- /test/collection.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const monk = require('../lib/monk') 4 | 5 | const db = monk('127.0.0.1/monk') 6 | db.addMiddleware(require('monk-middleware-debug')) 7 | const users = db.get('users-' + Date.now()) 8 | const indexCol = db.get('index-' + Date.now()) 9 | 10 | test.after(() => { 11 | return users.drop() 12 | }) 13 | 14 | test('createIndex > should accept a field string', (t) => { 15 | return indexCol.createIndex('name.first').then(indexCol.indexes).then((indexes) => { 16 | t.not(indexes['name.first_1'], undefined) 17 | }) 18 | }) 19 | 20 | test('createIndex > should accept an object argument', (t) => { 21 | return indexCol.createIndex({location: '2dsphere'}).then(indexCol.indexes).then((indexes) => { 22 | t.not(indexes.location_2dsphere, undefined) 23 | }) 24 | }) 25 | 26 | test('createIndex > should accept space-delimited compound indexes', (t) => { 27 | return indexCol.createIndex('name last').then(indexCol.indexes).then((indexes) => { 28 | t.not(indexes.name_1_last_1, undefined) 29 | }) 30 | }) 31 | 32 | test('createIndex > should accept array compound indexes', (t) => { 33 | return indexCol.createIndex(['nombre', 'apellido']).then(indexCol.indexes).then((indexes) => { 34 | t.not(indexes.nombre_1_apellido_1, undefined) 35 | }) 36 | }) 37 | 38 | test('createIndex > should accept object compound indexes', (t) => { 39 | return indexCol.createIndex({ up: 1, down: -1 }).then(indexCol.indexes).then((indexes) => { 40 | t.not(indexes['up_1_down_-1'], undefined) 41 | }) 42 | }) 43 | 44 | test('createIndex > should accept options', (t) => { 45 | return indexCol.createIndex({ woot: 1 }, { unique: true }).then(indexCol.indexes).then((indexes) => { 46 | t.not(indexes.woot_1, undefined) 47 | }) 48 | }) 49 | 50 | test.cb('createIndex > callback', (t) => { 51 | indexCol.createIndex('name.third', t.end) 52 | }) 53 | 54 | test('index > should accept a field string', (t) => { 55 | return indexCol.index('name.first').then(indexCol.indexes).then((indexes) => { 56 | t.not(indexes['name.first_1'], undefined) 57 | }) 58 | }) 59 | 60 | test('dropIndex > should accept a field string', (t) => { 61 | return indexCol.index('name2.first').then(indexCol.indexes).then((indexes) => { 62 | t.not(indexes['name2.first_1'], undefined) 63 | }).then(() => indexCol.dropIndex('name2.first')) 64 | .then(indexCol.indexes).then((indexes) => { 65 | t.is(indexes['name2.first_1'], undefined) 66 | }) 67 | }) 68 | 69 | test('dropIndex > should accept space-delimited compound indexes', (t) => { 70 | return indexCol.index('name2 last').then(indexCol.indexes).then((indexes) => { 71 | t.not(indexes.name2_1_last_1, undefined) 72 | }).then(() => indexCol.dropIndex('name2 last')) 73 | .then(indexCol.indexes).then((indexes) => { 74 | t.is(indexes.name2_1_last_1, undefined) 75 | }) 76 | }) 77 | 78 | test('dropIndex > should accept array compound indexes', (t) => { 79 | return indexCol.index(['nombre2', 'apellido']).then(indexCol.indexes).then((indexes) => { 80 | t.not(indexes.nombre2_1_apellido_1, undefined) 81 | }).then(() => indexCol.dropIndex(['nombre2', 'apellido'])) 82 | .then(indexCol.indexes).then((indexes) => { 83 | t.is(indexes.nombre2_1_apellido_1, undefined) 84 | }) 85 | }) 86 | 87 | test('dropIndex > should accept object compound indexes', (t) => { 88 | return indexCol.index({ up2: 1, down: -1 }).then(indexCol.indexes).then((indexes) => { 89 | t.not(indexes['up2_1_down_-1'], undefined) 90 | }).then(() => indexCol.dropIndex({ up2: 1, down: -1 })) 91 | .then(indexCol.indexes).then((indexes) => { 92 | t.is(indexes['up2_1_down_'], undefined) 93 | }) 94 | }) 95 | 96 | test.cb('dropIndex > callback', (t) => { 97 | indexCol.index('name3.first').then(indexCol.indexes).then((indexes) => { 98 | t.not(indexes['name3.first_1'], undefined) 99 | }).then(() => indexCol.dropIndex('name3.first', t.end)) 100 | }) 101 | 102 | test('dropIndexes > should drop all indexes', (t) => { 103 | const col = db.get('indexDrop-' + Date.now()) 104 | return col.index({ up2: 1, down: -1 }) 105 | .then(col.indexes) 106 | .then((indexes) => { 107 | t.not(indexes['up2_1_down_-1'], undefined) 108 | }).then(() => col.dropIndexes()) 109 | .then(col.indexes) 110 | .then((indexes) => { 111 | t.is(indexes['up2_1_down_'], undefined) 112 | }) 113 | }) 114 | 115 | test.cb('dropIndexes > callback', (t) => { 116 | const col = db.get('indexDropCallback-' + Date.now()) 117 | col.index({ up2: 1, down: -1 }).then(col.indexes).then((indexes) => { 118 | t.not(indexes['up2_1_down_-1'], undefined) 119 | }).then(() => col.dropIndexes(t.end)) 120 | }) 121 | 122 | test('insert > should force callback in next tick', (t) => { 123 | return users.insert({ woot: 'a' }).then(() => t.pass()) 124 | }) 125 | 126 | test('insert > should give you an object with the _id', (t) => { 127 | return users.insert({ woot: 'b' }).then((obj) => { 128 | t.is(typeof obj._id, 'object') 129 | t.not(obj._id.toHexString, undefined) 130 | }) 131 | }) 132 | 133 | test('insert > should return an array if an array was inserted', (t) => { 134 | return users.insert([{ woot: 'c' }, { woot: 'd' }]).then((docs) => { 135 | t.true(Array.isArray(docs)) 136 | t.is(docs.length, 2) 137 | }) 138 | }) 139 | 140 | test('insert > should not fail when inserting an empty array', (t) => { 141 | return users.insert([]).then((docs) => { 142 | t.true(Array.isArray(docs)) 143 | t.is(docs.length, 0) 144 | }) 145 | }) 146 | 147 | test.cb('insert > callback', (t) => { 148 | users.insert({ woot: 'a' }, t.end) 149 | }) 150 | 151 | test('findOne > should return null if no document', (t) => { 152 | return users.findOne({nonExistingField: true}) 153 | .then((doc) => { 154 | t.is(doc, null) 155 | }) 156 | }) 157 | 158 | test('findOne > findOne(undefined) should work', (t) => { 159 | return users.insert({ a: 'b', c: 'd', e: 'f' }).then((doc) => { 160 | return users.findOne() 161 | }).then(() => { 162 | t.pass() 163 | }) 164 | }) 165 | 166 | test('findOne > should only provide selected fields', (t) => { 167 | return users.insert({ a: 'b', c: 'd', e: 'f' }).then((doc) => { 168 | return users.findOne(doc._id, 'a e') 169 | }).then((doc) => { 170 | t.is(doc.a, 'b') 171 | t.is(doc.e, 'f') 172 | t.is(doc.c, undefined) 173 | }) 174 | }) 175 | 176 | test('find > should project only specified fields using projection options', t => { 177 | return users.insert([ 178 | { a: 1, b: 2 }, 179 | { a: 1, b: 1 } 180 | ]).then(() => { 181 | return users.find({ sort: true }, { projection: { a: 1 } }) 182 | }).then((docs) => { 183 | t.is(docs[0].a, 1) 184 | t.is(docs[0].b, undefined) 185 | t.is(docs[1].a, 1) 186 | t.is(docs[1].b, undefined) 187 | }) 188 | }) 189 | 190 | test.cb('findOne > callback', (t) => { 191 | users.insert({ woot: 'e' }).then((doc) => { 192 | return users.findOne(doc._id, t.end) 193 | }) 194 | }) 195 | 196 | test('find > should find with nested query', (t) => { 197 | return users.insert([{ nested: { a: 1 } }, { nested: { a: 2 } }]).then(() => { 198 | return users.find({ 'nested.a': 1 }) 199 | }).then((docs) => { 200 | t.is(docs.length, 1) 201 | t.is(docs[0].nested.a, 1) 202 | }) 203 | }) 204 | 205 | test('find > should find with nested array query', (t) => { 206 | return users.insert([{ nestedArray: [{ a: 1 }] }, { nestedArray: [{ a: 2 }] }]).then(() => { 207 | return users.find({ 'nestedArray.a': 1 }) 208 | }).then((docs) => { 209 | t.is(docs.length, 1) 210 | t.is(docs[0].nestedArray[0].a, 1) 211 | }) 212 | }) 213 | 214 | test('find > should sort', (t) => { 215 | return users.insert([{ sort: true, a: 1, b: 2 }, { sort: true, a: 1, b: 1 }]).then(() => { 216 | return users.find({ sort: true }, { sort: '-a b' }) 217 | }).then((docs) => { 218 | t.is(docs[0].b, 1) 219 | t.is(docs[1].b, 2) 220 | }) 221 | }) 222 | 223 | test('find > should return the raw cursor', (t) => { 224 | const query = { stream: 3 } 225 | return users.insert([{ stream: 3 }, { stream: 3 }, { stream: 3 }, { stream: 3 }]).then(() => { 226 | return users.find(query, {rawCursor: true}) 227 | .then((cursor) => { 228 | t.truthy(cursor.close) 229 | t.truthy(cursor.pause) 230 | t.truthy(cursor.resume) 231 | cursor.close() 232 | }) 233 | }) 234 | }) 235 | 236 | test('find > should work with streaming', (t) => { 237 | const query = { stream: 1 } 238 | let found = 0 239 | return users.insert([{ stream: 1 }, { stream: 1 }, { stream: 1 }, { stream: 1 }]).then(() => { 240 | return users.find(query) 241 | .each((doc) => { 242 | t.is(doc.stream, 1) 243 | found++ 244 | }) 245 | .then(() => { 246 | t.is(found, 4) 247 | }) 248 | }) 249 | }) 250 | 251 | test('find > should work with streaming option', (t) => { 252 | const query = { stream: 2 } 253 | let found = 0 254 | return users.insert([{ stream: 2 }, { stream: 2 }, { stream: 2 }, { stream: 2 }]).then(() => { 255 | return users.find(query, { stream: true }) 256 | .each((doc) => { 257 | t.is(doc.stream, 2) 258 | found++ 259 | }) 260 | .then(() => { 261 | t.is(found, 4) 262 | }) 263 | }) 264 | }) 265 | 266 | test('find > should work with streaming option without each', (t) => { 267 | const query = { stream: 5 } 268 | let found = 0 269 | return users.insert([{ stream: 5 }, { stream: 5 }, { stream: 5 }, { stream: 5 }]).then(() => { 270 | return users.find(query, { 271 | stream (doc) { 272 | t.is(doc.stream, 5) 273 | found++ 274 | } 275 | }) 276 | .then(() => { 277 | t.is(found, 4) 278 | }) 279 | }) 280 | }) 281 | 282 | test('find > should allow stream cursor destroy', (t) => { 283 | const query = { cursor: { $exists: true } } 284 | let found = 0 285 | return users.insert([{ cursor: true }, { cursor: true }, { cursor: true }, { cursor: true }]).then(() => { 286 | return users.find(query) 287 | .each((doc, {close}) => { 288 | t.not(doc.cursor, null) 289 | found++ 290 | if (found === 2) close() 291 | }) 292 | .then(() => { 293 | return new Promise((resolve) => { 294 | setTimeout(() => { 295 | t.is(found, 2) 296 | resolve() 297 | }, 100) 298 | }) 299 | }) 300 | }) 301 | }) 302 | 303 | test('find > should allow stream cursor destroy even when paused', (t) => { 304 | const query = { cursor: { $exists: true } } 305 | let found = 0 306 | return users.insert([{ cursor: true }, { cursor: true }, { cursor: true }, { cursor: true }]).then(() => { 307 | return users.find(query) 308 | .each((doc, {close, pause, resume}) => { 309 | pause() 310 | t.not(doc.cursor, null) 311 | found++ 312 | if (found === 2) return close() 313 | resume() 314 | }) 315 | .then(() => { 316 | return new Promise((resolve) => { 317 | setTimeout(() => { 318 | t.is(found, 2) 319 | resolve() 320 | }, 100) 321 | }) 322 | }) 323 | }) 324 | }) 325 | 326 | test('find > stream pause and continue', (t) => { 327 | const query = { stream: 4 } 328 | return users.insert([{ stream: 4 }, { stream: 4 }, { stream: 4 }, { stream: 4 }]).then(() => { 329 | const start = Date.now() 330 | let index = 0 331 | return users.find(query) 332 | .each((doc, {pause, resume}) => { 333 | pause() 334 | const duration = Date.now() - start 335 | t.true(duration > index * 1000) 336 | setTimeout(() => { 337 | index += 1 338 | resume() 339 | }, 1000) 340 | }) 341 | .then(() => { 342 | t.is(index, 4) 343 | const duration = Date.now() - start 344 | t.true(duration > 4000) 345 | }) 346 | }) 347 | }) 348 | 349 | test.cb('find > stream callback', (t) => { 350 | const query = { stream: 3 } 351 | users.insert([{ stream: 3 }, { stream: 3 }, { stream: 3 }, { stream: 3 }]).then(() => { 352 | return users.find(query, t.end) 353 | .each((doc) => { 354 | t.not(doc.a, null) 355 | }) 356 | }) 357 | }) 358 | 359 | test.cb('find > callback', (t) => { 360 | users.insert({ woot: 'e' }).then((doc) => { 361 | return users.find(doc._id, t.end) 362 | }) 363 | }) 364 | 365 | test('group > should work', (t) => { 366 | return users.insert([{ group: true }, { group: true }]).then(() => { 367 | return users.group( 368 | { group: true }, 369 | {}, 370 | { count: 0 }, 371 | function (obj, prev) { 372 | return prev.count++ 373 | } 374 | ) 375 | }).then(([group1, group2]) => { 376 | t.is(group1.group, null) 377 | t.true(group2.group) 378 | t.is(group2.count, 2) 379 | }) 380 | }) 381 | 382 | test.cb('group > callback', (t) => { 383 | users.group( 384 | { group: true }, 385 | {}, 386 | { count: 0 }, 387 | function (obj, prev) { 388 | prev.count++ 389 | }, 390 | function (x) { return x }, 391 | true, 392 | t.end 393 | ) 394 | }) 395 | 396 | test('count > should count', (t) => { 397 | return users.count({ a: 'counting' }).then((count) => { 398 | t.is(count, 0) 399 | return users.insert({ a: 'counting' }) 400 | }).then(() => { 401 | return users.count({ a: 'counting' }) 402 | }).then((count) => { 403 | t.is(count, 1) 404 | }) 405 | }) 406 | 407 | test('count > should not ignore options', (t) => { 408 | return users.count({ b: 'counting' }).then((count) => { 409 | t.is(count, 0) 410 | return users.insert([{ b: 'counting' }, { b: 'counting' }, { b: 'counting' }, { b: 'counting' }]) 411 | }).then(() => { 412 | return users.count({ b: 'counting' }, {limit: 2}) 413 | }).then((count) => { 414 | t.is(count, 2) 415 | }) 416 | }) 417 | 418 | test('count > should count with no arguments', (t) => { 419 | return users.count({ c: 'counting' }).then((count) => { 420 | t.is(count, 0) 421 | return users.insert({ c: 'counting' }) 422 | }).then(() => { 423 | return users.count() 424 | }).then((count) => { 425 | t.is(count, 77) 426 | }) 427 | }) 428 | 429 | test('count > should estimate count', (t) => { 430 | return users.count({}, { estimate: true }).then((count) => { 431 | t.is(count, 51) 432 | }) 433 | }) 434 | 435 | test('count > should estimate count with options', (t) => { 436 | return users.count({}, { estimate: true, maxTimeMS: 10000 }).then((count) => { 437 | t.is(count, 51) 438 | }) 439 | }) 440 | 441 | test.cb('count > callback', (t) => { 442 | users.count({ a: 'counting' }, t.end) 443 | }) 444 | 445 | test('distinct', (t) => { 446 | return users.insert([{ distinct: 'a' }, { distinct: 'a' }, { distinct: 'b' }]).then(() => { 447 | return users.distinct('distinct') 448 | }).then((docs) => { 449 | t.deepEqual(docs, ['a', 'b']) 450 | }) 451 | }) 452 | 453 | test('distinct with options', (t) => { 454 | return users.insert([{ distinct2: 'a' }, { distinct2: 'a' }, { distinct2: 'b' }]).then(() => { 455 | return users.distinct('distinct2', {}) 456 | }).then((docs) => { 457 | t.deepEqual(docs, ['a', 'b']) 458 | }) 459 | }) 460 | 461 | test.cb('distinct > with options callback', (t) => { 462 | users.distinct('distinct', {}, t.end) 463 | }) 464 | 465 | test.cb('distinct > callback', (t) => { 466 | users.distinct('distinct', t.end) 467 | }) 468 | 469 | test('update > should update', (t) => { 470 | return users.insert({ d: 'e' }).then((doc) => { 471 | return users.update({ _id: doc._id }, { $set: { d: 'f' } }).then(() => { 472 | return users.findOne(doc._id) 473 | }) 474 | }).then((doc) => { 475 | t.is(doc.d, 'f') 476 | }) 477 | }) 478 | 479 | test('update > should update with 0', (t) => { 480 | return users.insert({ d: 'e' }).then((doc) => { 481 | return users.update({ _id: doc._id }, { $set: { d: 0 } }).then(() => { 482 | return users.findOne(doc._id) 483 | }) 484 | }).then((doc) => { 485 | t.is(doc.d, 0) 486 | }) 487 | }) 488 | 489 | test.cb('update > callback', (t) => { 490 | users.update({ d: 'e' }, { $set: { d: 'f' } }, t.end) 491 | }) 492 | 493 | test('update > should update with an objectid', (t) => { 494 | return users.insert({ d: 'e' }).then((doc) => { 495 | return users.update(doc._id, { $set: { d: 'f' } }).then(() => { 496 | return users.findOne(doc._id) 497 | }) 498 | }).then((doc) => { 499 | t.is(doc.d, 'f') 500 | }) 501 | }) 502 | 503 | test('update > should update with an objectid (string)', (t) => { 504 | return users.insert({ d: 'e' }).then((doc) => { 505 | return users.update(doc._id.toString(), { $set: { d: 'f' } }).then(() => { 506 | return users.findOne(doc._id) 507 | }) 508 | }).then((doc) => { 509 | t.is(doc.d, 'f') 510 | }) 511 | }) 512 | 513 | test('remove > should remove a document', (t) => { 514 | return users.insert({ name: 'Tobi' }).then((doc) => { 515 | return users.remove({ name: 'Tobi' }) 516 | }).then(() => { 517 | return users.find({ name: 'Tobi' }) 518 | }).then((doc) => { 519 | t.deepEqual(doc, []) 520 | }) 521 | }) 522 | 523 | test.cb('remove > callback', (t) => { 524 | users.remove({ name: 'Mathieu' }, t.end) 525 | }) 526 | 527 | test('findOneAndDelete > should remove a document and return it', (t) => { 528 | return users.insert({ name: 'Bob' }).then((doc) => { 529 | return users.findOneAndDelete({ name: 'Bob' }) 530 | }).then((doc) => { 531 | t.is(doc.name, 'Bob') 532 | return users.find({ name: 'Bob' }) 533 | }).then((doc) => { 534 | t.deepEqual(doc, []) 535 | }) 536 | }) 537 | 538 | test.cb('findOneAndDelete > callback', (t) => { 539 | users.insert({ name: 'Bob2' }).then((doc) => { 540 | users.findOneAndDelete({ name: 'Bob2' }, (err, doc) => { 541 | t.is(err, null) 542 | t.is(doc.name, 'Bob2') 543 | users.find({ name: 'Bob2' }).then((doc) => { 544 | t.deepEqual(doc, []) 545 | t.end() 546 | }) 547 | }) 548 | }) 549 | }) 550 | 551 | test('findOneAndDelete > should return null if found nothing', (t) => { 552 | return users.findOneAndDelete({ name: 'Bob3' }) 553 | .then((doc) => { 554 | t.is(doc, null) 555 | }) 556 | }) 557 | 558 | test('findOneAndUpdate > should update a document and return it', (t) => { 559 | return users.insert({ name: 'Jack' }).then((doc) => { 560 | return users.findOneAndUpdate({ name: 'Jack' }, { $set: { name: 'Jack4' } }) 561 | }).then((doc) => { 562 | t.is(doc.name, 'Jack4') 563 | }) 564 | }) 565 | 566 | test('findOneAndUpdate > should return null if found nothing', (t) => { 567 | return users.findOneAndUpdate({ name: 'Jack5' }, { $set: { name: 'Jack6' } }) 568 | .then((doc) => { 569 | t.is(doc, null) 570 | }) 571 | }) 572 | 573 | test('findOneAndUpdate > should return an error if no atomic operations are specified', async t => { 574 | const err = await t.throws(users.findOneAndUpdate({ name: 'Jack5' }, { name: 'Jack6' })) 575 | t.is(err.message, 'the update operation document must contain atomic operators.') 576 | }) 577 | 578 | test.cb('findOneAndUpdate > callback', (t) => { 579 | users.insert({ name: 'Jack2' }).then(() => { 580 | users.findOneAndUpdate({ name: 'Jack2' }, { $set: { name: 'Jack3' } }, (err, doc) => { 581 | t.is(err, null) 582 | t.is(doc.name, 'Jack3') 583 | t.end() 584 | }) 585 | }) 586 | }) 587 | 588 | test('aggregate > should fail properly', (t) => { 589 | return users.aggregate().catch(() => { 590 | t.pass() 591 | }) 592 | }) 593 | 594 | test.cb('aggregate > should fail properly with callback', (t) => { 595 | users.aggregate(undefined, function (err) { 596 | t.truthy(err) 597 | t.end() 598 | }) 599 | }) 600 | 601 | test('aggregate > should work in normal case', (t) => { 602 | return users.aggregate([{$group: {_id: null, maxWoot: { $max: '$woot' }}}]).then((res) => { 603 | t.true(Array.isArray(res)) 604 | t.is(res.length, 1) 605 | }) 606 | }) 607 | 608 | test('aggregate > should work with option', (t) => { 609 | return users.aggregate([{$group: {_id: null, maxWoot: { $max: '$woot' }}}], { explain: true }).then((res) => { 610 | t.true(Array.isArray(res)) 611 | t.is(res.length, 1) 612 | }) 613 | }) 614 | 615 | test.cb('aggregate > callback', (t) => { 616 | users.aggregate([{$group: {_id: null, maxWoot: { $max: '$woot' }}}], t.end) 617 | }) 618 | 619 | test('bulkWrite', (t) => { 620 | return users.bulkWrite([ 621 | { insertOne: { document: { bulkWrite: 1 } } } 622 | ]).then((r) => { 623 | t.is(r.nInserted, 1) 624 | }) 625 | }) 626 | 627 | test.cb('bulkWrite > callback', (t) => { 628 | users.bulkWrite([ 629 | { insertOne: { document: { bulkWrite: 2 } } } 630 | ], t.end) 631 | }) 632 | 633 | test('should allow defaults', (t) => { 634 | return users.insert([{ f: true }, { f: true }, { g: true }, { g: true }]).then(() => { 635 | return users.update({}, { $set: { f: 'g' } }) 636 | }).then(() => { 637 | users.options.safe = false 638 | users.options.multi = false 639 | return users.update({}, { $set: { g: 'h' } }) 640 | }).then(({n}) => { 641 | t.true(n && n <= 1) 642 | }).then(() => { 643 | users.options.safe = true 644 | users.options.multi = true 645 | return users.update({}, { $set: { g: 'i' } }, { safe: false, multi: false }) 646 | }).then(({n}) => { 647 | t.true(n && n <= 1) 648 | }) 649 | }) 650 | 651 | test('drop > should not throw when dropping an empty db', (t) => { 652 | return db.get('dropDB-' + Date.now()).drop().then(() => t.pass()).catch(() => t.fail()) 653 | }) 654 | 655 | test.cb('drop > callback', (t) => { 656 | db.get('dropDB2-' + Date.now()).drop(t.end) 657 | }) 658 | 659 | test('caching collections', (t) => { 660 | const collectionName = 'cached-' + Date.now() 661 | t.is(db.get(collectionName), db.get(collectionName)) 662 | }) 663 | 664 | test('not caching collections', (t) => { 665 | const collectionName = 'cached-' + Date.now() 666 | t.not(db.get(collectionName, {cache: false}), db.get(collectionName, {cache: false})) 667 | }) 668 | 669 | test('geoHaystackSearch', (t) => { 670 | return users.ensureIndex({loc: 'geoHaystack', type: 1}, {bucketSize: 1}) 671 | .then(() => users.insert([{a: 1, loc: [50, 30]}, {a: 1, loc: [30, 50]}])) 672 | .then(() => users.geoHaystackSearch(50, 50, {search: {a: 1}, limit: 1, maxDistance: 100})) 673 | .then((r) => { 674 | t.is(r.length, 1) 675 | }) 676 | }) 677 | 678 | test.cb('geoHaystackSearch > callback', (t) => { 679 | users.ensureIndex({loc: 'geoHaystack', type: 1}, {bucketSize: 1}) 680 | .then(() => users.insert([{a: 1, loc: [50, 30]}, {a: 1, loc: [30, 50]}])) 681 | .then(() => users.geoHaystackSearch(50, 50, {search: {a: 1}, maxDistance: 100}, t.end)) 682 | }) 683 | 684 | test('geoNear', async t => { 685 | const cmd = users.ensureIndex({loc2: '2d'}) 686 | .then(() => users.insert([{a: 1, loc2: [50, 30]}, {a: 1, loc2: [30, 50]}])) 687 | .then(() => users.geoNear(50, 50, {query: {a: 1}, num: 1})) 688 | .then((r) => { 689 | t.is(r.length, 1) 690 | }) 691 | 692 | const err = await t.throws(cmd) 693 | 694 | t.is(err.message, 'geoNear command is not supported anymore (see https://docs.mongodb.com/manual/reference/command/geoNear)') 695 | }) 696 | 697 | test('mapReduce', (t) => { 698 | // Map function 699 | const map = function () { emit(this.user_id, 1) } // eslint-disable-line 700 | // Reduce function 701 | const reduce = function (k, vals) { return 1 } 702 | return users.insert([{user_id: 1}, {user_id: 2}]) 703 | .then(() => users.mapReduce(map, reduce, {out: {replace: 'tempCollection'}})) 704 | .then((collection) => collection.findOne({'_id': 1})) 705 | .then((r) => { 706 | t.is(r.value, 1) 707 | }) 708 | }) 709 | 710 | test.cb('mapReduce > callback', (t) => { 711 | // Map function 712 | const map = function () { emit(this.user_id, 1) } // eslint-disable-line 713 | // Reduce function 714 | const reduce = function (k, vals) { return 1 } 715 | users.mapReduce(map, reduce, {out: {replace: 'tempCollection'}}, t.end) 716 | }) 717 | 718 | test('stats', (t) => { 719 | return users.stats().then((res) => { 720 | t.truthy(res) 721 | }) 722 | }) 723 | 724 | test.cb('stats > callback', (t) => { 725 | users.stats(t.end) 726 | }) 727 | -------------------------------------------------------------------------------- /test/monk.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const monk = require('../lib/monk') 4 | 5 | test('Manager', (t) => { 6 | t.is(typeof monk, 'function') 7 | t.is(monk.name, 'Manager') 8 | }) 9 | 10 | test('Collection', (t) => { 11 | t.is(typeof monk.Collection, 'function') 12 | t.is(monk.Collection.name, 'Collection') 13 | }) 14 | 15 | test('Should throw if no uri provided', (t) => { 16 | try { 17 | monk() 18 | } catch (e) { 19 | t.is(e.message, 'No connection URI provided.') 20 | } 21 | }) 22 | 23 | test('Should handle srv connection string', (t) => { 24 | const m = monk('mongodb+srv://user:pw@host') 25 | t.true(m._connectionURI === 'mongodb+srv://user:pw@host') 26 | }) 27 | 28 | test.cb('to a regular server', (t) => { 29 | t.plan(2) 30 | monk('127.0.0.1/monk-test', (err, db) => { 31 | t.falsy(err) 32 | t.true(db instanceof monk) 33 | t.end() 34 | }) 35 | }) 36 | 37 | test('connect with promise', (t) => { 38 | return monk('127.0.0.1/monk-test').then((db) => { 39 | t.true(db instanceof monk) 40 | }) 41 | }) 42 | 43 | test.cb('should fail', (t) => { 44 | t.plan(2) 45 | monk('non-existent-db/monk-test', (err, db) => { 46 | t.truthy(err) 47 | t.true(db instanceof monk) 48 | t.end() 49 | }) 50 | }) 51 | 52 | test('should fail with promise', (t) => { 53 | return monk('non-existent-db/monk-test').catch((err) => { 54 | t.truthy(err) 55 | }) 56 | }) 57 | 58 | test.cb('to a replica set (array)', (t) => { 59 | t.plan(1) 60 | monk(['127.0.0.1/monk-test', 'localhost/monk-test'], () => { 61 | t.pass() 62 | t.end() 63 | }) 64 | }) 65 | 66 | test.cb('to a replica set (string)', (t) => { 67 | t.plan(1) 68 | monk('127.0.0.1,localhost/monk-test', () => { 69 | t.pass() 70 | t.end() 71 | }) 72 | }) 73 | 74 | test.cb('followed by disconnection', (t) => { 75 | t.plan(1) 76 | const db = monk('127.0.0.1/monk-test', () => { 77 | db.close(() => { 78 | t.pass() 79 | t.end() 80 | }) 81 | }) 82 | }) 83 | 84 | test('executeWhenOpened > should reopen the connection if closed', (t) => { 85 | const db = monk('127.0.0.1/monk') 86 | return db 87 | .then(() => t.is(db._state, 'open')) 88 | .then(() => db.close(true)) 89 | .then(() => t.is(db._state, 'closed')) 90 | .then(() => db.executeWhenOpened()) 91 | .then(() => t.is(db._state, 'open')) 92 | .then(() => db.close()) 93 | }) 94 | 95 | test('close > closing a closed connection should work', (t) => { 96 | const db = monk('127.0.0.1/monk') 97 | return db 98 | .then(() => t.is(db._state, 'open')) 99 | .then(() => db.close()) 100 | .then(() => t.is(db._state, 'closed')) 101 | .then(() => db.close()) 102 | }) 103 | 104 | test.cb('close > closing should emit an event', (t) => { 105 | const db = monk('127.0.0.1/monk') 106 | db.on('close', () => t.end()) 107 | db.close() 108 | }) 109 | 110 | test.cb('close > closing a closed connection should work with callback', (t) => { 111 | const db = monk('127.0.0.1/monk') 112 | db.then(() => t.is(db._state, 'open')) 113 | .then(() => db.close(() => { 114 | t.is(db._state, 'closed') 115 | db.close(() => t.end()) 116 | })) 117 | }) 118 | 119 | test('close > closing an opening connection should close it once opened', (t) => { 120 | const db = monk('127.0.0.1/monk') 121 | return db.close().then(() => t.pass()) 122 | }) 123 | 124 | const Collection = monk.Collection 125 | const db = monk('127.0.0.1/monk-test') 126 | 127 | test('Manager#create', (t) => { 128 | t.true(db.create('users') instanceof Collection) 129 | }) 130 | 131 | test('Manager#get', (t) => { 132 | t.true(db.get('users') instanceof Collection) 133 | }) 134 | 135 | test('Manager#listCollections', (t) => { 136 | return db.listCollections().then(collections => t.true(collections instanceof Array)) 137 | }) 138 | 139 | test('Manager#col', (t) => { 140 | t.true(db.col('users') instanceof Collection) 141 | }) 142 | 143 | test('Manager#id', (t) => { 144 | const oid = db.id() 145 | t.is(typeof oid.toHexString(), 'string') 146 | }) 147 | 148 | test('Manager#oid', (t) => { 149 | const oid = db.oid() 150 | t.is(typeof oid.toHexString(), 'string') 151 | }) 152 | 153 | test('oid from hex string', (t) => { 154 | const oid = db.oid('4ee0fd75d6bd52107c000118') 155 | t.is(oid.toString(), '4ee0fd75d6bd52107c000118') 156 | }) 157 | 158 | test('oid from oid', (t) => { 159 | const oid = db.oid() 160 | t.is(db.oid(oid), oid) 161 | }) 162 | 163 | test('option useNewUrlParser should be true if not specified', (t) => { 164 | return monk('127.0.0.1/monk-test').then((db) => { 165 | t.is(db._connectionOptions.useNewUrlParser, true) 166 | }) 167 | }) 168 | 169 | test('option useNewUrlParser should be true if specified', (t) => { 170 | return monk('127.0.0.1/monk-test', { useNewUrlParser: true }).then((db) => { 171 | t.is(db._connectionOptions.useNewUrlParser, true) 172 | }) 173 | }) 174 | 175 | test('option useNewUrlParser should have the specified value', (t) => { 176 | return monk('127.0.0.1/monk-test', { useNewUrlParser: false }).then((db) => { 177 | t.is(db._connectionOptions.useNewUrlParser, false) 178 | }) 179 | }) 180 | 181 | test('option useUnifiedTopology should be true if not specified', (t) => { 182 | return monk('127.0.0.1/monk-test').then(db => { 183 | t.is(db._connectionOptions.useUnifiedTopology, true) 184 | }) 185 | }) 186 | 187 | test('option useUnifiedTopology should be true if specified', (t) => { 188 | return monk('127.0.0.1/monk-test', { useUnifiedTopology: true }).then(db => { 189 | t.is(db._connectionOptions.useUnifiedTopology, true) 190 | }) 191 | }) 192 | 193 | test('option useUnifiedTopology should have the specified value', (t) => { 194 | return monk('127.0.0.1/monk-test', { useUnifiedTopology: false }).then(db => { 195 | t.is(db._connectionOptions.useUnifiedTopology, false) 196 | }) 197 | }) 198 | --------------------------------------------------------------------------------