├── .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 | [](https://secure.travis-ci.org/Automattic/monk)
14 | [](https://codecov.io/gh/Automattic/monk)
15 | [](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 |
--------------------------------------------------------------------------------