First Commit
This commit is contained in:
commit
601e886c14
2123 files changed
+334815
No files matched your search
+21
@@ -0,0 +1,21 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
node_js: [12, 11, 10, 9, 8, 7, 6, 5, 4]
|
||||
install:
|
||||
- travis_retry npm install
|
||||
before_script:
|
||||
- wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.6.6.tgz
|
||||
- tar -zxvf mongodb-linux-x86_64-3.6.6.tgz
|
||||
- mkdir -p ./data/db/27017
|
||||
- mkdir -p ./data/db/27000
|
||||
- printf "\n--timeout 8000" >> ./test/mocha.opts
|
||||
- ./mongodb-linux-x86_64-3.6.6/bin/mongod --fork --dbpath ./data/db/27017 --syslog --port 27017
|
||||
- sleep 2
|
||||
matrix:
|
||||
include:
|
||||
- name: "👕Linter"
|
||||
node_js: 10
|
||||
before_script: skip
|
||||
script: npm run lint
|
||||
notifications:
|
||||
email: false
|
||||
+5385
File diff suppressed because it is too large.
Load diff
+21
@@ -0,0 +1,21 @@
|
||||
# MIT License
|
||||
|
||||
Copyright (c) 2010 LearnBoost <dev@learnboost.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
# Mongoose
|
||||
|
||||
Mongoose is a [MongoDB](https://www.mongodb.org/) object modeling tool designed to work in an asynchronous environment.
|
||||
|
||||
[](http://slack.mongoosejs.io)
|
||||
[](https://travis-ci.org/Automattic/mongoose)
|
||||
[](http://badge.fury.io/js/mongoose)
|
||||
|
||||
[](https://www.npmjs.com/package/mongoose)
|
||||
|
||||
## Documentation
|
||||
|
||||
[mongoosejs.com](http://mongoosejs.com/)
|
||||
|
||||
[Mongoose 5.0.0](https://github.com/Automattic/mongoose/blob/master/History.md#500--2018-01-17) was released on January 17, 2018. You can find more details on [backwards breaking changes in 5.0.0 on our docs site](https://mongoosejs.com/docs/migrating_to_5.html).
|
||||
|
||||
## Support
|
||||
|
||||
- [Stack Overflow](http://stackoverflow.com/questions/tagged/mongoose)
|
||||
- [Bug Reports](https://github.com/Automattic/mongoose/issues/)
|
||||
- [Mongoose Slack Channel](http://slack.mongoosejs.io/)
|
||||
- [Help Forum](http://groups.google.com/group/mongoose-orm)
|
||||
- [MongoDB Support](https://docs.mongodb.org/manual/support/)
|
||||
|
||||
## Importing
|
||||
|
||||
```javascript
|
||||
// Using Node.js `require()`
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
// Using ES6 imports
|
||||
import mongoose from 'mongoose';
|
||||
```
|
||||
|
||||
## Plugins
|
||||
|
||||
Check out the [plugins search site](http://plugins.mongoosejs.io/) to see hundreds of related modules from the community. Next, learn how to write your own plugin from the [docs](http://mongoosejs.com/docs/plugins.html) or [this blog post](http://thecodebarbarian.com/2015/03/06/guide-to-mongoose-plugins).
|
||||
|
||||
## Contributors
|
||||
|
||||
Pull requests are always welcome! Please base pull requests against the `master`
|
||||
branch and follow the [contributing guide](https://github.com/Automattic/mongoose/blob/master/CONTRIBUTING.md).
|
||||
|
||||
If your pull requests makes documentation changes, please do **not**
|
||||
modify any `.html` files. The `.html` files are compiled code, so please make
|
||||
your changes in `docs/*.pug`, `lib/*.js`, or `test/docs/*.js`.
|
||||
|
||||
View all 400+ [contributors](https://github.com/Automattic/mongoose/graphs/contributors).
|
||||
|
||||
## Installation
|
||||
|
||||
First install [node.js](http://nodejs.org/) and [mongodb](https://www.mongodb.org/downloads). Then:
|
||||
|
||||
```sh
|
||||
$ npm install mongoose
|
||||
```
|
||||
|
||||
## Overview
|
||||
|
||||
### Connecting to MongoDB
|
||||
|
||||
First, we need to define a connection. If your app uses only one database, you should use `mongoose.connect`. If you need to create additional connections, use `mongoose.createConnection`.
|
||||
|
||||
Both `connect` and `createConnection` take a `mongodb://` URI, or the parameters `host, database, port, options`.
|
||||
|
||||
```js
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
mongoose.connect('mongodb://localhost/my_database', {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true
|
||||
});
|
||||
```
|
||||
|
||||
Once connected, the `open` event is fired on the `Connection` instance. If you're using `mongoose.connect`, the `Connection` is `mongoose.connection`. Otherwise, `mongoose.createConnection` return value is a `Connection`.
|
||||
|
||||
**Note:** _If the local connection fails then try using 127.0.0.1 instead of localhost. Sometimes issues may arise when the local hostname has been changed._
|
||||
|
||||
**Important!** Mongoose buffers all the commands until it's connected to the database. This means that you don't have to wait until it connects to MongoDB in order to define models, run queries, etc.
|
||||
|
||||
### Defining a Model
|
||||
|
||||
Models are defined through the `Schema` interface.
|
||||
|
||||
```js
|
||||
const Schema = mongoose.Schema;
|
||||
const ObjectId = Schema.ObjectId;
|
||||
|
||||
const BlogPost = new Schema({
|
||||
author: ObjectId,
|
||||
title: String,
|
||||
body: String,
|
||||
date: Date
|
||||
});
|
||||
```
|
||||
|
||||
Aside from defining the structure of your documents and the types of data you're storing, a Schema handles the definition of:
|
||||
|
||||
* [Validators](http://mongoosejs.com/docs/validation.html) (async and sync)
|
||||
* [Defaults](http://mongoosejs.com/docs/api.html#schematype_SchemaType-default)
|
||||
* [Getters](http://mongoosejs.com/docs/api.html#schematype_SchemaType-get)
|
||||
* [Setters](http://mongoosejs.com/docs/api.html#schematype_SchemaType-set)
|
||||
* [Indexes](http://mongoosejs.com/docs/guide.html#indexes)
|
||||
* [Middleware](http://mongoosejs.com/docs/middleware.html)
|
||||
* [Methods](http://mongoosejs.com/docs/guide.html#methods) definition
|
||||
* [Statics](http://mongoosejs.com/docs/guide.html#statics) definition
|
||||
* [Plugins](http://mongoosejs.com/docs/plugins.html)
|
||||
* [pseudo-JOINs](http://mongoosejs.com/docs/populate.html)
|
||||
|
||||
The following example shows some of these features:
|
||||
|
||||
```js
|
||||
const Comment = new Schema({
|
||||
name: { type: String, default: 'hahaha' },
|
||||
age: { type: Number, min: 18, index: true },
|
||||
bio: { type: String, match: /[a-z]/ },
|
||||
date: { type: Date, default: Date.now },
|
||||
buff: Buffer
|
||||
});
|
||||
|
||||
// a setter
|
||||
Comment.path('name').set(function (v) {
|
||||
return capitalize(v);
|
||||
});
|
||||
|
||||
// middleware
|
||||
Comment.pre('save', function (next) {
|
||||
notify(this.get('email'));
|
||||
next();
|
||||
});
|
||||
```
|
||||
|
||||
Take a look at the example in [`examples/schema/schema.js`](https://github.com/Automattic/mongoose/blob/master/examples/schema/schema.js) for an end-to-end example of a typical setup.
|
||||
|
||||
### Accessing a Model
|
||||
|
||||
Once we define a model through `mongoose.model('ModelName', mySchema)`, we can access it through the same function
|
||||
|
||||
```js
|
||||
const MyModel = mongoose.model('ModelName');
|
||||
```
|
||||
|
||||
Or just do it all at once
|
||||
|
||||
```js
|
||||
const MyModel = mongoose.model('ModelName', mySchema);
|
||||
```
|
||||
|
||||
The first argument is the _singular_ name of the collection your model is for. **Mongoose automatically looks for the _plural_ version of your model name.** For example, if you use
|
||||
|
||||
```js
|
||||
const MyModel = mongoose.model('Ticket', mySchema);
|
||||
```
|
||||
|
||||
Then Mongoose will create the model for your __tickets__ collection, not your __ticket__ collection.
|
||||
|
||||
Once we have our model, we can then instantiate it, and save it:
|
||||
|
||||
```js
|
||||
const instance = new MyModel();
|
||||
instance.my.key = 'hello';
|
||||
instance.save(function (err) {
|
||||
//
|
||||
});
|
||||
```
|
||||
|
||||
Or we can find documents from the same collection
|
||||
|
||||
```js
|
||||
MyModel.find({}, function (err, docs) {
|
||||
// docs.forEach
|
||||
});
|
||||
```
|
||||
|
||||
You can also `findOne`, `findById`, `update`, etc. For more details check out [the docs](http://mongoosejs.com/docs/queries.html).
|
||||
|
||||
**Important!** If you opened a separate connection using `mongoose.createConnection()` but attempt to access the model through `mongoose.model('ModelName')` it will not work as expected since it is not hooked up to an active db connection. In this case access your model through the connection you created:
|
||||
|
||||
```js
|
||||
const conn = mongoose.createConnection('your connection string');
|
||||
const MyModel = conn.model('ModelName', schema);
|
||||
const m = new MyModel;
|
||||
m.save(); // works
|
||||
```
|
||||
|
||||
vs
|
||||
|
||||
```js
|
||||
const conn = mongoose.createConnection('your connection string');
|
||||
const MyModel = mongoose.model('ModelName', schema);
|
||||
const m = new MyModel;
|
||||
m.save(); // does not work b/c the default connection object was never connected
|
||||
```
|
||||
|
||||
### Embedded Documents
|
||||
|
||||
In the first example snippet, we defined a key in the Schema that looks like:
|
||||
|
||||
```
|
||||
comments: [Comment]
|
||||
```
|
||||
|
||||
Where `Comment` is a `Schema` we created. This means that creating embedded documents is as simple as:
|
||||
|
||||
```js
|
||||
// retrieve my model
|
||||
const BlogPost = mongoose.model('BlogPost');
|
||||
|
||||
// create a blog post
|
||||
const post = new BlogPost();
|
||||
|
||||
// create a comment
|
||||
post.comments.push({ title: 'My comment' });
|
||||
|
||||
post.save(function (err) {
|
||||
if (!err) console.log('Success!');
|
||||
});
|
||||
```
|
||||
|
||||
The same goes for removing them:
|
||||
|
||||
```js
|
||||
BlogPost.findById(myId, function (err, post) {
|
||||
if (!err) {
|
||||
post.comments[0].remove();
|
||||
post.save(function (err) {
|
||||
// do something
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Embedded documents enjoy all the same features as your models. Defaults, validators, middleware. Whenever an error occurs, it's bubbled to the `save()` error callback, so error handling is a snap!
|
||||
|
||||
|
||||
### Middleware
|
||||
|
||||
See the [docs](http://mongoosejs.com/docs/middleware.html) page.
|
||||
|
||||
#### Intercepting and mutating method arguments
|
||||
|
||||
You can intercept method arguments via middleware.
|
||||
|
||||
For example, this would allow you to broadcast changes about your Documents every time someone `set`s a path in your Document to a new value:
|
||||
|
||||
```js
|
||||
schema.pre('set', function (next, path, val, typel) {
|
||||
// `this` is the current Document
|
||||
this.emit('set', path, val);
|
||||
|
||||
// Pass control to the next pre
|
||||
next();
|
||||
});
|
||||
```
|
||||
|
||||
Moreover, you can mutate the incoming `method` arguments so that subsequent middleware see different values for those arguments. To do so, just pass the new values to `next`:
|
||||
|
||||
```js
|
||||
.pre(method, function firstPre (next, methodArg1, methodArg2) {
|
||||
// Mutate methodArg1
|
||||
next("altered-" + methodArg1.toString(), methodArg2);
|
||||
});
|
||||
|
||||
// pre declaration is chainable
|
||||
.pre(method, function secondPre (next, methodArg1, methodArg2) {
|
||||
console.log(methodArg1);
|
||||
// => 'altered-originalValOfMethodArg1'
|
||||
|
||||
console.log(methodArg2);
|
||||
// => 'originalValOfMethodArg2'
|
||||
|
||||
// Passing no arguments to `next` automatically passes along the current argument values
|
||||
// i.e., the following `next()` is equivalent to `next(methodArg1, methodArg2)`
|
||||
// and also equivalent to, with the example method arg
|
||||
// values, `next('altered-originalValOfMethodArg1', 'originalValOfMethodArg2')`
|
||||
next();
|
||||
});
|
||||
```
|
||||
|
||||
#### Schema gotcha
|
||||
|
||||
`type`, when used in a schema has special meaning within Mongoose. If your schema requires using `type` as a nested property you must use object notation:
|
||||
|
||||
```js
|
||||
new Schema({
|
||||
broken: { type: Boolean },
|
||||
asset: {
|
||||
name: String,
|
||||
type: String // uh oh, it broke. asset will be interpreted as String
|
||||
}
|
||||
});
|
||||
|
||||
new Schema({
|
||||
works: { type: Boolean },
|
||||
asset: {
|
||||
name: String,
|
||||
type: { type: String } // works. asset is an object with a type property
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Driver Access
|
||||
|
||||
Mongoose is built on top of the [official MongoDB Node.js driver](https://github.com/mongodb/node-mongodb-native). Each mongoose model keeps a reference to a [native MongoDB driver collection](http://mongodb.github.io/node-mongodb-native/2.1/api/Collection.html). The collection object can be accessed using `YourModel.collection`. However, using the collection object directly bypasses all mongoose features, including hooks, validation, etc. The one
|
||||
notable exception that `YourModel.collection` still buffers
|
||||
commands. As such, `YourModel.collection.find()` will **not**
|
||||
return a cursor.
|
||||
|
||||
## API Docs
|
||||
|
||||
Find the API docs [here](http://mongoosejs.com/docs/api.html), generated using [dox](https://github.com/tj/dox)
|
||||
and [acquit](https://github.com/vkarpov15/acquit).
|
||||
|
||||
## Related Projects
|
||||
|
||||
#### MongoDB Runners
|
||||
|
||||
- [run-rs](https://www.npmjs.com/package/run-rs)
|
||||
- [mongodb-memory-server](https://www.npmjs.com/package/mongodb-memory-server)
|
||||
- [mongodb-topology-manager](https://www.npmjs.com/package/mongodb-topology-manager)
|
||||
|
||||
#### Unofficial CLIs
|
||||
|
||||
- [mongoosejs-cli](https://www.npmjs.com/package/mongoosejs-cli)
|
||||
|
||||
#### Data Seeding
|
||||
|
||||
- [dookie](https://www.npmjs.com/package/dookie)
|
||||
- [seedgoose](https://www.npmjs.com/package/seedgoose)
|
||||
- [mongoose-data-seed](https://www.npmjs.com/package/mongoose-data-seed)
|
||||
|
||||
#### Express Session Stores
|
||||
|
||||
- [connect-mongodb-session](https://www.npmjs.com/package/connect-mongodb-session)
|
||||
- [connect-mongo](https://www.npmjs.com/package/connect-mongo)
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2010 LearnBoost <dev@learnboost.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Export lib/mongoose
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./lib/browser');
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
|
||||
/**
|
||||
* Export lib/mongoose
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./lib/');
|
||||
+1112
File diff suppressed because it is too large.
Load diff
+144
@@ -0,0 +1,144 @@
|
||||
/* eslint-env browser */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('./driver').set(require('./drivers/browser'));
|
||||
|
||||
const DocumentProvider = require('./document_provider.js');
|
||||
const PromiseProvider = require('./promise_provider');
|
||||
|
||||
DocumentProvider.setBrowser(true);
|
||||
|
||||
/**
|
||||
* The Mongoose [Promise](#promise_Promise) constructor.
|
||||
*
|
||||
* @method Promise
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Object.defineProperty(exports, 'Promise', {
|
||||
get: function() {
|
||||
return PromiseProvider.get();
|
||||
},
|
||||
set: function(lib) {
|
||||
PromiseProvider.set(lib);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Storage layer for mongoose promises
|
||||
*
|
||||
* @method PromiseProvider
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.PromiseProvider = PromiseProvider;
|
||||
|
||||
/**
|
||||
* The [MongooseError](#error_MongooseError) constructor.
|
||||
*
|
||||
* @method Error
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.Error = require('./error/index');
|
||||
|
||||
/**
|
||||
* The Mongoose [Schema](#schema_Schema) constructor
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* var mongoose = require('mongoose');
|
||||
* var Schema = mongoose.Schema;
|
||||
* var CatSchema = new Schema(..);
|
||||
*
|
||||
* @method Schema
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.Schema = require('./schema');
|
||||
|
||||
/**
|
||||
* The various Mongoose Types.
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* var mongoose = require('mongoose');
|
||||
* var array = mongoose.Types.Array;
|
||||
*
|
||||
* ####Types:
|
||||
*
|
||||
* - [ObjectId](#types-objectid-js)
|
||||
* - [Buffer](#types-buffer-js)
|
||||
* - [SubDocument](#types-embedded-js)
|
||||
* - [Array](#types-array-js)
|
||||
* - [DocumentArray](#types-documentarray-js)
|
||||
*
|
||||
* Using this exposed access to the `ObjectId` type, we can construct ids on demand.
|
||||
*
|
||||
* var ObjectId = mongoose.Types.ObjectId;
|
||||
* var id1 = new ObjectId;
|
||||
*
|
||||
* @property Types
|
||||
* @api public
|
||||
*/
|
||||
exports.Types = require('./types');
|
||||
|
||||
/**
|
||||
* The Mongoose [VirtualType](#virtualtype_VirtualType) constructor
|
||||
*
|
||||
* @method VirtualType
|
||||
* @api public
|
||||
*/
|
||||
exports.VirtualType = require('./virtualtype');
|
||||
|
||||
/**
|
||||
* The various Mongoose SchemaTypes.
|
||||
*
|
||||
* ####Note:
|
||||
*
|
||||
* _Alias of mongoose.Schema.Types for backwards compatibility._
|
||||
*
|
||||
* @property SchemaTypes
|
||||
* @see Schema.SchemaTypes #schema_Schema.Types
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.SchemaType = require('./schematype.js');
|
||||
|
||||
/**
|
||||
* Internal utils
|
||||
*
|
||||
* @property utils
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.utils = require('./utils.js');
|
||||
|
||||
/**
|
||||
* The Mongoose browser [Document](#document-js) constructor.
|
||||
*
|
||||
* @method Document
|
||||
* @api public
|
||||
*/
|
||||
exports.Document = DocumentProvider();
|
||||
|
||||
/**
|
||||
* function stub for model
|
||||
*
|
||||
* @method model
|
||||
* @api public
|
||||
* @return null
|
||||
*/
|
||||
exports.model = function() {
|
||||
return null;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.mongoose = module.exports;
|
||||
window.Buffer = Buffer;
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const NodeJSDocument = require('./document');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const MongooseError = require('./error/index');
|
||||
const Schema = require('./schema');
|
||||
const ObjectId = require('./types/objectid');
|
||||
const ValidationError = MongooseError.ValidationError;
|
||||
const applyHooks = require('./helpers/model/applyHooks');
|
||||
const utils = require('./utils');
|
||||
|
||||
/**
|
||||
* Document constructor.
|
||||
*
|
||||
* @param {Object} obj the values to set
|
||||
* @param {Object} [fields] optional object containing the fields which were selected in the query returning this document and any populated paths data
|
||||
* @param {Boolean} [skipId] bool, should we auto create an ObjectId _id
|
||||
* @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
|
||||
* @event `init`: Emitted on a document after it has was retrieved from the db and fully hydrated by Mongoose.
|
||||
* @event `save`: Emitted when the document is successfully saved
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Document(obj, schema, fields, skipId, skipInit) {
|
||||
if (!(this instanceof Document)) {
|
||||
return new Document(obj, schema, fields, skipId, skipInit);
|
||||
}
|
||||
|
||||
if (utils.isObject(schema) && !schema.instanceOfSchema) {
|
||||
schema = new Schema(schema);
|
||||
}
|
||||
|
||||
// When creating EmbeddedDocument, it already has the schema and he doesn't need the _id
|
||||
schema = this.schema || schema;
|
||||
|
||||
// Generate ObjectId if it is missing, but it requires a scheme
|
||||
if (!this.schema && schema.options._id) {
|
||||
obj = obj || {};
|
||||
|
||||
if (obj._id === undefined) {
|
||||
obj._id = new ObjectId();
|
||||
}
|
||||
}
|
||||
|
||||
if (!schema) {
|
||||
throw new MongooseError.MissingSchemaError();
|
||||
}
|
||||
|
||||
this.$__setSchema(schema);
|
||||
|
||||
NodeJSDocument.call(this, obj, fields, skipId, skipInit);
|
||||
|
||||
applyHooks(this, schema, { decorateDoc: true });
|
||||
|
||||
// apply methods
|
||||
for (const m in schema.methods) {
|
||||
this[m] = schema.methods[m];
|
||||
}
|
||||
// apply statics
|
||||
for (const s in schema.statics) {
|
||||
this[s] = schema.statics[s];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherit from the NodeJS document
|
||||
*/
|
||||
|
||||
Document.prototype = Object.create(NodeJSDocument.prototype);
|
||||
Document.prototype.constructor = Document;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
Document.events = new EventEmitter();
|
||||
|
||||
/*!
|
||||
* Browser doc exposes the event emitter API
|
||||
*/
|
||||
|
||||
Document.$emitter = new EventEmitter();
|
||||
|
||||
utils.each(
|
||||
['on', 'once', 'emit', 'listeners', 'removeListener', 'setMaxListeners',
|
||||
'removeAllListeners', 'addListener'],
|
||||
function(emitterFn) {
|
||||
Document[emitterFn] = function() {
|
||||
return Document.$emitter[emitterFn].apply(Document.$emitter, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
Document.ValidationError = ValidationError;
|
||||
module.exports = exports = Document;
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const StrictModeError = require('./error/strict');
|
||||
const Types = require('./schema/index');
|
||||
const castTextSearch = require('./schema/operators/text');
|
||||
const get = require('./helpers/get');
|
||||
const util = require('util');
|
||||
const utils = require('./utils');
|
||||
|
||||
const ALLOWED_GEOWITHIN_GEOJSON_TYPES = ['Polygon', 'MultiPolygon'];
|
||||
|
||||
/**
|
||||
* Handles internal casting for query filters.
|
||||
*
|
||||
* @param {Schema} schema
|
||||
* @param {Object} obj Object to cast
|
||||
* @param {Object} options the query options
|
||||
* @param {Query} context passed to setters
|
||||
* @api private
|
||||
*/
|
||||
module.exports = function cast(schema, obj, options, context) {
|
||||
if (Array.isArray(obj)) {
|
||||
throw new Error('Query filter must be an object, got an array ', util.inspect(obj));
|
||||
}
|
||||
|
||||
// bson 1.x has the unfortunate tendency to remove filters that have a top-level
|
||||
// `_bsontype` property. Should remove this when we upgrade to bson 4.x. See gh-8222
|
||||
if (obj.hasOwnProperty('_bsontype')) {
|
||||
delete obj._bsontype;
|
||||
}
|
||||
|
||||
const paths = Object.keys(obj);
|
||||
let i = paths.length;
|
||||
let _keys;
|
||||
let any$conditionals;
|
||||
let schematype;
|
||||
let nested;
|
||||
let path;
|
||||
let type;
|
||||
let val;
|
||||
|
||||
options = options || {};
|
||||
|
||||
while (i--) {
|
||||
path = paths[i];
|
||||
val = obj[path];
|
||||
|
||||
if (path === '$or' || path === '$nor' || path === '$and') {
|
||||
let k = val.length;
|
||||
|
||||
while (k--) {
|
||||
val[k] = cast(schema, val[k], options, context);
|
||||
}
|
||||
} else if (path === '$where') {
|
||||
type = typeof val;
|
||||
|
||||
if (type !== 'string' && type !== 'function') {
|
||||
throw new Error('Must have a string or function for $where');
|
||||
}
|
||||
|
||||
if (type === 'function') {
|
||||
obj[path] = val.toString();
|
||||
}
|
||||
|
||||
continue;
|
||||
} else if (path === '$elemMatch') {
|
||||
val = cast(schema, val, options, context);
|
||||
} else if (path === '$text') {
|
||||
val = castTextSearch(val, path);
|
||||
} else {
|
||||
if (!schema) {
|
||||
// no casting for Mixed types
|
||||
continue;
|
||||
}
|
||||
|
||||
schematype = schema.path(path);
|
||||
|
||||
// Check for embedded discriminator paths
|
||||
if (!schematype) {
|
||||
const split = path.split('.');
|
||||
let j = split.length;
|
||||
while (j--) {
|
||||
const pathFirstHalf = split.slice(0, j).join('.');
|
||||
const pathLastHalf = split.slice(j).join('.');
|
||||
const _schematype = schema.path(pathFirstHalf);
|
||||
const discriminatorKey = get(_schematype, 'schema.options.discriminatorKey');
|
||||
|
||||
// gh-6027: if we haven't found the schematype but this path is
|
||||
// underneath an embedded discriminator and the embedded discriminator
|
||||
// key is in the query, use the embedded discriminator schema
|
||||
if (_schematype != null &&
|
||||
get(_schematype, 'schema.discriminators') != null &&
|
||||
discriminatorKey != null &&
|
||||
pathLastHalf !== discriminatorKey) {
|
||||
const discriminatorVal = get(obj, pathFirstHalf + '.' + discriminatorKey);
|
||||
if (discriminatorVal != null) {
|
||||
schematype = _schematype.schema.discriminators[discriminatorVal].
|
||||
path(pathLastHalf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!schematype) {
|
||||
// Handle potential embedded array queries
|
||||
const split = path.split('.');
|
||||
let j = split.length;
|
||||
let pathFirstHalf;
|
||||
let pathLastHalf;
|
||||
let remainingConds;
|
||||
|
||||
// Find the part of the var path that is a path of the Schema
|
||||
while (j--) {
|
||||
pathFirstHalf = split.slice(0, j).join('.');
|
||||
schematype = schema.path(pathFirstHalf);
|
||||
if (schematype) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If a substring of the input path resolves to an actual real path...
|
||||
if (schematype) {
|
||||
// Apply the casting; similar code for $elemMatch in schema/array.js
|
||||
if (schematype.caster && schematype.caster.schema) {
|
||||
remainingConds = {};
|
||||
pathLastHalf = split.slice(j).join('.');
|
||||
remainingConds[pathLastHalf] = val;
|
||||
obj[path] = cast(schematype.caster.schema, remainingConds, options, context)[pathLastHalf];
|
||||
} else {
|
||||
obj[path] = val;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (utils.isObject(val)) {
|
||||
// handle geo schemas that use object notation
|
||||
// { loc: { long: Number, lat: Number }
|
||||
|
||||
let geo = '';
|
||||
if (val.$near) {
|
||||
geo = '$near';
|
||||
} else if (val.$nearSphere) {
|
||||
geo = '$nearSphere';
|
||||
} else if (val.$within) {
|
||||
geo = '$within';
|
||||
} else if (val.$geoIntersects) {
|
||||
geo = '$geoIntersects';
|
||||
} else if (val.$geoWithin) {
|
||||
geo = '$geoWithin';
|
||||
}
|
||||
|
||||
if (geo) {
|
||||
const numbertype = new Types.Number('__QueryCasting__');
|
||||
let value = val[geo];
|
||||
|
||||
if (val.$maxDistance != null) {
|
||||
val.$maxDistance = numbertype.castForQueryWrapper({
|
||||
val: val.$maxDistance,
|
||||
context: context
|
||||
});
|
||||
}
|
||||
if (val.$minDistance != null) {
|
||||
val.$minDistance = numbertype.castForQueryWrapper({
|
||||
val: val.$minDistance,
|
||||
context: context
|
||||
});
|
||||
}
|
||||
|
||||
if (geo === '$within') {
|
||||
const withinType = value.$center
|
||||
|| value.$centerSphere
|
||||
|| value.$box
|
||||
|| value.$polygon;
|
||||
|
||||
if (!withinType) {
|
||||
throw new Error('Bad $within parameter: ' + JSON.stringify(val));
|
||||
}
|
||||
|
||||
value = withinType;
|
||||
} else if (geo === '$near' &&
|
||||
typeof value.type === 'string' && Array.isArray(value.coordinates)) {
|
||||
// geojson; cast the coordinates
|
||||
value = value.coordinates;
|
||||
} else if ((geo === '$near' || geo === '$nearSphere' || geo === '$geoIntersects') &&
|
||||
value.$geometry && typeof value.$geometry.type === 'string' &&
|
||||
Array.isArray(value.$geometry.coordinates)) {
|
||||
if (value.$maxDistance != null) {
|
||||
value.$maxDistance = numbertype.castForQueryWrapper({
|
||||
val: value.$maxDistance,
|
||||
context: context
|
||||
});
|
||||
}
|
||||
if (value.$minDistance != null) {
|
||||
value.$minDistance = numbertype.castForQueryWrapper({
|
||||
val: value.$minDistance,
|
||||
context: context
|
||||
});
|
||||
}
|
||||
if (utils.isMongooseObject(value.$geometry)) {
|
||||
value.$geometry = value.$geometry.toObject({
|
||||
transform: false,
|
||||
virtuals: false
|
||||
});
|
||||
}
|
||||
value = value.$geometry.coordinates;
|
||||
} else if (geo === '$geoWithin') {
|
||||
if (value.$geometry) {
|
||||
if (utils.isMongooseObject(value.$geometry)) {
|
||||
value.$geometry = value.$geometry.toObject({ virtuals: false });
|
||||
}
|
||||
const geoWithinType = value.$geometry.type;
|
||||
if (ALLOWED_GEOWITHIN_GEOJSON_TYPES.indexOf(geoWithinType) === -1) {
|
||||
throw new Error('Invalid geoJSON type for $geoWithin "' +
|
||||
geoWithinType + '", must be "Polygon" or "MultiPolygon"');
|
||||
}
|
||||
value = value.$geometry.coordinates;
|
||||
} else {
|
||||
value = value.$box || value.$polygon || value.$center ||
|
||||
value.$centerSphere;
|
||||
if (utils.isMongooseObject(value)) {
|
||||
value = value.toObject({ virtuals: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_cast(value, numbertype, context);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (schema.nested[path]) {
|
||||
continue;
|
||||
}
|
||||
if (options.upsert && options.strict) {
|
||||
if (options.strict === 'throw') {
|
||||
throw new StrictModeError(path);
|
||||
}
|
||||
throw new StrictModeError(path, 'Path "' + path + '" is not in ' +
|
||||
'schema, strict mode is `true`, and upsert is `true`.');
|
||||
} else if (options.strictQuery === 'throw') {
|
||||
throw new StrictModeError(path, 'Path "' + path + '" is not in ' +
|
||||
'schema and strictQuery is \'throw\'.');
|
||||
} else if (options.strictQuery) {
|
||||
delete obj[path];
|
||||
}
|
||||
} else if (val == null) {
|
||||
continue;
|
||||
} else if (val.constructor.name === 'Object') {
|
||||
any$conditionals = Object.keys(val).some(function(k) {
|
||||
return k.startsWith('$') && k !== '$id' && k !== '$ref';
|
||||
});
|
||||
|
||||
if (!any$conditionals) {
|
||||
obj[path] = schematype.castForQueryWrapper({
|
||||
val: val,
|
||||
context: context
|
||||
});
|
||||
} else {
|
||||
const ks = Object.keys(val);
|
||||
let $cond;
|
||||
|
||||
let k = ks.length;
|
||||
|
||||
while (k--) {
|
||||
$cond = ks[k];
|
||||
nested = val[$cond];
|
||||
|
||||
if ($cond === '$not') {
|
||||
if (nested && schematype && !schematype.caster) {
|
||||
_keys = Object.keys(nested);
|
||||
if (_keys.length && _keys[0].startsWith('$')) {
|
||||
for (const key in nested) {
|
||||
nested[key] = schematype.castForQueryWrapper({
|
||||
$conditional: key,
|
||||
val: nested[key],
|
||||
context: context
|
||||
});
|
||||
}
|
||||
} else {
|
||||
val[$cond] = schematype.castForQueryWrapper({
|
||||
$conditional: $cond,
|
||||
val: nested,
|
||||
context: context
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
cast(schematype.caster ? schematype.caster.schema : schema, nested, options, context);
|
||||
} else {
|
||||
val[$cond] = schematype.castForQueryWrapper({
|
||||
$conditional: $cond,
|
||||
val: nested,
|
||||
context: context
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (Array.isArray(val) && ['Buffer', 'Array'].indexOf(schematype.instance) === -1) {
|
||||
const casted = [];
|
||||
for (let valIndex = 0; valIndex < val.length; valIndex++) {
|
||||
casted.push(schematype.castForQueryWrapper({
|
||||
val: val[valIndex],
|
||||
context: context
|
||||
}));
|
||||
}
|
||||
|
||||
obj[path] = { $in: casted };
|
||||
} else {
|
||||
obj[path] = schematype.castForQueryWrapper({
|
||||
val: val,
|
||||
context: context
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
function _cast(val, numbertype, context) {
|
||||
if (Array.isArray(val)) {
|
||||
val.forEach(function(item, i) {
|
||||
if (Array.isArray(item) || utils.isObject(item)) {
|
||||
return _cast(item, numbertype, context);
|
||||
}
|
||||
val[i] = numbertype.castForQueryWrapper({ val: item, context: context });
|
||||
});
|
||||
} else {
|
||||
const nearKeys = Object.keys(val);
|
||||
let nearLen = nearKeys.length;
|
||||
while (nearLen--) {
|
||||
const nkey = nearKeys[nearLen];
|
||||
const item = val[nkey];
|
||||
if (Array.isArray(item) || utils.isObject(item)) {
|
||||
_cast(item, numbertype, context);
|
||||
val[nkey] = item;
|
||||
} else {
|
||||
val[nkey] = numbertype.castForQuery({ val: item, context: context });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
const CastError = require('../error/cast');
|
||||
|
||||
/*!
|
||||
* Given a value, cast it to a boolean, or throw a `CastError` if the value
|
||||
* cannot be casted. `null` and `undefined` are considered valid.
|
||||
*
|
||||
* @param {Any} value
|
||||
* @param {String} [path] optional the path to set on the CastError
|
||||
* @return {Boolean|null|undefined}
|
||||
* @throws {CastError} if `value` is not one of the allowed values
|
||||
* @api private
|
||||
*/
|
||||
|
||||
module.exports = function castBoolean(value, path) {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (module.exports.convertToTrue.has(value)) {
|
||||
return true;
|
||||
}
|
||||
if (module.exports.convertToFalse.has(value)) {
|
||||
return false;
|
||||
}
|
||||
throw new CastError('boolean', value, path);
|
||||
};
|
||||
|
||||
module.exports.convertToTrue = new Set([true, 'true', 1, '1', 'yes']);
|
||||
module.exports.convertToFalse = new Set([false, 'false', 0, '0', 'no']);
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
module.exports = function castDate(value) {
|
||||
// Support empty string because of empty form values. Originally introduced
|
||||
// in https://github.com/Automattic/mongoose/commit/efc72a1898fc3c33a319d915b8c5463a22938dfe
|
||||
if (value == null || value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value instanceof Date) {
|
||||
assert.ok(!isNaN(value.valueOf()));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
let date;
|
||||
|
||||
assert.ok(typeof value !== 'boolean');
|
||||
|
||||
if (value instanceof Number || typeof value === 'number') {
|
||||
date = new Date(value);
|
||||
} else if (typeof value === 'string' && !isNaN(Number(value)) && (Number(value) >= 275761 || Number(value) < -271820)) {
|
||||
// string representation of milliseconds take this path
|
||||
date = new Date(Number(value));
|
||||
} else if (typeof value.valueOf === 'function') {
|
||||
// support for moment.js. This is also the path strings will take because
|
||||
// strings have a `valueOf()`
|
||||
date = new Date(value.valueOf());
|
||||
} else {
|
||||
// fallback
|
||||
date = new Date(value);
|
||||
}
|
||||
|
||||
if (!isNaN(date.valueOf())) {
|
||||
return date;
|
||||
}
|
||||
|
||||
assert.ok(false);
|
||||
};
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
const Decimal128Type = require('../types/decimal128');
|
||||
const assert = require('assert');
|
||||
|
||||
module.exports = function castDecimal128(value) {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && typeof value.$numberDecimal === 'string') {
|
||||
return Decimal128Type.fromString(value.$numberDecimal);
|
||||
}
|
||||
|
||||
if (value instanceof Decimal128Type) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return Decimal128Type.fromString(value);
|
||||
}
|
||||
|
||||
if (Buffer.isBuffer(value)) {
|
||||
return new Decimal128Type(value);
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return Decimal128Type.fromString(String(value));
|
||||
}
|
||||
|
||||
if (typeof value.valueOf === 'function' && typeof value.valueOf() === 'string') {
|
||||
return Decimal128Type.fromString(value.valueOf());
|
||||
}
|
||||
|
||||
assert.ok(false);
|
||||
};
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
/*!
|
||||
* Given a value, cast it to a number, or throw a `CastError` if the value
|
||||
* cannot be casted. `null` and `undefined` are considered valid.
|
||||
*
|
||||
* @param {Any} value
|
||||
* @param {String} [path] optional the path to set on the CastError
|
||||
* @return {Boolean|null|undefined}
|
||||
* @throws {Error} if `value` is not one of the allowed values
|
||||
* @api private
|
||||
*/
|
||||
|
||||
module.exports = function castNumber(val) {
|
||||
assert.ok(!isNaN(val));
|
||||
|
||||
if (val == null) {
|
||||
return val;
|
||||
}
|
||||
if (val === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof val === 'string' || typeof val === 'boolean') {
|
||||
val = Number(val);
|
||||
}
|
||||
|
||||
assert.ok(!isNaN(val));
|
||||
if (val instanceof Number) {
|
||||
return val.valueOf();
|
||||
}
|
||||
if (typeof val === 'number') {
|
||||
return val;
|
||||
}
|
||||
if (!Array.isArray(val) && typeof val.valueOf === 'function') {
|
||||
return Number(val.valueOf());
|
||||
}
|
||||
if (val.toString && !Array.isArray(val) && val.toString() == Number(val)) {
|
||||
return Number(val);
|
||||
}
|
||||
|
||||
assert.ok(false);
|
||||
};
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const ObjectId = require('../driver').get().ObjectId;
|
||||
const assert = require('assert');
|
||||
|
||||
module.exports = function castObjectId(value) {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value instanceof ObjectId) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value._id) {
|
||||
if (value._id instanceof ObjectId) {
|
||||
return value._id;
|
||||
}
|
||||
if (value._id.toString instanceof Function) {
|
||||
return new ObjectId(value._id.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (value.toString instanceof Function) {
|
||||
return new ObjectId(value.toString());
|
||||
}
|
||||
|
||||
assert.ok(false);
|
||||
};
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
const CastError = require('../error/cast');
|
||||
|
||||
/*!
|
||||
* Given a value, cast it to a string, or throw a `CastError` if the value
|
||||
* cannot be casted. `null` and `undefined` are considered valid.
|
||||
*
|
||||
* @param {Any} value
|
||||
* @param {String} [path] optional the path to set on the CastError
|
||||
* @return {string|null|undefined}
|
||||
* @throws {CastError}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
module.exports = function castString(value, path) {
|
||||
// If null or undefined
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// handle documents being passed
|
||||
if (value._id && typeof value._id === 'string') {
|
||||
return value._id;
|
||||
}
|
||||
|
||||
// Re: gh-647 and gh-3030, we're ok with casting using `toString()`
|
||||
// **unless** its the default Object.toString, because "[object Object]"
|
||||
// doesn't really qualify as useful data
|
||||
if (value.toString &&
|
||||
value.toString !== Object.prototype.toString &&
|
||||
!Array.isArray(value)) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
throw new CastError('string', value, path);
|
||||
};
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const STATES = require('./connectionstate');
|
||||
const immediate = require('./helpers/immediate');
|
||||
|
||||
/**
|
||||
* Abstract Collection constructor
|
||||
*
|
||||
* This is the base class that drivers inherit from and implement.
|
||||
*
|
||||
* @param {String} name name of the collection
|
||||
* @param {Connection} conn A MongooseConnection instance
|
||||
* @param {Object} opts optional collection options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Collection(name, conn, opts) {
|
||||
if (opts === void 0) {
|
||||
opts = {};
|
||||
}
|
||||
if (opts.capped === void 0) {
|
||||
opts.capped = {};
|
||||
}
|
||||
|
||||
opts.bufferCommands = undefined === opts.bufferCommands
|
||||
? true
|
||||
: opts.bufferCommands;
|
||||
|
||||
if (typeof opts.capped === 'number') {
|
||||
opts.capped = {size: opts.capped};
|
||||
}
|
||||
|
||||
this.opts = opts;
|
||||
this.name = name;
|
||||
this.collectionName = name;
|
||||
this.conn = conn;
|
||||
this.queue = [];
|
||||
this.buffer = this.opts.bufferCommands;
|
||||
this.emitter = new EventEmitter();
|
||||
|
||||
if (STATES.connected === this.conn.readyState) {
|
||||
this.onOpen();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The collection name
|
||||
*
|
||||
* @api public
|
||||
* @property name
|
||||
*/
|
||||
|
||||
Collection.prototype.name;
|
||||
|
||||
/**
|
||||
* The collection name
|
||||
*
|
||||
* @api public
|
||||
* @property collectionName
|
||||
*/
|
||||
|
||||
Collection.prototype.collectionName;
|
||||
|
||||
/**
|
||||
* The Connection instance
|
||||
*
|
||||
* @api public
|
||||
* @property conn
|
||||
*/
|
||||
|
||||
Collection.prototype.conn;
|
||||
|
||||
/**
|
||||
* Called when the database connects
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Collection.prototype.onOpen = function() {
|
||||
this.buffer = false;
|
||||
immediate(() => this.doQueue());
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the database disconnects
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Collection.prototype.onClose = function(force) {
|
||||
if (this.opts.bufferCommands && !force) {
|
||||
this.buffer = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Queues a method for later execution when its
|
||||
* database connection opens.
|
||||
*
|
||||
* @param {String} name name of the method to queue
|
||||
* @param {Array} args arguments to pass to the method when executed
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Collection.prototype.addQueue = function(name, args) {
|
||||
this.queue.push([name, args]);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes all queued methods and clears the queue.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Collection.prototype.doQueue = function() {
|
||||
for (let i = 0, l = this.queue.length; i < l; i++) {
|
||||
if (typeof this.queue[i][0] === 'function') {
|
||||
this.queue[i][0].apply(this, this.queue[i][1]);
|
||||
} else {
|
||||
this[this.queue[i][0]].apply(this, this.queue[i][1]);
|
||||
}
|
||||
}
|
||||
this.queue = [];
|
||||
const _this = this;
|
||||
process.nextTick(function() {
|
||||
_this.emitter.emit('queue');
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract method that drivers must implement.
|
||||
*/
|
||||
|
||||
Collection.prototype.ensureIndex = function() {
|
||||
throw new Error('Collection#ensureIndex unimplemented by driver');
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract method that drivers must implement.
|
||||
*/
|
||||
|
||||
Collection.prototype.createIndex = function() {
|
||||
throw new Error('Collection#ensureIndex unimplemented by driver');
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract method that drivers must implement.
|
||||
*/
|
||||
|
||||
Collection.prototype.findAndModify = function() {
|
||||
throw new Error('Collection#findAndModify unimplemented by driver');
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract method that drivers must implement.
|
||||
*/
|
||||
|
||||
Collection.prototype.findOneAndUpdate = function() {
|
||||
throw new Error('Collection#findOneAndUpdate unimplemented by driver');
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract method that drivers must implement.
|
||||
*/
|
||||
|
||||
Collection.prototype.findOneAndDelete = function() {
|
||||
throw new Error('Collection#findOneAndDelete unimplemented by driver');
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract method that drivers must implement.
|
||||
*/
|
||||
|
||||
Collection.prototype.findOneAndReplace = function() {
|
||||
throw new Error('Collection#findOneAndReplace unimplemented by driver');
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract method that drivers must implement.
|
||||
*/
|
||||
|
||||
Collection.prototype.findOne = function() {
|
||||
throw new Error('Collection#findOne unimplemented by driver');
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract method that drivers must implement.
|
||||
*/
|
||||
|
||||
Collection.prototype.find = function() {
|
||||
throw new Error('Collection#find unimplemented by driver');
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract method that drivers must implement.
|
||||
*/
|
||||
|
||||
Collection.prototype.insert = function() {
|
||||
throw new Error('Collection#insert unimplemented by driver');
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract method that drivers must implement.
|
||||
*/
|
||||
|
||||
Collection.prototype.insertOne = function() {
|
||||
throw new Error('Collection#insertOne unimplemented by driver');
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract method that drivers must implement.
|
||||
*/
|
||||
|
||||
Collection.prototype.insertMany = function() {
|
||||
throw new Error('Collection#insertMany unimplemented by driver');
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract method that drivers must implement.
|
||||
*/
|
||||
|
||||
Collection.prototype.save = function() {
|
||||
throw new Error('Collection#save unimplemented by driver');
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract method that drivers must implement.
|
||||
*/
|
||||
|
||||
Collection.prototype.update = function() {
|
||||
throw new Error('Collection#update unimplemented by driver');
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract method that drivers must implement.
|
||||
*/
|
||||
|
||||
Collection.prototype.getIndexes = function() {
|
||||
throw new Error('Collection#getIndexes unimplemented by driver');
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract method that drivers must implement.
|
||||
*/
|
||||
|
||||
Collection.prototype.mapReduce = function() {
|
||||
throw new Error('Collection#mapReduce unimplemented by driver');
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract method that drivers must implement.
|
||||
*/
|
||||
|
||||
Collection.prototype.watch = function() {
|
||||
throw new Error('Collection#watch unimplemented by driver');
|
||||
};
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = Collection;
|
||||
+1110
File diff suppressed because it is too large.
Load diff
+26
@@ -0,0 +1,26 @@
|
||||
|
||||
/*!
|
||||
* Connection states
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const STATES = module.exports = exports = Object.create(null);
|
||||
|
||||
const disconnected = 'disconnected';
|
||||
const connected = 'connected';
|
||||
const connecting = 'connecting';
|
||||
const disconnecting = 'disconnecting';
|
||||
const uninitialized = 'uninitialized';
|
||||
|
||||
STATES[0] = disconnected;
|
||||
STATES[1] = connected;
|
||||
STATES[2] = connecting;
|
||||
STATES[3] = disconnecting;
|
||||
STATES[99] = uninitialized;
|
||||
|
||||
STATES[disconnected] = 0;
|
||||
STATES[connected] = 1;
|
||||
STATES[connecting] = 2;
|
||||
STATES[disconnecting] = 3;
|
||||
STATES[uninitialized] = 99;
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const Readable = require('stream').Readable;
|
||||
const eachAsync = require('../helpers/cursor/eachAsync');
|
||||
const util = require('util');
|
||||
const utils = require('../utils');
|
||||
|
||||
/**
|
||||
* An AggregationCursor is a concurrency primitive for processing aggregation
|
||||
* results one document at a time. It is analogous to QueryCursor.
|
||||
*
|
||||
* An AggregationCursor fulfills the Node.js streams3 API,
|
||||
* in addition to several other mechanisms for loading documents from MongoDB
|
||||
* one at a time.
|
||||
*
|
||||
* Creating an AggregationCursor executes the model's pre aggregate hooks,
|
||||
* but **not** the model's post aggregate hooks.
|
||||
*
|
||||
* Unless you're an advanced user, do **not** instantiate this class directly.
|
||||
* Use [`Aggregate#cursor()`](/docs/api.html#aggregate_Aggregate-cursor) instead.
|
||||
*
|
||||
* @param {Aggregate} agg
|
||||
* @param {Object} options
|
||||
* @inherits Readable
|
||||
* @event `cursor`: Emitted when the cursor is created
|
||||
* @event `error`: Emitted when an error occurred
|
||||
* @event `data`: Emitted when the stream is flowing and the next doc is ready
|
||||
* @event `end`: Emitted when the stream is exhausted
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function AggregationCursor(agg) {
|
||||
Readable.call(this, { objectMode: true });
|
||||
|
||||
this.cursor = null;
|
||||
this.agg = agg;
|
||||
this._transforms = [];
|
||||
const model = agg._model;
|
||||
delete agg.options.cursor.useMongooseAggCursor;
|
||||
this._mongooseOptions = {};
|
||||
|
||||
_init(model, this, agg);
|
||||
}
|
||||
|
||||
util.inherits(AggregationCursor, Readable);
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function _init(model, c, agg) {
|
||||
if (!model.collection.buffer) {
|
||||
model.hooks.execPre('aggregate', agg, function() {
|
||||
c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
|
||||
c.emit('cursor', c.cursor);
|
||||
});
|
||||
} else {
|
||||
model.collection.emitter.once('queue', function() {
|
||||
model.hooks.execPre('aggregate', agg, function() {
|
||||
c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
|
||||
c.emit('cursor', c.cursor);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Necessary to satisfy the Readable API
|
||||
*/
|
||||
|
||||
AggregationCursor.prototype._read = function() {
|
||||
const _this = this;
|
||||
_next(this, function(error, doc) {
|
||||
if (error) {
|
||||
return _this.emit('error', error);
|
||||
}
|
||||
if (!doc) {
|
||||
_this.push(null);
|
||||
_this.cursor.close(function(error) {
|
||||
if (error) {
|
||||
return _this.emit('error', error);
|
||||
}
|
||||
setTimeout(function() {
|
||||
_this.emit('close');
|
||||
}, 0);
|
||||
});
|
||||
return;
|
||||
}
|
||||
_this.push(doc);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a transform function which subsequently maps documents retrieved
|
||||
* via the streams interface or `.next()`
|
||||
*
|
||||
* ####Example
|
||||
*
|
||||
* // Map documents returned by `data` events
|
||||
* Thing.
|
||||
* find({ name: /^hello/ }).
|
||||
* cursor().
|
||||
* map(function (doc) {
|
||||
* doc.foo = "bar";
|
||||
* return doc;
|
||||
* })
|
||||
* on('data', function(doc) { console.log(doc.foo); });
|
||||
*
|
||||
* // Or map documents returned by `.next()`
|
||||
* var cursor = Thing.find({ name: /^hello/ }).
|
||||
* cursor().
|
||||
* map(function (doc) {
|
||||
* doc.foo = "bar";
|
||||
* return doc;
|
||||
* });
|
||||
* cursor.next(function(error, doc) {
|
||||
* console.log(doc.foo);
|
||||
* });
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {AggregationCursor}
|
||||
* @api public
|
||||
* @method map
|
||||
*/
|
||||
|
||||
AggregationCursor.prototype.map = function(fn) {
|
||||
this._transforms.push(fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Marks this cursor as errored
|
||||
*/
|
||||
|
||||
AggregationCursor.prototype._markError = function(error) {
|
||||
this._error = error;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Marks this cursor as closed. Will stop streaming and subsequent calls to
|
||||
* `next()` will error.
|
||||
*
|
||||
* @param {Function} callback
|
||||
* @return {Promise}
|
||||
* @api public
|
||||
* @method close
|
||||
* @emits close
|
||||
* @see MongoDB driver cursor#close http://mongodb.github.io/node-mongodb-native/2.1/api/Cursor.html#close
|
||||
*/
|
||||
|
||||
AggregationCursor.prototype.close = function(callback) {
|
||||
return utils.promiseOrCallback(callback, cb => {
|
||||
this.cursor.close(error => {
|
||||
if (error) {
|
||||
cb(error);
|
||||
return this.listeners('error').length > 0 && this.emit('error', error);
|
||||
}
|
||||
this.emit('close');
|
||||
cb(null);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the next document from this cursor. Will return `null` when there are
|
||||
* no documents left.
|
||||
*
|
||||
* @param {Function} callback
|
||||
* @return {Promise}
|
||||
* @api public
|
||||
* @method next
|
||||
*/
|
||||
|
||||
AggregationCursor.prototype.next = function(callback) {
|
||||
return utils.promiseOrCallback(callback, cb => {
|
||||
_next(this, cb);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute `fn` for every document in the cursor. If `fn` returns a promise,
|
||||
* will wait for the promise to resolve before iterating on to the next one.
|
||||
* Returns a promise that resolves when done.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @param {Object} [options]
|
||||
* @param {Number} [options.parallel] the number of promises to execute in parallel. Defaults to 1.
|
||||
* @param {Function} [callback] executed when all docs have been processed
|
||||
* @return {Promise}
|
||||
* @api public
|
||||
* @method eachAsync
|
||||
*/
|
||||
|
||||
AggregationCursor.prototype.eachAsync = function(fn, opts, callback) {
|
||||
const _this = this;
|
||||
if (typeof opts === 'function') {
|
||||
callback = opts;
|
||||
opts = {};
|
||||
}
|
||||
opts = opts || {};
|
||||
|
||||
return eachAsync(function(cb) { return _next(_this, cb); }, fn, opts, callback);
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
AggregationCursor.prototype.transformNull = function(val) {
|
||||
if (arguments.length === 0) {
|
||||
val = true;
|
||||
}
|
||||
this._mongooseOptions.transformNull = val;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a [cursor flag](http://mongodb.github.io/node-mongodb-native/2.2/api/Cursor.html#addCursorFlag).
|
||||
* Useful for setting the `noCursorTimeout` and `tailable` flags.
|
||||
*
|
||||
* @param {String} flag
|
||||
* @param {Boolean} value
|
||||
* @return {AggregationCursor} this
|
||||
* @api public
|
||||
* @method addCursorFlag
|
||||
*/
|
||||
|
||||
AggregationCursor.prototype.addCursorFlag = function(flag, value) {
|
||||
const _this = this;
|
||||
_waitForCursor(this, function() {
|
||||
_this.cursor.addCursorFlag(flag, value);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function _waitForCursor(ctx, cb) {
|
||||
if (ctx.cursor) {
|
||||
return cb();
|
||||
}
|
||||
ctx.once('cursor', function() {
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the next doc from the underlying cursor and mongooseify it
|
||||
* (populate, etc.)
|
||||
*/
|
||||
|
||||
function _next(ctx, cb) {
|
||||
let callback = cb;
|
||||
if (ctx._transforms.length) {
|
||||
callback = function(err, doc) {
|
||||
if (err || (doc === null && !ctx._mongooseOptions.transformNull)) {
|
||||
return cb(err, doc);
|
||||
}
|
||||
cb(err, ctx._transforms.reduce(function(doc, fn) {
|
||||
return fn(doc);
|
||||
}, doc));
|
||||
};
|
||||
}
|
||||
|
||||
if (ctx._error) {
|
||||
return process.nextTick(function() {
|
||||
callback(ctx._error);
|
||||
});
|
||||
}
|
||||
|
||||
if (ctx.cursor) {
|
||||
return ctx.cursor.next(function(error, doc) {
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
if (!doc) {
|
||||
return callback(null, null);
|
||||
}
|
||||
|
||||
callback(null, doc);
|
||||
});
|
||||
} else {
|
||||
ctx.once('cursor', function() {
|
||||
_next(ctx, cb);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AggregationCursor;
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
class ChangeStream extends EventEmitter {
|
||||
constructor(model, pipeline, options) {
|
||||
super();
|
||||
|
||||
this.driverChangeStream = null;
|
||||
this.closed = false;
|
||||
// This wrapper is necessary because of buffering.
|
||||
if (model.collection.buffer) {
|
||||
model.collection.addQueue(() => {
|
||||
if (this.closed) {
|
||||
return;
|
||||
}
|
||||
this.driverChangeStream = model.collection.watch(pipeline, options);
|
||||
this._bindEvents();
|
||||
this.emit('ready');
|
||||
});
|
||||
} else {
|
||||
this.driverChangeStream = model.collection.watch(pipeline, options);
|
||||
this._bindEvents();
|
||||
this.emit('ready');
|
||||
}
|
||||
}
|
||||
|
||||
_bindEvents() {
|
||||
this.driverChangeStream.on('close', () => {
|
||||
this.closed = true;
|
||||
});
|
||||
|
||||
['close', 'change', 'end', 'error'].forEach(ev => {
|
||||
this.driverChangeStream.on(ev, data => this.emit(ev, data));
|
||||
});
|
||||
}
|
||||
|
||||
_queue(cb) {
|
||||
this.once('ready', () => cb());
|
||||
}
|
||||
|
||||
close() {
|
||||
this.closed = true;
|
||||
if (this.driverChangeStream) {
|
||||
this.driverChangeStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = ChangeStream;
|
||||
+338
@@ -0,0 +1,338 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const Readable = require('stream').Readable;
|
||||
const eachAsync = require('../helpers/cursor/eachAsync');
|
||||
const helpers = require('../queryhelpers');
|
||||
const util = require('util');
|
||||
const utils = require('../utils');
|
||||
|
||||
/**
|
||||
* A QueryCursor is a concurrency primitive for processing query results
|
||||
* one document at a time. A QueryCursor fulfills the Node.js streams3 API,
|
||||
* in addition to several other mechanisms for loading documents from MongoDB
|
||||
* one at a time.
|
||||
*
|
||||
* QueryCursors execute the model's pre find hooks, but **not** the model's
|
||||
* post find hooks.
|
||||
*
|
||||
* Unless you're an advanced user, do **not** instantiate this class directly.
|
||||
* Use [`Query#cursor()`](/docs/api.html#query_Query-cursor) instead.
|
||||
*
|
||||
* @param {Query} query
|
||||
* @param {Object} options query options passed to `.find()`
|
||||
* @inherits Readable
|
||||
* @event `cursor`: Emitted when the cursor is created
|
||||
* @event `error`: Emitted when an error occurred
|
||||
* @event `data`: Emitted when the stream is flowing and the next doc is ready
|
||||
* @event `end`: Emitted when the stream is exhausted
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function QueryCursor(query, options) {
|
||||
Readable.call(this, { objectMode: true });
|
||||
|
||||
this.cursor = null;
|
||||
this.query = query;
|
||||
const _this = this;
|
||||
const model = query.model;
|
||||
this._mongooseOptions = {};
|
||||
this._transforms = [];
|
||||
this.model = model;
|
||||
model.hooks.execPre('find', query, () => {
|
||||
this._transforms = this._transforms.concat(query._transforms.slice());
|
||||
if (options.transform) {
|
||||
this._transforms.push(options.transform);
|
||||
}
|
||||
// Re: gh-8039, you need to set the `cursor.batchSize` option, top-level
|
||||
// `batchSize` option doesn't work.
|
||||
options = options || {};
|
||||
if (options.batchSize) {
|
||||
options.cursor = options.cursor || {};
|
||||
options.cursor.batchSize = options.batchSize;
|
||||
}
|
||||
model.collection.find(query._conditions, options, function(err, cursor) {
|
||||
if (_this._error) {
|
||||
cursor.close(function() {});
|
||||
_this.listeners('error').length > 0 && _this.emit('error', _this._error);
|
||||
}
|
||||
if (err) {
|
||||
return _this.emit('error', err);
|
||||
}
|
||||
_this.cursor = cursor;
|
||||
_this.emit('cursor', cursor);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
util.inherits(QueryCursor, Readable);
|
||||
|
||||
/*!
|
||||
* Necessary to satisfy the Readable API
|
||||
*/
|
||||
|
||||
QueryCursor.prototype._read = function() {
|
||||
const _this = this;
|
||||
_next(this, function(error, doc) {
|
||||
if (error) {
|
||||
return _this.emit('error', error);
|
||||
}
|
||||
if (!doc) {
|
||||
_this.push(null);
|
||||
_this.cursor.close(function(error) {
|
||||
if (error) {
|
||||
return _this.emit('error', error);
|
||||
}
|
||||
setTimeout(function() {
|
||||
_this.emit('close');
|
||||
}, 0);
|
||||
});
|
||||
return;
|
||||
}
|
||||
_this.push(doc);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a transform function which subsequently maps documents retrieved
|
||||
* via the streams interface or `.next()`
|
||||
*
|
||||
* ####Example
|
||||
*
|
||||
* // Map documents returned by `data` events
|
||||
* Thing.
|
||||
* find({ name: /^hello/ }).
|
||||
* cursor().
|
||||
* map(function (doc) {
|
||||
* doc.foo = "bar";
|
||||
* return doc;
|
||||
* })
|
||||
* on('data', function(doc) { console.log(doc.foo); });
|
||||
*
|
||||
* // Or map documents returned by `.next()`
|
||||
* var cursor = Thing.find({ name: /^hello/ }).
|
||||
* cursor().
|
||||
* map(function (doc) {
|
||||
* doc.foo = "bar";
|
||||
* return doc;
|
||||
* });
|
||||
* cursor.next(function(error, doc) {
|
||||
* console.log(doc.foo);
|
||||
* });
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {QueryCursor}
|
||||
* @api public
|
||||
* @method map
|
||||
*/
|
||||
|
||||
QueryCursor.prototype.map = function(fn) {
|
||||
this._transforms.push(fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Marks this cursor as errored
|
||||
*/
|
||||
|
||||
QueryCursor.prototype._markError = function(error) {
|
||||
this._error = error;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Marks this cursor as closed. Will stop streaming and subsequent calls to
|
||||
* `next()` will error.
|
||||
*
|
||||
* @param {Function} callback
|
||||
* @return {Promise}
|
||||
* @api public
|
||||
* @method close
|
||||
* @emits close
|
||||
* @see MongoDB driver cursor#close http://mongodb.github.io/node-mongodb-native/2.1/api/Cursor.html#close
|
||||
*/
|
||||
|
||||
QueryCursor.prototype.close = function(callback) {
|
||||
return utils.promiseOrCallback(callback, cb => {
|
||||
this.cursor.close(error => {
|
||||
if (error) {
|
||||
cb(error);
|
||||
return this.listeners('error').length > 0 && this.emit('error', error);
|
||||
}
|
||||
this.emit('close');
|
||||
cb(null);
|
||||
});
|
||||
}, this.model.events);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the next document from this cursor. Will return `null` when there are
|
||||
* no documents left.
|
||||
*
|
||||
* @param {Function} callback
|
||||
* @return {Promise}
|
||||
* @api public
|
||||
* @method next
|
||||
*/
|
||||
|
||||
QueryCursor.prototype.next = function(callback) {
|
||||
return utils.promiseOrCallback(callback, cb => {
|
||||
_next(this, function(error, doc) {
|
||||
if (error) {
|
||||
return cb(error);
|
||||
}
|
||||
cb(null, doc);
|
||||
});
|
||||
}, this.model.events);
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute `fn` for every document in the cursor. If `fn` returns a promise,
|
||||
* will wait for the promise to resolve before iterating on to the next one.
|
||||
* Returns a promise that resolves when done.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @param {Object} [options]
|
||||
* @param {Number} [options.parallel] the number of promises to execute in parallel. Defaults to 1.
|
||||
* @param {Function} [callback] executed when all docs have been processed
|
||||
* @return {Promise}
|
||||
* @api public
|
||||
* @method eachAsync
|
||||
*/
|
||||
|
||||
QueryCursor.prototype.eachAsync = function(fn, opts, callback) {
|
||||
const _this = this;
|
||||
if (typeof opts === 'function') {
|
||||
callback = opts;
|
||||
opts = {};
|
||||
}
|
||||
opts = opts || {};
|
||||
|
||||
return eachAsync(function(cb) { return _next(_this, cb); }, fn, opts, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a [cursor flag](http://mongodb.github.io/node-mongodb-native/2.2/api/Cursor.html#addCursorFlag).
|
||||
* Useful for setting the `noCursorTimeout` and `tailable` flags.
|
||||
*
|
||||
* @param {String} flag
|
||||
* @param {Boolean} value
|
||||
* @return {AggregationCursor} this
|
||||
* @api public
|
||||
* @method addCursorFlag
|
||||
*/
|
||||
|
||||
QueryCursor.prototype.addCursorFlag = function(flag, value) {
|
||||
const _this = this;
|
||||
_waitForCursor(this, function() {
|
||||
_this.cursor.addCursorFlag(flag, value);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
QueryCursor.prototype.transformNull = function(val) {
|
||||
if (arguments.length === 0) {
|
||||
val = true;
|
||||
}
|
||||
this._mongooseOptions.transformNull = val;
|
||||
return this;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Get the next doc from the underlying cursor and mongooseify it
|
||||
* (populate, etc.)
|
||||
*/
|
||||
|
||||
function _next(ctx, cb) {
|
||||
let callback = cb;
|
||||
if (ctx._transforms.length) {
|
||||
callback = function(err, doc) {
|
||||
if (err || (doc === null && !ctx._mongooseOptions.transformNull)) {
|
||||
return cb(err, doc);
|
||||
}
|
||||
cb(err, ctx._transforms.reduce(function(doc, fn) {
|
||||
return fn.call(ctx, doc);
|
||||
}, doc));
|
||||
};
|
||||
}
|
||||
|
||||
if (ctx._error) {
|
||||
return process.nextTick(function() {
|
||||
callback(ctx._error);
|
||||
});
|
||||
}
|
||||
|
||||
if (ctx.cursor) {
|
||||
return ctx.cursor.next(function(error, doc) {
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
if (!doc) {
|
||||
return callback(null, null);
|
||||
}
|
||||
|
||||
const opts = ctx.query._mongooseOptions;
|
||||
if (!opts.populate) {
|
||||
return opts.lean ?
|
||||
callback(null, doc) :
|
||||
_create(ctx, doc, null, callback);
|
||||
}
|
||||
|
||||
const pop = helpers.preparePopulationOptionsMQ(ctx.query,
|
||||
ctx.query._mongooseOptions);
|
||||
pop.__noPromise = true;
|
||||
ctx.query.model.populate(doc, pop, function(err, doc) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
return opts.lean ?
|
||||
callback(null, doc) :
|
||||
_create(ctx, doc, pop, callback);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
ctx.once('cursor', function() {
|
||||
_next(ctx, cb);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function _waitForCursor(ctx, cb) {
|
||||
if (ctx.cursor) {
|
||||
return cb();
|
||||
}
|
||||
ctx.once('cursor', function() {
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
/*!
|
||||
* Convert a raw doc into a full mongoose doc.
|
||||
*/
|
||||
|
||||
function _create(ctx, doc, populatedIds, cb) {
|
||||
const instance = helpers.createModel(ctx.query.model, doc, ctx.query._fields);
|
||||
const opts = populatedIds ?
|
||||
{ populated: populatedIds } :
|
||||
undefined;
|
||||
|
||||
instance.init(doc, opts, function(err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
cb(null, instance);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = QueryCursor;
|
||||
+3506
File diff suppressed because it is too large.
Load diff
+30
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
const Document = require('./document.js');
|
||||
const BrowserDocument = require('./browserDocument.js');
|
||||
|
||||
let isBrowser = false;
|
||||
|
||||
/**
|
||||
* Returns the Document constructor for the current context
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
module.exports = function() {
|
||||
if (isBrowser) {
|
||||
return BrowserDocument;
|
||||
}
|
||||
return Document;
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
module.exports.setBrowser = function(flag) {
|
||||
isBrowser = flag;
|
||||
};
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
let driver = null;
|
||||
|
||||
module.exports.get = function() {
|
||||
return driver;
|
||||
};
|
||||
|
||||
module.exports.set = function(v) {
|
||||
driver = v;
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
|
||||
# Driver Spec
|
||||
|
||||
TODO
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = function() {};
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const Binary = require('bson').Binary;
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = exports = Binary;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = require('bson').Decimal128;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
exports.Binary = require('./binary');
|
||||
exports.Collection = function() {
|
||||
throw new Error('Cannot create a collection from browser library');
|
||||
};
|
||||
exports.Decimal128 = require('./decimal128');
|
||||
exports.ObjectId = require('./objectid');
|
||||
exports.ReadPreference = require('./ReadPreference');
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
|
||||
/*!
|
||||
* [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) ObjectId
|
||||
* @constructor NodeMongoDbObjectId
|
||||
* @see ObjectId
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ObjectId = require('bson').ObjectID;
|
||||
|
||||
/*!
|
||||
* Getter for convenience with populate, see gh-6115
|
||||
*/
|
||||
|
||||
Object.defineProperty(ObjectId.prototype, '_id', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
get: function() {
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = exports = ObjectId;
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const mongodb = require('mongodb');
|
||||
const ReadPref = mongodb.ReadPreference;
|
||||
|
||||
/*!
|
||||
* Converts arguments to ReadPrefs the driver
|
||||
* can understand.
|
||||
*
|
||||
* @param {String|Array} pref
|
||||
* @param {Array} [tags]
|
||||
*/
|
||||
|
||||
module.exports = function readPref(pref, tags) {
|
||||
if (Array.isArray(pref)) {
|
||||
tags = pref[1];
|
||||
pref = pref[0];
|
||||
}
|
||||
|
||||
if (pref instanceof ReadPref) {
|
||||
return pref;
|
||||
}
|
||||
|
||||
switch (pref) {
|
||||
case 'p':
|
||||
pref = 'primary';
|
||||
break;
|
||||
case 'pp':
|
||||
pref = 'primaryPreferred';
|
||||
break;
|
||||
case 's':
|
||||
pref = 'secondary';
|
||||
break;
|
||||
case 'sp':
|
||||
pref = 'secondaryPreferred';
|
||||
break;
|
||||
case 'n':
|
||||
pref = 'nearest';
|
||||
break;
|
||||
}
|
||||
|
||||
return new ReadPref(pref, tags);
|
||||
};
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const Binary = require('mongodb').Binary;
|
||||
|
||||
module.exports = exports = Binary;
|
||||
+352
@@ -0,0 +1,352 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const MongooseCollection = require('../../collection');
|
||||
const MongooseError = require('../../error/mongooseError');
|
||||
const Collection = require('mongodb').Collection;
|
||||
const get = require('../../helpers/get');
|
||||
const sliced = require('sliced');
|
||||
const stream = require('stream');
|
||||
const util = require('util');
|
||||
|
||||
/**
|
||||
* A [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) collection implementation.
|
||||
*
|
||||
* All methods methods from the [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) driver are copied and wrapped in queue management.
|
||||
*
|
||||
* @inherits Collection
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function NativeCollection(name, options) {
|
||||
this.collection = null;
|
||||
this.Promise = options.Promise || Promise;
|
||||
MongooseCollection.apply(this, arguments);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherit from abstract Collection.
|
||||
*/
|
||||
|
||||
NativeCollection.prototype.__proto__ = MongooseCollection.prototype;
|
||||
|
||||
/**
|
||||
* Called when the connection opens.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
NativeCollection.prototype.onOpen = function() {
|
||||
const _this = this;
|
||||
|
||||
// always get a new collection in case the user changed host:port
|
||||
// of parent db instance when re-opening the connection.
|
||||
|
||||
if (!_this.opts.capped.size) {
|
||||
// non-capped
|
||||
callback(null, _this.conn.db.collection(_this.name));
|
||||
return _this.collection;
|
||||
}
|
||||
|
||||
// capped
|
||||
return _this.conn.db.collection(_this.name, function(err, c) {
|
||||
if (err) return callback(err);
|
||||
|
||||
// discover if this collection exists and if it is capped
|
||||
_this.conn.db.listCollections({name: _this.name}).toArray(function(err, docs) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
const doc = docs[0];
|
||||
const exists = !!doc;
|
||||
|
||||
if (exists) {
|
||||
if (doc.options && doc.options.capped) {
|
||||
callback(null, c);
|
||||
} else {
|
||||
const msg = 'A non-capped collection exists with the name: ' + _this.name + '\n\n'
|
||||
+ ' To use this collection as a capped collection, please '
|
||||
+ 'first convert it.\n'
|
||||
+ ' http://www.mongodb.org/display/DOCS/Capped+Collections#CappedCollections-Convertingacollectiontocapped';
|
||||
err = new Error(msg);
|
||||
callback(err);
|
||||
}
|
||||
} else {
|
||||
// create
|
||||
const opts = Object.assign({}, _this.opts.capped);
|
||||
opts.capped = true;
|
||||
_this.conn.db.createCollection(_this.name, opts, callback);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function callback(err, collection) {
|
||||
if (err) {
|
||||
// likely a strict mode error
|
||||
_this.conn.emit('error', err);
|
||||
} else {
|
||||
_this.collection = collection;
|
||||
MongooseCollection.prototype.onOpen.call(_this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the connection closes
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
NativeCollection.prototype.onClose = function(force) {
|
||||
MongooseCollection.prototype.onClose.call(this, force);
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
const syncCollectionMethods = { watch: true };
|
||||
|
||||
/*!
|
||||
* Copy the collection methods and make them subject to queues
|
||||
*/
|
||||
|
||||
function iter(i) {
|
||||
NativeCollection.prototype[i] = function() {
|
||||
const collection = this.collection;
|
||||
const args = Array.from(arguments);
|
||||
const _this = this;
|
||||
const debug = get(_this, 'conn.base.options.debug');
|
||||
const lastArg = arguments[arguments.length - 1];
|
||||
|
||||
// If user force closed, queueing will hang forever. See #5664
|
||||
if (this.conn.$wasForceClosed) {
|
||||
const error = new MongooseError('Connection was force closed');
|
||||
if (args.length > 0 &&
|
||||
typeof args[args.length - 1] === 'function') {
|
||||
args[args.length - 1](error);
|
||||
return;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
if (this.buffer) {
|
||||
if (syncCollectionMethods[i]) {
|
||||
throw new Error('Collection method ' + i + ' is synchronous');
|
||||
}
|
||||
if (typeof lastArg === 'function') {
|
||||
this.addQueue(i, args);
|
||||
return;
|
||||
}
|
||||
return new this.Promise((resolve, reject) => {
|
||||
this.addQueue(i, [].concat(args).concat([(err, res) => {
|
||||
if (err != null) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(res);
|
||||
}]));
|
||||
});
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
if (typeof debug === 'function') {
|
||||
debug.apply(_this,
|
||||
[_this.name, i].concat(sliced(args, 0, args.length - 1)));
|
||||
} else if (debug instanceof stream.Writable) {
|
||||
this.$printToStream(_this.name, i, args, debug);
|
||||
} else {
|
||||
this.$print(_this.name, i, args, typeof debug.color === 'undefined' ? true : debug.color);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return collection[i].apply(collection, args);
|
||||
} catch (error) {
|
||||
// Collection operation may throw because of max bson size, catch it here
|
||||
// See gh-3906
|
||||
if (args.length > 0 &&
|
||||
typeof args[args.length - 1] === 'function') {
|
||||
args[args.length - 1](error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for (const i in Collection.prototype) {
|
||||
// Janky hack to work around gh-3005 until we can get rid of the mongoose
|
||||
// collection abstraction
|
||||
try {
|
||||
if (typeof Collection.prototype[i] !== 'function') {
|
||||
continue;
|
||||
}
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
iter(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug print helper
|
||||
*
|
||||
* @api public
|
||||
* @method $print
|
||||
*/
|
||||
|
||||
NativeCollection.prototype.$print = function(name, i, args, color) {
|
||||
const moduleName = color ? '\x1B[0;36mMongoose:\x1B[0m ' : 'Mongoose: ';
|
||||
const functionCall = [name, i].join('.');
|
||||
const _args = [];
|
||||
for (let j = args.length - 1; j >= 0; --j) {
|
||||
if (this.$format(args[j]) || _args.length) {
|
||||
_args.unshift(this.$format(args[j], color));
|
||||
}
|
||||
}
|
||||
const params = '(' + _args.join(', ') + ')';
|
||||
|
||||
console.info(moduleName + functionCall + params);
|
||||
};
|
||||
|
||||
/**
|
||||
* Debug print helper
|
||||
*
|
||||
* @api public
|
||||
* @method $print
|
||||
*/
|
||||
|
||||
NativeCollection.prototype.$printToStream = function(name, i, args, stream) {
|
||||
const functionCall = [name, i].join('.');
|
||||
const _args = [];
|
||||
for (let j = args.length - 1; j >= 0; --j) {
|
||||
if (this.$format(args[j]) || _args.length) {
|
||||
_args.unshift(this.$format(args[j]));
|
||||
}
|
||||
}
|
||||
const params = '(' + _args.join(', ') + ')';
|
||||
|
||||
stream.write(functionCall + params, 'utf8');
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatter for debug print args
|
||||
*
|
||||
* @api public
|
||||
* @method $format
|
||||
*/
|
||||
|
||||
NativeCollection.prototype.$format = function(arg, color) {
|
||||
const type = typeof arg;
|
||||
if (type === 'function' || type === 'undefined') return '';
|
||||
return format(arg, false, color);
|
||||
};
|
||||
|
||||
/*!
|
||||
* Debug print helper
|
||||
*/
|
||||
|
||||
function inspectable(representation) {
|
||||
const ret = {
|
||||
inspect: function() { return representation; },
|
||||
};
|
||||
if (util.inspect.custom) {
|
||||
ret[util.inspect.custom] = ret.inspect;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
function map(o) {
|
||||
return format(o, true);
|
||||
}
|
||||
function formatObjectId(x, key) {
|
||||
x[key] = inspectable('ObjectId("' + x[key].toHexString() + '")');
|
||||
}
|
||||
function formatDate(x, key) {
|
||||
x[key] = inspectable('new Date("' + x[key].toUTCString() + '")');
|
||||
}
|
||||
function format(obj, sub, color) {
|
||||
if (obj && typeof obj.toBSON === 'function') {
|
||||
obj = obj.toBSON();
|
||||
}
|
||||
if (obj == null) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
let x = require('../../utils').clone(obj, {transform: false});
|
||||
|
||||
if (x.constructor.name === 'Binary') {
|
||||
x = 'BinData(' + x.sub_type + ', "' + x.toString('base64') + '")';
|
||||
} else if (x.constructor.name === 'ObjectID') {
|
||||
x = inspectable('ObjectId("' + x.toHexString() + '")');
|
||||
} else if (x.constructor.name === 'Date') {
|
||||
x = inspectable('new Date("' + x.toUTCString() + '")');
|
||||
} else if (x.constructor.name === 'Object') {
|
||||
const keys = Object.keys(x);
|
||||
const numKeys = keys.length;
|
||||
let key;
|
||||
for (let i = 0; i < numKeys; ++i) {
|
||||
key = keys[i];
|
||||
if (x[key]) {
|
||||
let error;
|
||||
if (typeof x[key].toBSON === 'function') {
|
||||
try {
|
||||
// `session.toBSON()` throws an error. This means we throw errors
|
||||
// in debug mode when using transactions, see gh-6712. As a
|
||||
// workaround, catch `toBSON()` errors, try to serialize without
|
||||
// `toBSON()`, and rethrow if serialization still fails.
|
||||
x[key] = x[key].toBSON();
|
||||
} catch (_error) {
|
||||
error = _error;
|
||||
}
|
||||
}
|
||||
if (x[key].constructor.name === 'Binary') {
|
||||
x[key] = 'BinData(' + x[key].sub_type + ', "' +
|
||||
x[key].buffer.toString('base64') + '")';
|
||||
} else if (x[key].constructor.name === 'Object') {
|
||||
x[key] = format(x[key], true);
|
||||
} else if (x[key].constructor.name === 'ObjectID') {
|
||||
formatObjectId(x, key);
|
||||
} else if (x[key].constructor.name === 'Date') {
|
||||
formatDate(x, key);
|
||||
} else if (x[key].constructor.name === 'ClientSession') {
|
||||
x[key] = inspectable('ClientSession("' +
|
||||
get(x[key], 'id.id.buffer', '').toString('hex') + '")');
|
||||
} else if (Array.isArray(x[key])) {
|
||||
x[key] = x[key].map(map);
|
||||
} else if (error != null) {
|
||||
// If there was an error with `toBSON()` and the object wasn't
|
||||
// already converted to a string representation, rethrow it.
|
||||
// Open to better ideas on how to handle this.
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sub) {
|
||||
return x;
|
||||
}
|
||||
|
||||
return util.
|
||||
inspect(x, false, 10, color).
|
||||
replace(/\n/g, '').
|
||||
replace(/\s{2,}/g, ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves information about this collections indexes.
|
||||
*
|
||||
* @param {Function} callback
|
||||
* @method getIndexes
|
||||
* @api public
|
||||
*/
|
||||
|
||||
NativeCollection.prototype.getIndexes = NativeCollection.prototype.indexInformation;
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = NativeCollection;
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const MongooseConnection = require('../../connection');
|
||||
const STATES = require('../../connectionstate');
|
||||
|
||||
/**
|
||||
* A [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) connection implementation.
|
||||
*
|
||||
* @inherits Connection
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function NativeConnection() {
|
||||
MongooseConnection.apply(this, arguments);
|
||||
this._listening = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose the possible connection states.
|
||||
* @api public
|
||||
*/
|
||||
|
||||
NativeConnection.STATES = STATES;
|
||||
|
||||
/*!
|
||||
* Inherits from Connection.
|
||||
*/
|
||||
|
||||
NativeConnection.prototype.__proto__ = MongooseConnection.prototype;
|
||||
|
||||
/**
|
||||
* Switches to a different database using the same connection pool.
|
||||
*
|
||||
* Returns a new connection object, with the new db. If you set the `useCache`
|
||||
* option, `useDb()` will cache connections by `name`.
|
||||
*
|
||||
* @param {String} name The database name
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.useCache=false] If true, cache results so calling `useDb()` multiple times with the same name only creates 1 connection object.
|
||||
* @return {Connection} New Connection Object
|
||||
* @api public
|
||||
*/
|
||||
|
||||
NativeConnection.prototype.useDb = function(name, options) {
|
||||
// Return immediately if cached
|
||||
if (options && options.useCache && this.relatedDbs[name]) {
|
||||
return this.relatedDbs[name];
|
||||
}
|
||||
|
||||
// we have to manually copy all of the attributes...
|
||||
const newConn = new this.constructor();
|
||||
newConn.name = name;
|
||||
newConn.base = this.base;
|
||||
newConn.collections = {};
|
||||
newConn.models = {};
|
||||
newConn.replica = this.replica;
|
||||
newConn.name = this.name;
|
||||
newConn.options = this.options;
|
||||
newConn._readyState = this._readyState;
|
||||
newConn._closeCalled = this._closeCalled;
|
||||
newConn._hasOpened = this._hasOpened;
|
||||
newConn._listening = false;
|
||||
|
||||
newConn.host = this.host;
|
||||
newConn.port = this.port;
|
||||
newConn.user = this.user;
|
||||
newConn.pass = this.pass;
|
||||
|
||||
// First, when we create another db object, we are not guaranteed to have a
|
||||
// db object to work with. So, in the case where we have a db object and it
|
||||
// is connected, we can just proceed with setting everything up. However, if
|
||||
// we do not have a db or the state is not connected, then we need to wait on
|
||||
// the 'open' event of the connection before doing the rest of the setup
|
||||
// the 'connected' event is the first time we'll have access to the db object
|
||||
|
||||
const _this = this;
|
||||
|
||||
newConn.client = _this.client;
|
||||
|
||||
if (this.db && this._readyState === STATES.connected) {
|
||||
wireup();
|
||||
} else {
|
||||
this.once('connected', wireup);
|
||||
}
|
||||
|
||||
function wireup() {
|
||||
newConn.client = _this.client;
|
||||
newConn.db = _this.client.db(name);
|
||||
newConn.onOpen();
|
||||
// setup the events appropriately
|
||||
listen(newConn);
|
||||
}
|
||||
|
||||
newConn.name = name;
|
||||
|
||||
// push onto the otherDbs stack, this is used when state changes
|
||||
this.otherDbs.push(newConn);
|
||||
newConn.otherDbs.push(this);
|
||||
|
||||
// push onto the relatedDbs cache, this is used when state changes
|
||||
if (options && options.useCache) {
|
||||
this.relatedDbs[newConn.name] = newConn;
|
||||
newConn.relatedDbs = this.relatedDbs;
|
||||
}
|
||||
|
||||
return newConn;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Register listeners for important events and bubble appropriately.
|
||||
*/
|
||||
|
||||
function listen(conn) {
|
||||
if (conn.db._listening) {
|
||||
return;
|
||||
}
|
||||
conn.db._listening = true;
|
||||
|
||||
conn.db.on('close', function(force) {
|
||||
if (conn._closeCalled) return;
|
||||
|
||||
// the driver never emits an `open` event. auto_reconnect still
|
||||
// emits a `close` event but since we never get another
|
||||
// `open` we can't emit close
|
||||
if (conn.db.serverConfig.autoReconnect) {
|
||||
conn.readyState = STATES.disconnected;
|
||||
conn.emit('close');
|
||||
return;
|
||||
}
|
||||
conn.onClose(force);
|
||||
});
|
||||
conn.db.on('error', function(err) {
|
||||
conn.emit('error', err);
|
||||
});
|
||||
conn.db.on('reconnect', function() {
|
||||
conn.readyState = STATES.connected;
|
||||
conn.emit('reconnect');
|
||||
conn.emit('reconnected');
|
||||
conn.onOpen();
|
||||
});
|
||||
conn.db.on('timeout', function(err) {
|
||||
conn.emit('timeout', err);
|
||||
});
|
||||
conn.db.on('open', function(err, db) {
|
||||
if (STATES.disconnected === conn.readyState && db && db.databaseName) {
|
||||
conn.readyState = STATES.connected;
|
||||
conn.emit('reconnect');
|
||||
conn.emit('reconnected');
|
||||
}
|
||||
});
|
||||
conn.db.on('parseError', function(err) {
|
||||
conn.emit('parseError', err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection
|
||||
*
|
||||
* @param {Boolean} [force]
|
||||
* @param {Function} [fn]
|
||||
* @return {Connection} this
|
||||
* @api private
|
||||
*/
|
||||
|
||||
NativeConnection.prototype.doClose = function(force, fn) {
|
||||
this.client.close(force, (err, res) => {
|
||||
// Defer because the driver will wait at least 1ms before finishing closing
|
||||
// the pool, see https://github.com/mongodb-js/mongodb-core/blob/a8f8e4ce41936babc3b9112bf42d609779f03b39/lib/connection/pool.js#L1026-L1030.
|
||||
// If there's queued operations, you may still get some background work
|
||||
// after the callback is called.
|
||||
setTimeout(() => fn(err, res), 1);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = NativeConnection;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = require('mongodb').Decimal128;
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
exports.Binary = require('./binary');
|
||||
exports.Collection = require('./collection');
|
||||
exports.Decimal128 = require('./decimal128');
|
||||
exports.ObjectId = require('./objectid');
|
||||
exports.ReadPreference = require('./ReadPreference');
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
|
||||
/*!
|
||||
* [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) ObjectId
|
||||
* @constructor NodeMongoDbObjectId
|
||||
* @see ObjectId
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ObjectId = require('mongodb').ObjectId;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = exports = ObjectId;
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const MongooseError = require('./');
|
||||
|
||||
/*!
|
||||
* MissingSchema Error constructor.
|
||||
*
|
||||
* @inherits MongooseError
|
||||
*/
|
||||
|
||||
function MissingSchemaError() {
|
||||
const msg = 'Schema hasn\'t been registered for document.\n'
|
||||
+ 'Use mongoose.Document(name, schema)';
|
||||
MongooseError.call(this, msg);
|
||||
this.name = 'MissingSchemaError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
MissingSchemaError.prototype = Object.create(MongooseError.prototype);
|
||||
MissingSchemaError.prototype.constructor = MongooseError;
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = MissingSchemaError;
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const MongooseError = require('./mongooseError');
|
||||
const util = require('util');
|
||||
|
||||
/**
|
||||
* Casting Error constructor.
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {String} value
|
||||
* @inherits MongooseError
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function CastError(type, value, path, reason) {
|
||||
let stringValue = util.inspect(value);
|
||||
stringValue = stringValue.replace(/^'/, '"').replace(/'$/, '"');
|
||||
if (!stringValue.startsWith('"')) {
|
||||
stringValue = '"' + stringValue + '"';
|
||||
}
|
||||
MongooseError.call(this, 'Cast to ' + type + ' failed for value ' +
|
||||
stringValue + ' at path "' + path + '"');
|
||||
this.name = 'CastError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
this.stringValue = stringValue;
|
||||
this.kind = type;
|
||||
this.value = value;
|
||||
this.path = path;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
CastError.prototype = Object.create(MongooseError.prototype);
|
||||
CastError.prototype.constructor = MongooseError;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
CastError.prototype.setModel = function(model) {
|
||||
this.model = model;
|
||||
this.message = 'Cast to ' + this.kind + ' failed for value ' +
|
||||
this.stringValue + ' at path "' + this.path + '"' + ' for model "' +
|
||||
model.modelName + '"';
|
||||
};
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = CastError;
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const MongooseError = require('./');
|
||||
|
||||
/**
|
||||
* The connection failed to reconnect and will never successfully reconnect to
|
||||
* MongoDB without manual intervention.
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {String} value
|
||||
* @inherits MongooseError
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function DisconnectedError(connectionString) {
|
||||
MongooseError.call(this, 'Ran out of retries trying to reconnect to "' +
|
||||
connectionString + '". Try setting `server.reconnectTries` and ' +
|
||||
'`server.reconnectInterval` to something higher.');
|
||||
this.name = 'DisconnectedError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
DisconnectedError.prototype = Object.create(MongooseError.prototype);
|
||||
DisconnectedError.prototype.constructor = MongooseError;
|
||||
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = DisconnectedError;
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const MongooseError = require('./');
|
||||
|
||||
/*!
|
||||
* DivergentArrayError constructor.
|
||||
*
|
||||
* @inherits MongooseError
|
||||
*/
|
||||
|
||||
function DivergentArrayError(paths) {
|
||||
const msg = 'For your own good, using `document.save()` to update an array '
|
||||
+ 'which was selected using an $elemMatch projection OR '
|
||||
+ 'populated using skip, limit, query conditions, or exclusion of '
|
||||
+ 'the _id field when the operation results in a $pop or $set of '
|
||||
+ 'the entire array is not supported. The following '
|
||||
+ 'path(s) would have been modified unsafely:\n'
|
||||
+ ' ' + paths.join('\n ') + '\n'
|
||||
+ 'Use Model.update() to update these arrays instead.';
|
||||
// TODO write up a docs page (FAQ) and link to it
|
||||
|
||||
MongooseError.call(this, msg);
|
||||
this.name = 'DivergentArrayError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
DivergentArrayError.prototype = Object.create(MongooseError.prototype);
|
||||
DivergentArrayError.prototype.constructor = MongooseError;
|
||||
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = DivergentArrayError;
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* MongooseError constructor. MongooseError is the base class for all
|
||||
* Mongoose-specific errors.
|
||||
*
|
||||
* ####Example:
|
||||
* const Model = mongoose.model('Test', new Schema({ answer: Number }));
|
||||
* const doc = new Model({ answer: 'not a number' });
|
||||
* const err = doc.validateSync();
|
||||
*
|
||||
* err instanceof mongoose.Error; // true
|
||||
*
|
||||
* @constructor Error
|
||||
* @param {String} msg Error message
|
||||
* @inherits Error https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error
|
||||
*/
|
||||
|
||||
const MongooseError = require('./mongooseError');
|
||||
|
||||
/**
|
||||
* The name of the error. The name uniquely identifies this Mongoose error. The
|
||||
* possible values are:
|
||||
*
|
||||
* - `MongooseError`: general Mongoose error
|
||||
* - `CastError`: Mongoose could not convert a value to the type defined in the schema path. May be in a `ValidationError` class' `errors` property.
|
||||
* - `DisconnectedError`: This [connection](connections.html) timed out in trying to reconnect to MongoDB and will not successfully reconnect to MongoDB unless you explicitly reconnect.
|
||||
* - `DivergentArrayError`: You attempted to `save()` an array that was modified after you loaded it with a `$elemMatch` or similar projection
|
||||
* - `MissingSchemaError`: You tried to access a model with [`mongoose.model()`](api.html#mongoose_Mongoose-model) that was not defined
|
||||
* - `DocumentNotFoundError`: The document you tried to [`save()`](api.html#document_Document-save) was not found
|
||||
* - `ValidatorError`: error from an individual schema path's validator
|
||||
* - `ValidationError`: error returned from [`validate()`](api.html#document_Document-validate) or [`validateSync()`](api.html#document_Document-validateSync). Contains zero or more `ValidatorError` instances in `.errors` property.
|
||||
* - `MissingSchemaError`: You called `mongoose.Document()` without a schema
|
||||
* - `ObjectExpectedError`: Thrown when you set a nested path to a non-object value with [strict mode set](guide.html#strict).
|
||||
* - `ObjectParameterError`: Thrown when you pass a non-object value to a function which expects an object as a paramter
|
||||
* - `OverwriteModelError`: Thrown when you call [`mongoose.model()`](api.html#mongoose_Mongoose-model) to re-define a model that was already defined.
|
||||
* - `ParallelSaveError`: Thrown when you call [`save()`](api.html#model_Model-save) on a document when the same document instance is already saving.
|
||||
* - `StrictModeError`: Thrown when you set a path that isn't the schema and [strict mode](guide.html#strict) is set to `throw`.
|
||||
* - `VersionError`: Thrown when the [document is out of sync](guide.html#versionKey)
|
||||
*
|
||||
* @api public
|
||||
* @property {String} name
|
||||
* @memberOf Error
|
||||
* @instance
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = exports = MongooseError;
|
||||
|
||||
/**
|
||||
* The default built-in validator error messages.
|
||||
*
|
||||
* @see Error.messages #error_messages_MongooseError-messages
|
||||
* @api public
|
||||
* @memberOf Error
|
||||
* @static messages
|
||||
*/
|
||||
|
||||
MongooseError.messages = require('./messages');
|
||||
|
||||
// backward compat
|
||||
MongooseError.Messages = MongooseError.messages;
|
||||
|
||||
/**
|
||||
* An instance of this error class will be returned when `save()` fails
|
||||
* because the underlying
|
||||
* document was not found. The constructor takes one parameter, the
|
||||
* conditions that mongoose passed to `update()` when trying to update
|
||||
* the document.
|
||||
*
|
||||
* @api public
|
||||
* @memberOf Error
|
||||
* @static DocumentNotFoundError
|
||||
*/
|
||||
|
||||
MongooseError.DocumentNotFoundError = require('./notFound');
|
||||
|
||||
/**
|
||||
* An instance of this error class will be returned when mongoose failed to
|
||||
* cast a value.
|
||||
*
|
||||
* @api public
|
||||
* @memberOf Error
|
||||
* @static CastError
|
||||
*/
|
||||
|
||||
MongooseError.CastError = require('./cast');
|
||||
|
||||
/**
|
||||
* An instance of this error class will be returned when [validation](/docs/validation.html) failed.
|
||||
* The `errors` property contains an object whose keys are the paths that failed and whose values are
|
||||
* instances of CastError or ValidationError.
|
||||
*
|
||||
* @api public
|
||||
* @memberOf Error
|
||||
* @static ValidationError
|
||||
*/
|
||||
|
||||
MongooseError.ValidationError = require('./validation');
|
||||
|
||||
/**
|
||||
* A `ValidationError` has a hash of `errors` that contain individual `ValidatorError` instances
|
||||
*
|
||||
* @api public
|
||||
* @memberOf Error
|
||||
* @static ValidatorError
|
||||
*/
|
||||
|
||||
MongooseError.ValidatorError = require('./validator');
|
||||
|
||||
/**
|
||||
* An instance of this error class will be returned when you call `save()` after
|
||||
* the document in the database was changed in a potentially unsafe way. See
|
||||
* the [`versionKey` option](/docs/guide.html#versionKey) for more information.
|
||||
*
|
||||
* @api public
|
||||
* @memberOf Error
|
||||
* @static VersionError
|
||||
*/
|
||||
|
||||
MongooseError.VersionError = require('./version');
|
||||
|
||||
/**
|
||||
* An instance of this error class will be returned when you call `save()` multiple
|
||||
* times on the same document in parallel. See the [FAQ](/docs/faq.html) for more
|
||||
* information.
|
||||
*
|
||||
* @api public
|
||||
* @memberOf Error
|
||||
* @static ParallelSaveError
|
||||
*/
|
||||
|
||||
MongooseError.ParallelSaveError = require('./parallelSave');
|
||||
|
||||
/**
|
||||
* Thrown when a model with the given name was already registered on the connection.
|
||||
* See [the FAQ about `OverwriteModelError`](/docs/faq.html#overwrite-model-error).
|
||||
*
|
||||
* @api public
|
||||
* @memberOf Error
|
||||
* @static OverwriteModelError
|
||||
*/
|
||||
|
||||
MongooseError.OverwriteModelError = require('./overwriteModel');
|
||||
|
||||
/**
|
||||
* Thrown when you try to access a model that has not been registered yet
|
||||
*
|
||||
* @api public
|
||||
* @memberOf Error
|
||||
* @static MissingSchemaError
|
||||
*/
|
||||
|
||||
MongooseError.MissingSchemaError = require('./missingSchema');
|
||||
|
||||
/**
|
||||
* An instance of this error will be returned if you used an array projection
|
||||
* and then modified the array in an unsafe way.
|
||||
*
|
||||
* @api public
|
||||
* @memberOf Error
|
||||
* @static DivergentArrayError
|
||||
*/
|
||||
|
||||
MongooseError.DivergentArrayError = require('./divergentArray');
|
||||
|
||||
/**
|
||||
* Thrown when your try to pass values to model contrtuctor that
|
||||
* were not specified in schema or change immutable properties when
|
||||
* `strict` mode is `"throw"`
|
||||
*
|
||||
* @api public
|
||||
* @memberOf Error
|
||||
* @static StrictModeError
|
||||
*/
|
||||
|
||||
MongooseError.StrictModeError = require('./strict');
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
|
||||
/**
|
||||
* The default built-in validator error messages. These may be customized.
|
||||
*
|
||||
* // customize within each schema or globally like so
|
||||
* var mongoose = require('mongoose');
|
||||
* mongoose.Error.messages.String.enum = "Your custom message for {PATH}.";
|
||||
*
|
||||
* As you might have noticed, error messages support basic templating
|
||||
*
|
||||
* - `{PATH}` is replaced with the invalid document path
|
||||
* - `{VALUE}` is replaced with the invalid value
|
||||
* - `{TYPE}` is replaced with the validator type such as "regexp", "min", or "user defined"
|
||||
* - `{MIN}` is replaced with the declared min value for the Number.min validator
|
||||
* - `{MAX}` is replaced with the declared max value for the Number.max validator
|
||||
*
|
||||
* Click the "show code" link below to see all defaults.
|
||||
*
|
||||
* @static messages
|
||||
* @receiver MongooseError
|
||||
* @api public
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const msg = module.exports = exports = {};
|
||||
|
||||
msg.DocumentNotFoundError = null;
|
||||
|
||||
msg.general = {};
|
||||
msg.general.default = 'Validator failed for path `{PATH}` with value `{VALUE}`';
|
||||
msg.general.required = 'Path `{PATH}` is required.';
|
||||
|
||||
msg.Number = {};
|
||||
msg.Number.min = 'Path `{PATH}` ({VALUE}) is less than minimum allowed value ({MIN}).';
|
||||
msg.Number.max = 'Path `{PATH}` ({VALUE}) is more than maximum allowed value ({MAX}).';
|
||||
|
||||
msg.Date = {};
|
||||
msg.Date.min = 'Path `{PATH}` ({VALUE}) is before minimum allowed value ({MIN}).';
|
||||
msg.Date.max = 'Path `{PATH}` ({VALUE}) is after maximum allowed value ({MAX}).';
|
||||
|
||||
msg.String = {};
|
||||
msg.String.enum = '`{VALUE}` is not a valid enum value for path `{PATH}`.';
|
||||
msg.String.match = 'Path `{PATH}` is invalid ({VALUE}).';
|
||||
msg.String.minlength = 'Path `{PATH}` (`{VALUE}`) is shorter than the minimum allowed length ({MINLENGTH}).';
|
||||
msg.String.maxlength = 'Path `{PATH}` (`{VALUE}`) is longer than the maximum allowed length ({MAXLENGTH}).';
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const MongooseError = require('./');
|
||||
|
||||
/*!
|
||||
* MissingSchema Error constructor.
|
||||
*
|
||||
* @inherits MongooseError
|
||||
*/
|
||||
|
||||
function MissingSchemaError(name) {
|
||||
const msg = 'Schema hasn\'t been registered for model "' + name + '".\n'
|
||||
+ 'Use mongoose.model(name, schema)';
|
||||
MongooseError.call(this, msg);
|
||||
this.name = 'MissingSchemaError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
MissingSchemaError.prototype = Object.create(MongooseError.prototype);
|
||||
MissingSchemaError.prototype.constructor = MongooseError;
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = MissingSchemaError;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function MongooseError(msg) {
|
||||
Error.call(this);
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
this.message = msg;
|
||||
this.name = 'MongooseError';
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from Error.
|
||||
*/
|
||||
|
||||
MongooseError.prototype = Object.create(Error.prototype);
|
||||
MongooseError.prototype.constructor = Error;
|
||||
|
||||
module.exports = MongooseError;
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const MongooseError = require('./');
|
||||
const util = require('util');
|
||||
|
||||
/*!
|
||||
* OverwriteModel Error constructor.
|
||||
*
|
||||
* @inherits MongooseError
|
||||
*/
|
||||
|
||||
function DocumentNotFoundError(filter, model, numAffected, result) {
|
||||
let msg;
|
||||
const messages = MongooseError.messages;
|
||||
if (messages.DocumentNotFoundError != null) {
|
||||
msg = typeof messages.DocumentNotFoundError === 'function' ?
|
||||
messages.DocumentNotFoundError(filter, model) :
|
||||
messages.DocumentNotFoundError;
|
||||
} else {
|
||||
msg = 'No document found for query "' + util.inspect(filter) +
|
||||
'" on model "' + model + '"';
|
||||
}
|
||||
|
||||
MongooseError.call(this, msg);
|
||||
|
||||
this.name = 'DocumentNotFoundError';
|
||||
this.result = result;
|
||||
this.numAffected = numAffected;
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
|
||||
this.filter = filter;
|
||||
// Backwards compat
|
||||
this.query = filter;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
DocumentNotFoundError.prototype = Object.create(MongooseError.prototype);
|
||||
DocumentNotFoundError.prototype.constructor = MongooseError;
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = DocumentNotFoundError;
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const MongooseError = require('./');
|
||||
|
||||
/**
|
||||
* Strict mode error constructor
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {String} value
|
||||
* @inherits MongooseError
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function ObjectExpectedError(path, val) {
|
||||
const typeDescription = Array.isArray(val) ? 'array' : 'primitive value';
|
||||
MongooseError.call(this, 'Tried to set nested object field `' + path +
|
||||
`\` to ${typeDescription} \`` + val + '` and strict mode is set to throw.');
|
||||
this.name = 'ObjectExpectedError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
ObjectExpectedError.prototype = Object.create(MongooseError.prototype);
|
||||
ObjectExpectedError.prototype.constructor = MongooseError;
|
||||
|
||||
module.exports = ObjectExpectedError;
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const MongooseError = require('./');
|
||||
|
||||
/**
|
||||
* Constructor for errors that happen when a parameter that's expected to be
|
||||
* an object isn't an object
|
||||
*
|
||||
* @param {Any} value
|
||||
* @param {String} paramName
|
||||
* @param {String} fnName
|
||||
* @inherits MongooseError
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function ObjectParameterError(value, paramName, fnName) {
|
||||
MongooseError.call(this, 'Parameter "' + paramName + '" to ' + fnName +
|
||||
'() must be an object, got ' + value.toString());
|
||||
this.name = 'ObjectParameterError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
ObjectParameterError.prototype = Object.create(MongooseError.prototype);
|
||||
ObjectParameterError.prototype.constructor = MongooseError;
|
||||
|
||||
module.exports = ObjectParameterError;
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const MongooseError = require('./');
|
||||
|
||||
/*!
|
||||
* OverwriteModel Error constructor.
|
||||
*
|
||||
* @inherits MongooseError
|
||||
*/
|
||||
|
||||
function OverwriteModelError(name) {
|
||||
MongooseError.call(this, 'Cannot overwrite `' + name + '` model once compiled.');
|
||||
this.name = 'OverwriteModelError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
OverwriteModelError.prototype = Object.create(MongooseError.prototype);
|
||||
OverwriteModelError.prototype.constructor = MongooseError;
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = OverwriteModelError;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const MongooseError = require('./');
|
||||
|
||||
/**
|
||||
* ParallelSave Error constructor.
|
||||
*
|
||||
* @inherits MongooseError
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function ParallelSaveError(doc) {
|
||||
const msg = 'Can\'t save() the same doc multiple times in parallel. Document: ';
|
||||
MongooseError.call(this, msg + doc.id);
|
||||
this.name = 'ParallelSaveError';
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
ParallelSaveError.prototype = Object.create(MongooseError.prototype);
|
||||
ParallelSaveError.prototype.constructor = MongooseError;
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = ParallelSaveError;
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const MongooseError = require('./');
|
||||
|
||||
/**
|
||||
* Strict mode error constructor
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {String} value
|
||||
* @inherits MongooseError
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function StrictModeError(path, msg, immutable) {
|
||||
msg = msg || 'Field `' + path + '` is not in schema and strict ' +
|
||||
'mode is set to throw.';
|
||||
MongooseError.call(this, msg);
|
||||
this.name = 'StrictModeError';
|
||||
this.isImmutableError = !!immutable;
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
StrictModeError.prototype = Object.create(MongooseError.prototype);
|
||||
StrictModeError.prototype.constructor = MongooseError;
|
||||
|
||||
module.exports = StrictModeError;
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*!
|
||||
* Module requirements
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const MongooseError = require('./');
|
||||
const util = require('util');
|
||||
|
||||
/**
|
||||
* Document Validation Error
|
||||
*
|
||||
* @api private
|
||||
* @param {Document} instance
|
||||
* @inherits MongooseError
|
||||
*/
|
||||
|
||||
function ValidationError(instance) {
|
||||
this.errors = {};
|
||||
this._message = '';
|
||||
|
||||
MongooseError.call(this, this._message);
|
||||
if (instance && instance.constructor.name === 'model') {
|
||||
this._message = instance.constructor.modelName + ' validation failed';
|
||||
} else {
|
||||
this._message = 'Validation failed';
|
||||
}
|
||||
this.name = 'ValidationError';
|
||||
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
|
||||
if (instance) {
|
||||
instance.errors = this.errors;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
ValidationError.prototype = Object.create(MongooseError.prototype);
|
||||
ValidationError.prototype.constructor = MongooseError;
|
||||
|
||||
/**
|
||||
* Console.log helper
|
||||
*/
|
||||
|
||||
ValidationError.prototype.toString = function() {
|
||||
return this.name + ': ' + _generateMessage(this);
|
||||
};
|
||||
|
||||
/*!
|
||||
* inspect helper
|
||||
*/
|
||||
|
||||
ValidationError.prototype.inspect = function() {
|
||||
return Object.assign(new Error(this.message), this);
|
||||
};
|
||||
|
||||
if (util.inspect.custom) {
|
||||
/*!
|
||||
* Avoid Node deprecation warning DEP0079
|
||||
*/
|
||||
|
||||
ValidationError.prototype[util.inspect.custom] = ValidationError.prototype.inspect;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Helper for JSON.stringify
|
||||
*/
|
||||
|
||||
ValidationError.prototype.toJSON = function() {
|
||||
return Object.assign({}, this, { message: this.message });
|
||||
};
|
||||
|
||||
/*!
|
||||
* add message
|
||||
*/
|
||||
|
||||
ValidationError.prototype.addError = function(path, error) {
|
||||
this.errors[path] = error;
|
||||
this.message = this._message + ': ' + _generateMessage(this);
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function _generateMessage(err) {
|
||||
const keys = Object.keys(err.errors || {});
|
||||
const len = keys.length;
|
||||
const msgs = [];
|
||||
let key;
|
||||
|
||||
for (let i = 0; i < len; ++i) {
|
||||
key = keys[i];
|
||||
if (err === err.errors[key]) {
|
||||
continue;
|
||||
}
|
||||
msgs.push(key + ': ' + err.errors[key].message);
|
||||
}
|
||||
|
||||
return msgs.join(', ');
|
||||
}
|
||||
|
||||
/*!
|
||||
* Module exports
|
||||
*/
|
||||
|
||||
module.exports = exports = ValidationError;
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const MongooseError = require('./');
|
||||
|
||||
/**
|
||||
* Schema validator error
|
||||
*
|
||||
* @param {Object} properties
|
||||
* @inherits MongooseError
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function ValidatorError(properties) {
|
||||
let msg = properties.message;
|
||||
if (!msg) {
|
||||
msg = MongooseError.messages.general.default;
|
||||
}
|
||||
|
||||
const message = this.formatMessage(msg, properties);
|
||||
MongooseError.call(this, message);
|
||||
|
||||
properties = Object.assign({}, properties, { message: message });
|
||||
this.name = 'ValidatorError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
this.properties = properties;
|
||||
this.kind = properties.type;
|
||||
this.path = properties.path;
|
||||
this.value = properties.value;
|
||||
this.reason = properties.reason;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError
|
||||
*/
|
||||
|
||||
ValidatorError.prototype = Object.create(MongooseError.prototype);
|
||||
ValidatorError.prototype.constructor = MongooseError;
|
||||
|
||||
/*!
|
||||
* The object used to define this validator. Not enumerable to hide
|
||||
* it from `require('util').inspect()` output re: gh-3925
|
||||
*/
|
||||
|
||||
Object.defineProperty(ValidatorError.prototype, 'properties', {
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: null
|
||||
});
|
||||
|
||||
/*!
|
||||
* Formats error messages
|
||||
*/
|
||||
|
||||
ValidatorError.prototype.formatMessage = function(msg, properties) {
|
||||
if (typeof msg === 'function') {
|
||||
return msg(properties);
|
||||
}
|
||||
const propertyNames = Object.keys(properties);
|
||||
for (let i = 0; i < propertyNames.length; ++i) {
|
||||
const propertyName = propertyNames[i];
|
||||
if (propertyName === 'message') {
|
||||
continue;
|
||||
}
|
||||
msg = msg.replace('{' + propertyName.toUpperCase() + '}', properties[propertyName]);
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
|
||||
/*!
|
||||
* toString helper
|
||||
*/
|
||||
|
||||
ValidatorError.prototype.toString = function() {
|
||||
return this.message;
|
||||
};
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = ValidatorError;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const MongooseError = require('./');
|
||||
|
||||
/**
|
||||
* Version Error constructor.
|
||||
*
|
||||
* @inherits MongooseError
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function VersionError(doc, currentVersion, modifiedPaths) {
|
||||
const modifiedPathsStr = modifiedPaths.join(', ');
|
||||
MongooseError.call(this, 'No matching document found for id "' + doc._id +
|
||||
'" version ' + currentVersion + ' modifiedPaths "' + modifiedPathsStr + '"');
|
||||
this.name = 'VersionError';
|
||||
this.version = currentVersion;
|
||||
this.modifiedPaths = modifiedPaths;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
VersionError.prototype = Object.create(MongooseError.prototype);
|
||||
VersionError.prototype.constructor = MongooseError;
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = VersionError;
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const Decimal128 = require('../types/decimal128');
|
||||
const ObjectId = require('../types/objectid');
|
||||
const utils = require('../utils');
|
||||
|
||||
exports.flatten = flatten;
|
||||
exports.modifiedPaths = modifiedPaths;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function flatten(update, path, options, schema) {
|
||||
let keys;
|
||||
if (update && utils.isMongooseObject(update) && !Buffer.isBuffer(update)) {
|
||||
keys = Object.keys(update.toObject({ transform: false, virtuals: false }));
|
||||
} else {
|
||||
keys = Object.keys(update || {});
|
||||
}
|
||||
|
||||
const numKeys = keys.length;
|
||||
const result = {};
|
||||
path = path ? path + '.' : '';
|
||||
|
||||
for (let i = 0; i < numKeys; ++i) {
|
||||
const key = keys[i];
|
||||
const val = update[key];
|
||||
result[path + key] = val;
|
||||
|
||||
// Avoid going into mixed paths if schema is specified
|
||||
const keySchema = schema && schema.path && schema.path(path + key);
|
||||
if (keySchema && keySchema.instance === 'Mixed') continue;
|
||||
|
||||
if (shouldFlatten(val)) {
|
||||
if (options && options.skipArrays && Array.isArray(val)) {
|
||||
continue;
|
||||
}
|
||||
const flat = flatten(val, path + key, options, schema);
|
||||
for (const k in flat) {
|
||||
result[k] = flat[k];
|
||||
}
|
||||
if (Array.isArray(val)) {
|
||||
result[path + key] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function modifiedPaths(update, path, result) {
|
||||
const keys = Object.keys(update || {});
|
||||
const numKeys = keys.length;
|
||||
result = result || {};
|
||||
path = path ? path + '.' : '';
|
||||
|
||||
for (let i = 0; i < numKeys; ++i) {
|
||||
const key = keys[i];
|
||||
let val = update[key];
|
||||
|
||||
result[path + key] = true;
|
||||
if (utils.isMongooseObject(val) && !Buffer.isBuffer(val)) {
|
||||
val = val.toObject({ transform: false, virtuals: false });
|
||||
}
|
||||
if (shouldFlatten(val)) {
|
||||
modifiedPaths(val, path + key, result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function shouldFlatten(val) {
|
||||
return val &&
|
||||
typeof val === 'object' &&
|
||||
!(val instanceof Date) &&
|
||||
!(val instanceof ObjectId) &&
|
||||
(!Array.isArray(val) || val.length > 0) &&
|
||||
!(val instanceof Buffer) &&
|
||||
!(val instanceof Decimal128);
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const utils = require('../../utils');
|
||||
|
||||
/**
|
||||
* Execute `fn` for every document in the cursor. If `fn` returns a promise,
|
||||
* will wait for the promise to resolve before iterating on to the next one.
|
||||
* Returns a promise that resolves when done.
|
||||
*
|
||||
* @param {Function} next the thunk to call to get the next document
|
||||
* @param {Function} fn
|
||||
* @param {Object} options
|
||||
* @param {Function} [callback] executed when all docs have been processed
|
||||
* @return {Promise}
|
||||
* @api public
|
||||
* @method eachAsync
|
||||
*/
|
||||
|
||||
module.exports = function eachAsync(next, fn, options, callback) {
|
||||
const parallel = options.parallel || 1;
|
||||
|
||||
const handleNextResult = function(doc, callback) {
|
||||
const promise = fn(doc);
|
||||
if (promise && typeof promise.then === 'function') {
|
||||
promise.then(
|
||||
function() { callback(null); },
|
||||
function(error) { callback(error || new Error('`eachAsync()` promise rejected without error')); });
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
};
|
||||
|
||||
const iterate = function(callback) {
|
||||
let drained = false;
|
||||
|
||||
const getAndRun = function(cb) {
|
||||
_next(function(err, doc) {
|
||||
if (err) return cb(err);
|
||||
if (drained) {
|
||||
return;
|
||||
}
|
||||
if (doc == null) {
|
||||
drained = true;
|
||||
return callback(null);
|
||||
}
|
||||
handleNextResult(doc, function(err) {
|
||||
if (err) return cb(err);
|
||||
// Make sure to clear the stack re: gh-4697
|
||||
setTimeout(function() {
|
||||
getAndRun(cb);
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
let error = null;
|
||||
for (let i = 0; i < parallel; ++i) {
|
||||
getAndRun(err => {
|
||||
if (error != null) {
|
||||
return;
|
||||
}
|
||||
if (err != null) {
|
||||
error = err;
|
||||
return callback(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const _nextQueue = [];
|
||||
return utils.promiseOrCallback(callback, cb => {
|
||||
iterate(cb);
|
||||
});
|
||||
|
||||
// `next()` can only execute one at a time, so make sure we always execute
|
||||
// `next()` in series, while still allowing multiple `fn()` instances to run
|
||||
// in parallel.
|
||||
function _next(cb) {
|
||||
if (_nextQueue.length === 0) {
|
||||
return next(_step(cb));
|
||||
}
|
||||
_nextQueue.push(cb);
|
||||
}
|
||||
|
||||
function _step(cb) {
|
||||
return function(err, doc) {
|
||||
if (err != null) {
|
||||
return cb(err);
|
||||
}
|
||||
cb(null, doc);
|
||||
|
||||
if (doc == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (_nextQueue.length > 0) {
|
||||
next(_step(_nextQueue.unshift()));
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
}
|
||||
};
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function checkEmbeddedDiscriminatorKeyProjection(userProjection, path, schema, selected, addedPaths) {
|
||||
const userProjectedInPath = Object.keys(userProjection).
|
||||
reduce((cur, key) => cur || key.startsWith(path + '.'), false);
|
||||
const _discriminatorKey = path + '.' + schema.options.discriminatorKey;
|
||||
if (!userProjectedInPath &&
|
||||
addedPaths.length === 1 &&
|
||||
addedPaths[0] === _discriminatorKey) {
|
||||
selected.splice(selected.indexOf(_discriminatorKey), 1);
|
||||
}
|
||||
};
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
const getDiscriminatorByValue = require('../../queryhelpers').getDiscriminatorByValue;
|
||||
|
||||
/*!
|
||||
* Find the correct constructor, taking into account discriminators
|
||||
*/
|
||||
|
||||
module.exports = function getConstructor(Constructor, value) {
|
||||
const discriminatorKey = Constructor.schema.options.discriminatorKey;
|
||||
if (value != null &&
|
||||
Constructor.discriminators &&
|
||||
value[discriminatorKey] != null) {
|
||||
if (Constructor.discriminators[value[discriminatorKey]]) {
|
||||
Constructor = Constructor.discriminators[value[discriminatorKey]];
|
||||
} else {
|
||||
const constructorByValue = getDiscriminatorByValue(Constructor, value[discriminatorKey]);
|
||||
if (constructorByValue) {
|
||||
Constructor = constructorByValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Constructor;
|
||||
};
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function cleanModifiedSubpaths(doc, path, options) {
|
||||
options = options || {};
|
||||
const skipDocArrays = options.skipDocArrays;
|
||||
|
||||
let deleted = 0;
|
||||
if (!doc) {
|
||||
return deleted;
|
||||
}
|
||||
for (const modifiedPath of Object.keys(doc.$__.activePaths.states.modify)) {
|
||||
if (skipDocArrays) {
|
||||
const schemaType = doc.schema.path(modifiedPath);
|
||||
if (schemaType && schemaType.$isMongooseDocumentArray) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (modifiedPath.startsWith(path + '.')) {
|
||||
delete doc.$__.activePaths.states.modify[modifiedPath];
|
||||
++deleted;
|
||||
}
|
||||
}
|
||||
return deleted;
|
||||
};
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
'use strict';
|
||||
|
||||
const documentSchemaSymbol = require('../../helpers/symbols').documentSchemaSymbol;
|
||||
const get = require('../../helpers/get');
|
||||
const getSymbol = require('../../helpers/symbols').getSymbol;
|
||||
const utils = require('../../utils');
|
||||
|
||||
let Document;
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
exports.compile = compile;
|
||||
exports.defineKey = defineKey;
|
||||
|
||||
/*!
|
||||
* Compiles schemas.
|
||||
*/
|
||||
|
||||
function compile(tree, proto, prefix, options) {
|
||||
Document = Document || require('../../document');
|
||||
const keys = Object.keys(tree);
|
||||
const len = keys.length;
|
||||
let limb;
|
||||
let key;
|
||||
|
||||
for (let i = 0; i < len; ++i) {
|
||||
key = keys[i];
|
||||
limb = tree[key];
|
||||
|
||||
const hasSubprops = utils.isPOJO(limb) && Object.keys(limb).length &&
|
||||
(!limb[options.typeKey] || (options.typeKey === 'type' && limb.type.type));
|
||||
const subprops = hasSubprops ? limb : null;
|
||||
|
||||
defineKey(key, subprops, proto, prefix, keys, options);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Defines the accessor named prop on the incoming prototype.
|
||||
*/
|
||||
|
||||
function defineKey(prop, subprops, prototype, prefix, keys, options) {
|
||||
Document = Document || require('../../document');
|
||||
const path = (prefix ? prefix + '.' : '') + prop;
|
||||
prefix = prefix || '';
|
||||
|
||||
if (subprops) {
|
||||
Object.defineProperty(prototype, prop, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
get: function() {
|
||||
const _this = this;
|
||||
if (!this.$__.getters) {
|
||||
this.$__.getters = {};
|
||||
}
|
||||
|
||||
if (!this.$__.getters[path]) {
|
||||
const nested = Object.create(Document.prototype, getOwnPropertyDescriptors(this));
|
||||
|
||||
// save scope for nested getters/setters
|
||||
if (!prefix) {
|
||||
nested.$__.scope = this;
|
||||
}
|
||||
nested.$__.nestedPath = path;
|
||||
|
||||
Object.defineProperty(nested, 'schema', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: false,
|
||||
value: prototype.schema
|
||||
});
|
||||
|
||||
Object.defineProperty(nested, documentSchemaSymbol, {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: false,
|
||||
value: prototype.schema
|
||||
});
|
||||
|
||||
Object.defineProperty(nested, 'toObject', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: false,
|
||||
value: function() {
|
||||
return utils.clone(_this.get(path, null, {
|
||||
virtuals: get(this, 'schema.options.toObject.virtuals', null)
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(nested, 'toJSON', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: false,
|
||||
value: function() {
|
||||
return _this.get(path, null, {
|
||||
virtuals: get(_this, 'schema.options.toJSON.virtuals', null)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(nested, '$__isNested', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: false,
|
||||
value: true
|
||||
});
|
||||
|
||||
const _isEmptyOptions = Object.freeze({
|
||||
minimize: true,
|
||||
virtuals: false,
|
||||
getters: false,
|
||||
transform: false
|
||||
});
|
||||
Object.defineProperty(nested, '$isEmpty', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: false,
|
||||
value: function() {
|
||||
return Object.keys(this.get(path, null, _isEmptyOptions) || {}).length === 0;
|
||||
}
|
||||
});
|
||||
|
||||
compile(subprops, nested, path, options);
|
||||
this.$__.getters[path] = nested;
|
||||
}
|
||||
|
||||
return this.$__.getters[path];
|
||||
},
|
||||
set: function(v) {
|
||||
if (v instanceof Document) {
|
||||
v = v.toObject({ transform: false });
|
||||
}
|
||||
const doc = this.$__.scope || this;
|
||||
return doc.$set(path, v);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Object.defineProperty(prototype, prop, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
get: function() {
|
||||
return this[getSymbol].call(this.$__.scope || this, path);
|
||||
},
|
||||
set: function(v) {
|
||||
return this.$set.call(this.$__.scope || this, path, v);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// gets descriptors for all properties of `object`
|
||||
// makes all properties non-enumerable to match previous behavior to #2211
|
||||
function getOwnPropertyDescriptors(object) {
|
||||
const result = {};
|
||||
|
||||
Object.getOwnPropertyNames(object).forEach(function(key) {
|
||||
result[key] = Object.getOwnPropertyDescriptor(object, key);
|
||||
// Assume these are schema paths, ignore them re: #5470
|
||||
if (result[key].get) {
|
||||
delete result[key];
|
||||
return;
|
||||
}
|
||||
result[key].enumerable = ['isNew', '$__', 'errors', '_doc', '$locals'].indexOf(key) === -1;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
const get = require('../get');
|
||||
|
||||
/*!
|
||||
* Like `schema.path()`, except with a document, because impossible to
|
||||
* determine path type without knowing the embedded discriminator key.
|
||||
*/
|
||||
|
||||
module.exports = function getEmbeddedDiscriminatorPath(doc, path, options) {
|
||||
options = options || {};
|
||||
const typeOnly = options.typeOnly;
|
||||
const parts = path.split('.');
|
||||
let schema = null;
|
||||
let type = 'adhocOrUndefined';
|
||||
|
||||
for (let i = 0; i < parts.length; ++i) {
|
||||
const subpath = parts.slice(0, i + 1).join('.');
|
||||
schema = doc.schema.path(subpath);
|
||||
if (schema == null) {
|
||||
type = 'adhocOrUndefined';
|
||||
continue;
|
||||
}
|
||||
if (schema.instance === 'Mixed') {
|
||||
return typeOnly ? 'real' : schema;
|
||||
}
|
||||
type = doc.schema.pathType(subpath);
|
||||
if ((schema.$isSingleNested || schema.$isMongooseDocumentArrayElement) &&
|
||||
schema.schema.discriminators != null) {
|
||||
const discriminators = schema.schema.discriminators;
|
||||
const discriminatorKey = doc.get(subpath + '.' +
|
||||
get(schema, 'schema.options.discriminatorKey'));
|
||||
if (discriminatorKey == null || discriminators[discriminatorKey] == null) {
|
||||
continue;
|
||||
}
|
||||
const rest = parts.slice(i + 1).join('.');
|
||||
return getEmbeddedDiscriminatorPath(doc.get(subpath), rest, options);
|
||||
}
|
||||
}
|
||||
|
||||
// Are we getting the whole schema or just the type, 'real', 'nested', etc.
|
||||
return typeOnly ? type : schema;
|
||||
};
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function each(arr, cb, done) {
|
||||
if (arr.length === 0) {
|
||||
return done();
|
||||
}
|
||||
|
||||
let remaining = arr.length;
|
||||
let err = null;
|
||||
for (const v of arr) {
|
||||
cb(v, function(_err) {
|
||||
if (err != null) {
|
||||
return;
|
||||
}
|
||||
if (_err != null) {
|
||||
err = _err;
|
||||
return done(err);
|
||||
}
|
||||
|
||||
if (--remaining <= 0) {
|
||||
return done();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Simplified lodash.get to work around the annoying null quirk. See:
|
||||
* https://github.com/lodash/lodash/issues/3659
|
||||
*/
|
||||
|
||||
module.exports = function get(obj, path, def) {
|
||||
const parts = path.split('.');
|
||||
let rest = path;
|
||||
let cur = obj;
|
||||
for (const part of parts) {
|
||||
if (cur == null) {
|
||||
return def;
|
||||
}
|
||||
|
||||
// `lib/cast.js` depends on being able to get dotted paths in updates,
|
||||
// like `{ $set: { 'a.b': 42 } }`
|
||||
if (cur[rest] != null) {
|
||||
return cur[rest];
|
||||
}
|
||||
|
||||
cur = getProperty(cur, part);
|
||||
|
||||
rest = rest.substr(part.length + 1);
|
||||
}
|
||||
|
||||
return cur == null ? def : cur;
|
||||
};
|
||||
|
||||
function getProperty(obj, prop) {
|
||||
if (obj == null) {
|
||||
return obj;
|
||||
}
|
||||
if (obj instanceof Map) {
|
||||
return obj.get(prop);
|
||||
}
|
||||
return obj[prop];
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/*!
|
||||
* Centralize this so we can more easily work around issues with people
|
||||
* stubbing out `process.nextTick()` in tests using sinon:
|
||||
* https://github.com/sinonjs/lolex#automatically-incrementing-mocked-time
|
||||
* See gh-6074
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = function immediate(cb) {
|
||||
return process.nextTick(cb);
|
||||
};
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
'use strict';
|
||||
|
||||
const symbols = require('../../schema/symbols');
|
||||
const utils = require('../../utils');
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = applyHooks;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
applyHooks.middlewareFunctions = [
|
||||
'deleteOne',
|
||||
'save',
|
||||
'validate',
|
||||
'remove',
|
||||
'updateOne',
|
||||
'init'
|
||||
];
|
||||
|
||||
/*!
|
||||
* Register hooks for this model
|
||||
*
|
||||
* @param {Model} model
|
||||
* @param {Schema} schema
|
||||
*/
|
||||
|
||||
function applyHooks(model, schema, options) {
|
||||
options = options || {};
|
||||
|
||||
const kareemOptions = {
|
||||
useErrorHandlers: true,
|
||||
numCallbackParams: 1,
|
||||
nullResultByDefault: true,
|
||||
contextParameter: true
|
||||
};
|
||||
const objToDecorate = options.decorateDoc ? model : model.prototype;
|
||||
|
||||
model.$appliedHooks = true;
|
||||
for (const key of Object.keys(schema.paths)) {
|
||||
const type = schema.paths[key];
|
||||
let childModel = null;
|
||||
if (type.$isSingleNested) {
|
||||
childModel = type.caster;
|
||||
} else if (type.$isMongooseDocumentArray) {
|
||||
childModel = type.Constructor;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (childModel.$appliedHooks) {
|
||||
continue;
|
||||
}
|
||||
|
||||
applyHooks(childModel, type.schema, options);
|
||||
if (childModel.discriminators != null) {
|
||||
const keys = Object.keys(childModel.discriminators);
|
||||
for (let j = 0; j < keys.length; ++j) {
|
||||
applyHooks(childModel.discriminators[keys[j]],
|
||||
childModel.discriminators[keys[j]].schema, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Built-in hooks rely on hooking internal functions in order to support
|
||||
// promises and make it so that `doc.save.toString()` provides meaningful
|
||||
// information.
|
||||
|
||||
const middleware = schema.s.hooks.
|
||||
filter(hook => {
|
||||
if (hook.name === 'updateOne' || hook.name === 'deleteOne') {
|
||||
return !!hook['document'];
|
||||
}
|
||||
if (hook.name === 'remove') {
|
||||
return hook['document'] == null || !!hook['document'];
|
||||
}
|
||||
return true;
|
||||
}).
|
||||
filter(hook => {
|
||||
// If user has overwritten the method, don't apply built-in middleware
|
||||
if (schema.methods[hook.name]) {
|
||||
return !hook.fn[symbols.builtInMiddleware];
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
model._middleware = middleware;
|
||||
|
||||
objToDecorate.$__save = middleware.
|
||||
createWrapper('save', objToDecorate.$__save, null, kareemOptions);
|
||||
|
||||
objToDecorate.$__originalValidate = objToDecorate.$__originalValidate || objToDecorate.$__validate;
|
||||
objToDecorate.$__validate = middleware.
|
||||
createWrapper('validate', objToDecorate.$__originalValidate, null, kareemOptions);
|
||||
objToDecorate.$__remove = middleware.
|
||||
createWrapper('remove', objToDecorate.$__remove, null, kareemOptions);
|
||||
objToDecorate.$__deleteOne = middleware.
|
||||
createWrapper('deleteOne', objToDecorate.$__deleteOne, null, kareemOptions);
|
||||
objToDecorate.$__init = middleware.
|
||||
createWrapperSync('init', objToDecorate.$__init, null, kareemOptions);
|
||||
|
||||
// Support hooks for custom methods
|
||||
const customMethods = Object.keys(schema.methods);
|
||||
const customMethodOptions = Object.assign({}, kareemOptions, {
|
||||
// Only use `checkForPromise` for custom methods, because mongoose
|
||||
// query thunks are not as consistent as I would like about returning
|
||||
// a nullish value rather than the query. If a query thunk returns
|
||||
// a query, `checkForPromise` causes infinite recursion
|
||||
checkForPromise: true
|
||||
});
|
||||
for (const method of customMethods) {
|
||||
if (!middleware.hasHooks(method)) {
|
||||
// Don't wrap if there are no hooks for the custom method to avoid
|
||||
// surprises. Also, `createWrapper()` enforces consistent async,
|
||||
// so wrapping a sync method would break it.
|
||||
continue;
|
||||
}
|
||||
const originalMethod = objToDecorate[method];
|
||||
objToDecorate[method] = function() {
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
const cb = utils.last(args);
|
||||
const argsWithoutCallback = typeof cb === 'function' ?
|
||||
args.slice(0, args.length - 1) : args;
|
||||
return utils.promiseOrCallback(cb, callback => {
|
||||
return this[`$__${method}`].apply(this,
|
||||
argsWithoutCallback.concat([callback]));
|
||||
}, model.events);
|
||||
};
|
||||
objToDecorate[`$__${method}`] = middleware.
|
||||
createWrapper(method, originalMethod, null, customMethodOptions);
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
const get = require('../get');
|
||||
|
||||
/*!
|
||||
* Register methods for this model
|
||||
*
|
||||
* @param {Model} model
|
||||
* @param {Schema} schema
|
||||
*/
|
||||
|
||||
module.exports = function applyMethods(model, schema) {
|
||||
function apply(method, schema) {
|
||||
Object.defineProperty(model.prototype, method, {
|
||||
get: function() {
|
||||
const h = {};
|
||||
for (const k in schema.methods[method]) {
|
||||
h[k] = schema.methods[method][k].bind(this);
|
||||
}
|
||||
return h;
|
||||
},
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
for (const method of Object.keys(schema.methods)) {
|
||||
const fn = schema.methods[method];
|
||||
if (schema.tree.hasOwnProperty(method)) {
|
||||
throw new Error('You have a method and a property in your schema both ' +
|
||||
'named "' + method + '"');
|
||||
}
|
||||
if (schema.reserved[method] &&
|
||||
!get(schema, `methodOptions.${method}.suppressWarning`, false)) {
|
||||
console.warn(`mongoose: the method name "${method}" is used by mongoose ` +
|
||||
'internally, overwriting it may cause bugs. If you\'re sure you know ' +
|
||||
'what you\'re doing, you can suppress this error by using ' +
|
||||
`\`schema.method('${method}', fn, { suppressWarning: true })\`.`);
|
||||
}
|
||||
if (typeof fn === 'function') {
|
||||
model.prototype[method] = fn;
|
||||
} else {
|
||||
apply(method, schema);
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively call `applyMethods()` on child schemas
|
||||
model.$appliedMethods = true;
|
||||
for (const key of Object.keys(schema.paths)) {
|
||||
const type = schema.paths[key];
|
||||
if (type.$isSingleNested && !type.caster.$appliedMethods) {
|
||||
applyMethods(type.caster, type.schema);
|
||||
}
|
||||
if (type.$isMongooseDocumentArray && !type.Constructor.$appliedMethods) {
|
||||
applyMethods(type.Constructor, type.schema);
|
||||
}
|
||||
}
|
||||
};
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
'use strict';
|
||||
|
||||
const middlewareFunctions = require('../query/applyQueryMiddleware').middlewareFunctions;
|
||||
const utils = require('../../utils');
|
||||
|
||||
module.exports = function applyStaticHooks(model, hooks, statics) {
|
||||
const kareemOptions = {
|
||||
useErrorHandlers: true,
|
||||
numCallbackParams: 1
|
||||
};
|
||||
|
||||
hooks = hooks.filter(hook => {
|
||||
// If the custom static overwrites an existing query middleware, don't apply
|
||||
// middleware to it by default. This avoids a potential backwards breaking
|
||||
// change with plugins like `mongoose-delete` that use statics to overwrite
|
||||
// built-in Mongoose functions.
|
||||
if (middlewareFunctions.indexOf(hook.name) !== -1) {
|
||||
return !!hook.model;
|
||||
}
|
||||
return hook.model !== false;
|
||||
});
|
||||
|
||||
model.$__insertMany = hooks.createWrapper('insertMany',
|
||||
model.$__insertMany, model, kareemOptions);
|
||||
|
||||
for (const key of Object.keys(statics)) {
|
||||
if (hooks.hasHooks(key)) {
|
||||
const original = model[key];
|
||||
|
||||
model[key] = function() {
|
||||
const numArgs = arguments.length;
|
||||
const lastArg = numArgs > 0 ? arguments[numArgs - 1] : null;
|
||||
const cb = typeof lastArg === 'function' ? lastArg : null;
|
||||
const args = Array.prototype.slice.
|
||||
call(arguments, 0, cb == null ? numArgs : numArgs - 1);
|
||||
// Special case: can't use `Kareem#wrap()` because it doesn't currently
|
||||
// support wrapped functions that return a promise.
|
||||
return utils.promiseOrCallback(cb, callback => {
|
||||
hooks.execPre(key, model, args, function(err) {
|
||||
if (err != null) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
let postCalled = 0;
|
||||
const ret = original.apply(model, args.concat(post));
|
||||
if (ret != null && typeof ret.then === 'function') {
|
||||
ret.then(res => post(null, res), err => post(err));
|
||||
}
|
||||
|
||||
function post(error, res) {
|
||||
if (postCalled++ > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
hooks.execPost(key, model, [res], function(error) {
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
callback(null, res);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, model.events);
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Register statics for this model
|
||||
* @param {Model} model
|
||||
* @param {Schema} schema
|
||||
*/
|
||||
module.exports = function applyStatics(model, schema) {
|
||||
for (const i in schema.statics) {
|
||||
model[i] = schema.statics[i];
|
||||
}
|
||||
};
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
'use strict';
|
||||
|
||||
const applyTimestampsToChildren = require('../update/applyTimestampsToChildren');
|
||||
const applyTimestampsToUpdate = require('../update/applyTimestampsToUpdate');
|
||||
const cast = require('../../cast');
|
||||
const castUpdate = require('../query/castUpdate');
|
||||
const setDefaultsOnInsert = require('../setDefaultsOnInsert');
|
||||
|
||||
/*!
|
||||
* Given a model and a bulkWrite op, return a thunk that handles casting and
|
||||
* validating the individual op.
|
||||
*/
|
||||
|
||||
module.exports = function castBulkWrite(model, op, options) {
|
||||
const now = model.base.now();
|
||||
if (op['insertOne']) {
|
||||
return (callback) => {
|
||||
const doc = new model(op['insertOne']['document']);
|
||||
if (model.schema.options.timestamps != null) {
|
||||
doc.initializeTimestamps();
|
||||
}
|
||||
if (options.session != null) {
|
||||
doc.$session(options.session);
|
||||
}
|
||||
op['insertOne']['document'] = doc;
|
||||
op['insertOne']['document'].validate({ __noPromise: true }, function(error) {
|
||||
if (error) {
|
||||
return callback(error, null);
|
||||
}
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
} else if (op['updateOne']) {
|
||||
op = op['updateOne'];
|
||||
return (callback) => {
|
||||
try {
|
||||
op['filter'] = cast(model.schema, op['filter']);
|
||||
op['update'] = castUpdate(model.schema, op['update'], {
|
||||
strict: model.schema.options.strict,
|
||||
overwrite: false
|
||||
});
|
||||
if (op.setDefaultsOnInsert) {
|
||||
setDefaultsOnInsert(op['filter'], model.schema, op['update'], {
|
||||
setDefaultsOnInsert: true,
|
||||
upsert: op.upsert
|
||||
});
|
||||
}
|
||||
if (model.schema.$timestamps != null) {
|
||||
const createdAt = model.schema.$timestamps.createdAt;
|
||||
const updatedAt = model.schema.$timestamps.updatedAt;
|
||||
applyTimestampsToUpdate(now, createdAt, updatedAt, op['update'], {});
|
||||
}
|
||||
applyTimestampsToChildren(now, op['update'], model.schema);
|
||||
} catch (error) {
|
||||
return callback(error, null);
|
||||
}
|
||||
|
||||
callback(null);
|
||||
};
|
||||
} else if (op['updateMany']) {
|
||||
op = op['updateMany'];
|
||||
return (callback) => {
|
||||
try {
|
||||
op['filter'] = cast(model.schema, op['filter']);
|
||||
op['update'] = castUpdate(model.schema, op['update'], {
|
||||
strict: model.schema.options.strict,
|
||||
overwrite: false
|
||||
});
|
||||
if (op.setDefaultsOnInsert) {
|
||||
setDefaultsOnInsert(op['filter'], model.schema, op['update'], {
|
||||
setDefaultsOnInsert: true,
|
||||
upsert: op.upsert
|
||||
});
|
||||
}
|
||||
if (model.schema.$timestamps != null) {
|
||||
const createdAt = model.schema.$timestamps.createdAt;
|
||||
const updatedAt = model.schema.$timestamps.updatedAt;
|
||||
applyTimestampsToUpdate(now, createdAt, updatedAt, op['update'], {});
|
||||
}
|
||||
applyTimestampsToChildren(now, op['update'], model.schema);
|
||||
} catch (error) {
|
||||
return callback(error, null);
|
||||
}
|
||||
|
||||
callback(null);
|
||||
};
|
||||
} else if (op['replaceOne']) {
|
||||
return (callback) => {
|
||||
try {
|
||||
op['replaceOne']['filter'] = cast(model.schema,
|
||||
op['replaceOne']['filter']);
|
||||
} catch (error) {
|
||||
return callback(error, null);
|
||||
}
|
||||
|
||||
// set `skipId`, otherwise we get "_id field cannot be changed"
|
||||
const doc = new model(op['replaceOne']['replacement'], null, true);
|
||||
if (model.schema.options.timestamps != null) {
|
||||
doc.initializeTimestamps();
|
||||
}
|
||||
if (options.session != null) {
|
||||
doc.$session(options.session);
|
||||
}
|
||||
op['replaceOne']['replacement'] = doc;
|
||||
|
||||
op['replaceOne']['replacement'].validate({ __noPromise: true }, function(error) {
|
||||
if (error) {
|
||||
return callback(error, null);
|
||||
}
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
} else if (op['deleteOne']) {
|
||||
return (callback) => {
|
||||
try {
|
||||
op['deleteOne']['filter'] = cast(model.schema,
|
||||
op['deleteOne']['filter']);
|
||||
} catch (error) {
|
||||
return callback(error, null);
|
||||
}
|
||||
|
||||
callback(null);
|
||||
};
|
||||
} else if (op['deleteMany']) {
|
||||
return (callback) => {
|
||||
try {
|
||||
op['deleteMany']['filter'] = cast(model.schema,
|
||||
op['deleteMany']['filter']);
|
||||
} catch (error) {
|
||||
return callback(error, null);
|
||||
}
|
||||
|
||||
callback(null);
|
||||
};
|
||||
} else {
|
||||
return (callback) => {
|
||||
callback(new Error('Invalid op passed to `bulkWrite()`'), null);
|
||||
};
|
||||
}
|
||||
};
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
'use strict';
|
||||
|
||||
const defineKey = require('../document/compile').defineKey;
|
||||
const get = require('../get');
|
||||
const utils = require('../../utils');
|
||||
|
||||
const CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
|
||||
toJSON: true,
|
||||
toObject: true,
|
||||
_id: true,
|
||||
id: true
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function discriminator(model, name, schema, tiedValue, applyPlugins) {
|
||||
if (!(schema && schema.instanceOfSchema)) {
|
||||
throw new Error('You must pass a valid discriminator Schema');
|
||||
}
|
||||
|
||||
if (model.schema.discriminatorMapping &&
|
||||
!model.schema.discriminatorMapping.isRoot) {
|
||||
throw new Error('Discriminator "' + name +
|
||||
'" can only be a discriminator of the root model');
|
||||
}
|
||||
|
||||
if (applyPlugins) {
|
||||
const applyPluginsToDiscriminators = get(model.base,
|
||||
'options.applyPluginsToDiscriminators', false);
|
||||
// Even if `applyPluginsToDiscriminators` isn't set, we should still apply
|
||||
// global plugins to schemas embedded in the discriminator schema (gh-7370)
|
||||
model.base._applyPlugins(schema, {
|
||||
skipTopLevel: !applyPluginsToDiscriminators
|
||||
});
|
||||
}
|
||||
|
||||
const key = model.schema.options.discriminatorKey;
|
||||
|
||||
const existingPath = model.schema.path(key);
|
||||
if (existingPath != null) {
|
||||
if (!utils.hasUserDefinedProperty(existingPath.options, 'select')) {
|
||||
existingPath.options.select = true;
|
||||
}
|
||||
existingPath.options.$skipDiscriminatorCheck = true;
|
||||
} else {
|
||||
const baseSchemaAddition = {};
|
||||
baseSchemaAddition[key] = {
|
||||
default: void 0,
|
||||
select: true,
|
||||
$skipDiscriminatorCheck: true
|
||||
};
|
||||
baseSchemaAddition[key][model.schema.options.typeKey] = String;
|
||||
model.schema.add(baseSchemaAddition);
|
||||
defineKey(key, null, model.prototype, null, [key], model.schema.options);
|
||||
}
|
||||
|
||||
if (schema.path(key) && schema.path(key).options.$skipDiscriminatorCheck !== true) {
|
||||
throw new Error('Discriminator "' + name +
|
||||
'" cannot have field with name "' + key + '"');
|
||||
}
|
||||
|
||||
let value = name;
|
||||
if (typeof tiedValue == 'string' && tiedValue.length) {
|
||||
value = tiedValue;
|
||||
}
|
||||
|
||||
function merge(schema, baseSchema) {
|
||||
// Retain original schema before merging base schema
|
||||
schema._baseSchema = baseSchema;
|
||||
if (baseSchema.paths._id &&
|
||||
baseSchema.paths._id.options &&
|
||||
!baseSchema.paths._id.options.auto) {
|
||||
const originalSchema = schema;
|
||||
utils.merge(schema, originalSchema);
|
||||
delete schema.paths._id;
|
||||
delete schema.tree._id;
|
||||
}
|
||||
|
||||
// Find conflicting paths: if something is a path in the base schema
|
||||
// and a nested path in the child schema, overwrite the base schema path.
|
||||
// See gh-6076
|
||||
const baseSchemaPaths = Object.keys(baseSchema.paths);
|
||||
const conflictingPaths = [];
|
||||
for (let i = 0; i < baseSchemaPaths.length; ++i) {
|
||||
if (schema.nested[baseSchemaPaths[i]]) {
|
||||
conflictingPaths.push(baseSchemaPaths[i]);
|
||||
}
|
||||
}
|
||||
|
||||
utils.merge(schema, baseSchema, {
|
||||
omit: { discriminators: true },
|
||||
omitNested: conflictingPaths.reduce((cur, path) => {
|
||||
cur['tree.' + path] = true;
|
||||
return cur;
|
||||
}, {})
|
||||
});
|
||||
|
||||
// Clean up conflicting paths _after_ merging re: gh-6076
|
||||
for (let i = 0; i < conflictingPaths.length; ++i) {
|
||||
delete schema.paths[conflictingPaths[i]];
|
||||
}
|
||||
|
||||
// Rebuild schema models because schemas may have been merged re: #7884
|
||||
schema.childSchemas.forEach(obj => {
|
||||
obj.model.prototype.$__setSchema(obj.schema);
|
||||
});
|
||||
|
||||
const obj = {};
|
||||
obj[key] = {
|
||||
default: value,
|
||||
select: true,
|
||||
set: function(newName) {
|
||||
if (newName === value) {
|
||||
return value;
|
||||
}
|
||||
throw new Error('Can\'t set discriminator key "' + key + '"');
|
||||
},
|
||||
$skipDiscriminatorCheck: true
|
||||
};
|
||||
obj[key][schema.options.typeKey] = existingPath ?
|
||||
existingPath.instance :
|
||||
String;
|
||||
schema.add(obj);
|
||||
schema.discriminatorMapping = {key: key, value: value, isRoot: false};
|
||||
|
||||
if (baseSchema.options.collection) {
|
||||
schema.options.collection = baseSchema.options.collection;
|
||||
}
|
||||
|
||||
const toJSON = schema.options.toJSON;
|
||||
const toObject = schema.options.toObject;
|
||||
const _id = schema.options._id;
|
||||
const id = schema.options.id;
|
||||
|
||||
const keys = Object.keys(schema.options);
|
||||
schema.options.discriminatorKey = baseSchema.options.discriminatorKey;
|
||||
|
||||
for (let i = 0; i < keys.length; ++i) {
|
||||
const _key = keys[i];
|
||||
if (!CUSTOMIZABLE_DISCRIMINATOR_OPTIONS[_key]) {
|
||||
if (!utils.deepEqual(schema.options[_key], baseSchema.options[_key])) {
|
||||
throw new Error('Can\'t customize discriminator option ' + _key +
|
||||
' (can only modify ' +
|
||||
Object.keys(CUSTOMIZABLE_DISCRIMINATOR_OPTIONS).join(', ') +
|
||||
')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
schema.options = utils.clone(baseSchema.options);
|
||||
if (toJSON) schema.options.toJSON = toJSON;
|
||||
if (toObject) schema.options.toObject = toObject;
|
||||
if (typeof _id !== 'undefined') {
|
||||
schema.options._id = _id;
|
||||
}
|
||||
schema.options.id = id;
|
||||
schema.s.hooks = model.schema.s.hooks.merge(schema.s.hooks);
|
||||
|
||||
schema.plugins = Array.prototype.slice(baseSchema.plugins);
|
||||
schema.callQueue = baseSchema.callQueue.concat(schema.callQueue);
|
||||
delete schema._requiredpaths; // reset just in case Schema#requiredPaths() was called on either schema
|
||||
}
|
||||
|
||||
// merges base schema into new discriminator schema and sets new type field.
|
||||
merge(schema, model.schema);
|
||||
|
||||
if (!model.discriminators) {
|
||||
model.discriminators = {};
|
||||
}
|
||||
|
||||
if (!model.schema.discriminatorMapping) {
|
||||
model.schema.discriminatorMapping = {key: key, value: null, isRoot: true};
|
||||
}
|
||||
if (!model.schema.discriminators) {
|
||||
model.schema.discriminators = {};
|
||||
}
|
||||
|
||||
model.schema.discriminators[name] = schema;
|
||||
|
||||
if (model.discriminators[name]) {
|
||||
throw new Error('Discriminator with name "' + name + '" already exists');
|
||||
}
|
||||
|
||||
return schema;
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function once(fn) {
|
||||
let called = false;
|
||||
return function() {
|
||||
if (called) {
|
||||
return;
|
||||
}
|
||||
called = true;
|
||||
return fn.apply(null, arguments);
|
||||
};
|
||||
};
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = parallelLimit;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function parallelLimit(fns, limit, callback) {
|
||||
let numInProgress = 0;
|
||||
let numFinished = 0;
|
||||
let error = null;
|
||||
|
||||
if (limit <= 0) {
|
||||
throw new Error('Limit must be positive');
|
||||
}
|
||||
|
||||
if (fns.length === 0) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
for (let i = 0; i < fns.length && i < limit; ++i) {
|
||||
_start();
|
||||
}
|
||||
|
||||
function _start() {
|
||||
fns[numFinished + numInProgress](_done(numFinished + numInProgress));
|
||||
++numInProgress;
|
||||
}
|
||||
|
||||
const results = [];
|
||||
|
||||
function _done(index) {
|
||||
return (err, res) => {
|
||||
--numInProgress;
|
||||
++numFinished;
|
||||
|
||||
if (error != null) {
|
||||
return;
|
||||
}
|
||||
if (err != null) {
|
||||
error = err;
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
results[index] = res;
|
||||
|
||||
if (numFinished === fns.length) {
|
||||
return callback(null, results);
|
||||
} else if (numFinished + numInProgress < fns.length) {
|
||||
_start();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = assignRawDocsToIdStructure;
|
||||
|
||||
/*!
|
||||
* Assign `vals` returned by mongo query to the `rawIds`
|
||||
* structure returned from utils.getVals() honoring
|
||||
* query sort order if specified by user.
|
||||
*
|
||||
* This can be optimized.
|
||||
*
|
||||
* Rules:
|
||||
*
|
||||
* if the value of the path is not an array, use findOne rules, else find.
|
||||
* for findOne the results are assigned directly to doc path (including null results).
|
||||
* for find, if user specified sort order, results are assigned directly
|
||||
* else documents are put back in original order of array if found in results
|
||||
*
|
||||
* @param {Array} rawIds
|
||||
* @param {Array} vals
|
||||
* @param {Boolean} sort
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function assignRawDocsToIdStructure(rawIds, resultDocs, resultOrder, options, recursed) {
|
||||
// honor user specified sort order
|
||||
const newOrder = [];
|
||||
const sorting = options.sort && rawIds.length > 1;
|
||||
const nullIfNotFound = options.$nullIfNotFound;
|
||||
let doc;
|
||||
let sid;
|
||||
let id;
|
||||
|
||||
for (let i = 0; i < rawIds.length; ++i) {
|
||||
id = rawIds[i];
|
||||
|
||||
if (Array.isArray(id)) {
|
||||
// handle [ [id0, id2], [id3] ]
|
||||
assignRawDocsToIdStructure(id, resultDocs, resultOrder, options, true);
|
||||
newOrder.push(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (id === null && !sorting) {
|
||||
// keep nulls for findOne unless sorting, which always
|
||||
// removes them (backward compat)
|
||||
newOrder.push(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
sid = String(id);
|
||||
|
||||
doc = resultDocs[sid];
|
||||
// If user wants separate copies of same doc, use this option
|
||||
if (options.clone) {
|
||||
doc = doc.constructor.hydrate(doc._doc);
|
||||
}
|
||||
|
||||
if (recursed) {
|
||||
if (doc) {
|
||||
if (sorting) {
|
||||
newOrder[resultOrder[sid]] = doc;
|
||||
} else {
|
||||
newOrder.push(doc);
|
||||
}
|
||||
} else {
|
||||
newOrder.push(nullIfNotFound ? null : id);
|
||||
}
|
||||
} else {
|
||||
// apply findOne behavior - if document in results, assign, else assign null
|
||||
newOrder[i] = doc || null;
|
||||
}
|
||||
}
|
||||
|
||||
rawIds.length = 0;
|
||||
if (newOrder.length) {
|
||||
// reassign the documents based on corrected order
|
||||
|
||||
// forEach skips over sparse entries in arrays so we
|
||||
// can safely use this to our advantage dealing with sorted
|
||||
// result sets too.
|
||||
newOrder.forEach(function(doc, i) {
|
||||
rawIds[i] = doc;
|
||||
});
|
||||
}
|
||||
}
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
'use strict';
|
||||
|
||||
const assignRawDocsToIdStructure = require('./assignRawDocsToIdStructure');
|
||||
const get = require('../get');
|
||||
const getVirtual = require('./getVirtual');
|
||||
const leanPopulateMap = require('./leanPopulateMap');
|
||||
const mpath = require('mpath');
|
||||
const sift = require('sift').default;
|
||||
const utils = require('../../utils');
|
||||
|
||||
module.exports = function assignVals(o) {
|
||||
// Options that aren't explicitly listed in `populateOptions`
|
||||
const userOptions = get(o, 'allOptions.options.options');
|
||||
// `o.options` contains options explicitly listed in `populateOptions`, like
|
||||
// `match` and `limit`.
|
||||
const populateOptions = Object.assign({}, o.options, userOptions, {
|
||||
justOne: o.justOne
|
||||
});
|
||||
populateOptions.$nullIfNotFound = o.isVirtual;
|
||||
|
||||
const originalIds = [].concat(o.rawIds);
|
||||
|
||||
// replace the original ids in our intermediate _ids structure
|
||||
// with the documents found by query
|
||||
assignRawDocsToIdStructure(o.rawIds, o.rawDocs, o.rawOrder, populateOptions);
|
||||
|
||||
// now update the original documents being populated using the
|
||||
// result structure that contains real documents.
|
||||
const docs = o.docs;
|
||||
const rawIds = o.rawIds;
|
||||
const options = o.options;
|
||||
const count = o.count && o.isVirtual;
|
||||
|
||||
function setValue(val) {
|
||||
if (count) {
|
||||
return val;
|
||||
}
|
||||
if (o.justOne === true && Array.isArray(val)) {
|
||||
return valueFilter(val[0], options, populateOptions);
|
||||
} else if (o.justOne === false && !Array.isArray(val)) {
|
||||
return valueFilter([val], options, populateOptions);
|
||||
}
|
||||
return valueFilter(val, options, populateOptions);
|
||||
}
|
||||
|
||||
for (let i = 0; i < docs.length; ++i) {
|
||||
const existingVal = utils.getValue(o.path, docs[i]);
|
||||
if (existingVal == null && !getVirtual(o.originalModel.schema, o.path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let valueToSet;
|
||||
if (count) {
|
||||
valueToSet = numDocs(rawIds[i]);
|
||||
} else if (Array.isArray(o.match)) {
|
||||
valueToSet = Array.isArray(rawIds[i]) ?
|
||||
sift(o.match[i], rawIds[i]) :
|
||||
sift(o.match[i], [rawIds[i]])[0];
|
||||
} else {
|
||||
valueToSet = rawIds[i];
|
||||
}
|
||||
|
||||
// If we're populating a map, the existing value will be an object, so
|
||||
// we need to transform again
|
||||
const originalSchema = o.originalModel.schema;
|
||||
const isDoc = get(docs[i], '$__', null) != null;
|
||||
let isMap = isDoc ?
|
||||
existingVal instanceof Map :
|
||||
utils.isPOJO(existingVal);
|
||||
// If we pass the first check, also make sure the local field's schematype
|
||||
// is map (re: gh-6460)
|
||||
isMap = isMap && get(originalSchema._getSchema(o.path), '$isSchemaMap');
|
||||
if (!o.isVirtual && isMap) {
|
||||
const _keys = existingVal instanceof Map ?
|
||||
Array.from(existingVal.keys()) :
|
||||
Object.keys(existingVal);
|
||||
valueToSet = valueToSet.reduce((cur, v, i) => {
|
||||
cur.set(_keys[i], v);
|
||||
return cur;
|
||||
}, new Map());
|
||||
}
|
||||
|
||||
if (o.isVirtual && isDoc) {
|
||||
docs[i].populated(o.path, o.justOne ? originalIds[0] : originalIds, o.allOptions);
|
||||
// If virtual populate and doc is already init-ed, need to walk through
|
||||
// the actual doc to set rather than setting `_doc` directly
|
||||
mpath.set(o.path, valueToSet, docs[i], setValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
const parts = o.path.split('.');
|
||||
let cur = docs[i];
|
||||
for (let j = 0; j < parts.length - 1; ++j) {
|
||||
// If we get to an array with a dotted path, like `arr.foo`, don't set
|
||||
// `foo` on the array.
|
||||
if (Array.isArray(cur) && !utils.isArrayIndex(parts[j])) {
|
||||
break;
|
||||
}
|
||||
if (cur[parts[j]] == null) {
|
||||
cur[parts[j]] = {};
|
||||
}
|
||||
cur = cur[parts[j]];
|
||||
// If the property in MongoDB is a primitive, we won't be able to populate
|
||||
// the nested path, so skip it. See gh-7545
|
||||
if (typeof cur !== 'object') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (docs[i].$__) {
|
||||
docs[i].populated(o.path, o.allIds[i], o.allOptions);
|
||||
}
|
||||
|
||||
// If lean, need to check that each individual virtual respects
|
||||
// `justOne`, because you may have a populated virtual with `justOne`
|
||||
// underneath an array. See gh-6867
|
||||
utils.setValue(o.path, valueToSet, docs[i], setValue, false);
|
||||
}
|
||||
};
|
||||
|
||||
function numDocs(v) {
|
||||
if (Array.isArray(v)) {
|
||||
// If setting underneath an array of populated subdocs, we may have an
|
||||
// array of arrays. See gh-7573
|
||||
if (v.some(el => Array.isArray(el))) {
|
||||
return v.map(el => numDocs(el));
|
||||
}
|
||||
return v.length;
|
||||
}
|
||||
return v == null ? 0 : 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
* 1) Apply backwards compatible find/findOne behavior to sub documents
|
||||
*
|
||||
* find logic:
|
||||
* a) filter out non-documents
|
||||
* b) remove _id from sub docs when user specified
|
||||
*
|
||||
* findOne
|
||||
* a) if no doc found, set to null
|
||||
* b) remove _id from sub docs when user specified
|
||||
*
|
||||
* 2) Remove _ids when specified by users query.
|
||||
*
|
||||
* background:
|
||||
* _ids are left in the query even when user excludes them so
|
||||
* that population mapping can occur.
|
||||
*/
|
||||
|
||||
function valueFilter(val, assignmentOpts, populateOptions) {
|
||||
if (Array.isArray(val)) {
|
||||
// find logic
|
||||
const ret = [];
|
||||
const numValues = val.length;
|
||||
for (let i = 0; i < numValues; ++i) {
|
||||
const subdoc = val[i];
|
||||
if (!isPopulatedObject(subdoc) && (!populateOptions.retainNullValues || subdoc != null)) {
|
||||
continue;
|
||||
}
|
||||
maybeRemoveId(subdoc, assignmentOpts);
|
||||
ret.push(subdoc);
|
||||
if (assignmentOpts.originalLimit &&
|
||||
ret.length >= assignmentOpts.originalLimit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Since we don't want to have to create a new mongoosearray, make sure to
|
||||
// modify the array in place
|
||||
while (val.length > ret.length) {
|
||||
Array.prototype.pop.apply(val, []);
|
||||
}
|
||||
for (let i = 0; i < ret.length; ++i) {
|
||||
val[i] = ret[i];
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
// findOne
|
||||
if (isPopulatedObject(val)) {
|
||||
maybeRemoveId(val, assignmentOpts);
|
||||
return val;
|
||||
}
|
||||
|
||||
if (val instanceof Map) {
|
||||
return val;
|
||||
}
|
||||
|
||||
if (populateOptions.justOne === true) {
|
||||
return (val == null ? val : null);
|
||||
}
|
||||
if (populateOptions.justOne === false) {
|
||||
return [];
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Remove _id from `subdoc` if user specified "lean" query option
|
||||
*/
|
||||
|
||||
function maybeRemoveId(subdoc, assignmentOpts) {
|
||||
if (assignmentOpts.excludeId) {
|
||||
if (typeof subdoc.$__setValue === 'function') {
|
||||
delete subdoc._doc._id;
|
||||
} else {
|
||||
delete subdoc._id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Determine if `obj` is something we can set a populated path to. Can be a
|
||||
* document, a lean document, or an array/map that contains docs.
|
||||
*/
|
||||
|
||||
function isPopulatedObject(obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Array.isArray(obj) ||
|
||||
obj.$isMongooseMap ||
|
||||
obj.$__ != null ||
|
||||
leanPopulateMap.has(obj);
|
||||
}
|
||||
+419
@@ -0,0 +1,419 @@
|
||||
'use strict';
|
||||
|
||||
const MongooseError = require('../../error/index');
|
||||
const get = require('../get');
|
||||
const isPathExcluded = require('../projection/isPathExcluded');
|
||||
const getSchemaTypes = require('./getSchemaTypes');
|
||||
const getVirtual = require('./getVirtual');
|
||||
const normalizeRefPath = require('./normalizeRefPath');
|
||||
const util = require('util');
|
||||
const utils = require('../../utils');
|
||||
|
||||
const modelSymbol = require('../symbols').modelSymbol;
|
||||
const populateModelSymbol = require('../symbols').populateModelSymbol;
|
||||
const schemaMixedSymbol = require('../../schema/symbols').schemaMixedSymbol;
|
||||
|
||||
module.exports = function getModelsMapForPopulate(model, docs, options) {
|
||||
let i;
|
||||
let doc;
|
||||
const len = docs.length;
|
||||
const available = {};
|
||||
const map = [];
|
||||
const modelNameFromQuery = options.model && options.model.modelName || options.model;
|
||||
let schema;
|
||||
let refPath;
|
||||
let Model;
|
||||
let currentOptions;
|
||||
let modelNames;
|
||||
let modelName;
|
||||
let modelForFindSchema;
|
||||
|
||||
const originalModel = options.model;
|
||||
let isVirtual = false;
|
||||
const modelSchema = model.schema;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
doc = docs[i];
|
||||
|
||||
schema = getSchemaTypes(modelSchema, doc, options.path);
|
||||
const isUnderneathDocArray = schema && schema.$isUnderneathDocArray;
|
||||
if (isUnderneathDocArray && get(options, 'options.sort') != null) {
|
||||
return new MongooseError('Cannot populate with `sort` on path ' + options.path +
|
||||
' because it is a subproperty of a document array');
|
||||
}
|
||||
|
||||
modelNames = null;
|
||||
let isRefPath = false;
|
||||
if (Array.isArray(schema)) {
|
||||
for (let j = 0; j < schema.length; ++j) {
|
||||
let _modelNames;
|
||||
try {
|
||||
const res = _getModelNames(doc, schema[j]);
|
||||
_modelNames = res.modelNames;
|
||||
isRefPath = res.isRefPath;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
if (!_modelNames) {
|
||||
continue;
|
||||
}
|
||||
modelNames = modelNames || [];
|
||||
for (let x = 0; x < _modelNames.length; ++x) {
|
||||
if (modelNames.indexOf(_modelNames[x]) === -1) {
|
||||
modelNames.push(_modelNames[x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const res = _getModelNames(doc, schema);
|
||||
modelNames = res.modelNames;
|
||||
isRefPath = res.isRefPath;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (!modelNames) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const _virtualRes = getVirtual(model.schema, options.path);
|
||||
const virtual = _virtualRes == null ? null : _virtualRes.virtual;
|
||||
|
||||
let localField;
|
||||
let count = false;
|
||||
if (virtual && virtual.options) {
|
||||
const virtualPrefix = _virtualRes.nestedSchemaPath ?
|
||||
_virtualRes.nestedSchemaPath + '.' : '';
|
||||
if (typeof virtual.options.localField === 'function') {
|
||||
localField = virtualPrefix + virtual.options.localField.call(doc, doc);
|
||||
} else {
|
||||
localField = virtualPrefix + virtual.options.localField;
|
||||
}
|
||||
count = virtual.options.count;
|
||||
} else {
|
||||
localField = options.path;
|
||||
}
|
||||
let foreignField = virtual && virtual.options ?
|
||||
virtual.options.foreignField :
|
||||
'_id';
|
||||
|
||||
// `justOne = null` means we don't know from the schema whether the end
|
||||
// result should be an array or a single doc. This can result from
|
||||
// populating a POJO using `Model.populate()`
|
||||
let justOne = null;
|
||||
if ('justOne' in options && options.justOne !== void 0) {
|
||||
justOne = options.justOne;
|
||||
} else if (virtual && virtual.options && virtual.options.refPath) {
|
||||
const normalizedRefPath =
|
||||
normalizeRefPath(virtual.options.refPath, doc, options.path);
|
||||
justOne = !!virtual.options.justOne;
|
||||
isVirtual = true;
|
||||
const refValue = utils.getValue(normalizedRefPath, doc);
|
||||
modelNames = Array.isArray(refValue) ? refValue : [refValue];
|
||||
} else if (virtual && virtual.options && virtual.options.ref) {
|
||||
let normalizedRef;
|
||||
if (typeof virtual.options.ref === 'function') {
|
||||
normalizedRef = virtual.options.ref.call(doc, doc);
|
||||
} else {
|
||||
normalizedRef = virtual.options.ref;
|
||||
}
|
||||
justOne = !!virtual.options.justOne;
|
||||
isVirtual = true;
|
||||
if (!modelNames) {
|
||||
modelNames = [].concat(normalizedRef);
|
||||
}
|
||||
} else if (schema && !schema[schemaMixedSymbol]) {
|
||||
// Skip Mixed types because we explicitly don't do casting on those.
|
||||
justOne = !schema.$isMongooseArray;
|
||||
}
|
||||
|
||||
if (!modelNames) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (virtual && (!localField || !foreignField)) {
|
||||
return new MongooseError('If you are populating a virtual, you must set the ' +
|
||||
'localField and foreignField options');
|
||||
}
|
||||
|
||||
options.isVirtual = isVirtual;
|
||||
options.virtual = virtual;
|
||||
if (typeof localField === 'function') {
|
||||
localField = localField.call(doc, doc);
|
||||
}
|
||||
if (typeof foreignField === 'function') {
|
||||
foreignField = foreignField.call(doc);
|
||||
}
|
||||
|
||||
const localFieldPathType = modelSchema._getPathType(localField);
|
||||
const localFieldPath = localFieldPathType === 'real' ? modelSchema.path(localField) : localFieldPathType.schema;
|
||||
const localFieldGetters = localFieldPath && localFieldPath.getters ? localFieldPath.getters : [];
|
||||
let ret;
|
||||
|
||||
const _populateOptions = get(options, 'options', {});
|
||||
|
||||
const getters = 'getters' in _populateOptions ?
|
||||
_populateOptions.getters :
|
||||
options.isVirtual && get(virtual, 'options.getters', false);
|
||||
if (localFieldGetters.length > 0 && getters) {
|
||||
const hydratedDoc = (doc.$__ != null) ? doc : model.hydrate(doc);
|
||||
const localFieldValue = utils.getValue(localField, doc);
|
||||
if (Array.isArray(localFieldValue)) {
|
||||
const localFieldHydratedValue = utils.getValue(localField.split('.').slice(0, -1), hydratedDoc);
|
||||
ret = localFieldValue.map((localFieldArrVal, localFieldArrIndex) =>
|
||||
localFieldPath.applyGetters(localFieldArrVal, localFieldHydratedValue[localFieldArrIndex]));
|
||||
} else {
|
||||
ret = localFieldPath.applyGetters(localFieldValue, hydratedDoc);
|
||||
}
|
||||
} else {
|
||||
ret = convertTo_id(utils.getValue(localField, doc), schema);
|
||||
}
|
||||
|
||||
const id = String(utils.getValue(foreignField, doc));
|
||||
options._docs[id] = Array.isArray(ret) ? ret.slice() : ret;
|
||||
|
||||
let match = get(options, 'match', null) ||
|
||||
get(currentOptions, 'match', null) ||
|
||||
get(options, 'virtual.options.options.match', null);
|
||||
|
||||
const hasMatchFunction = typeof match === 'function';
|
||||
if (hasMatchFunction) {
|
||||
match = match.call(doc, doc);
|
||||
}
|
||||
|
||||
let k = modelNames.length;
|
||||
while (k--) {
|
||||
modelName = modelNames[k];
|
||||
if (modelName == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// `PopulateOptions#connection`: if the model is passed as a string, the
|
||||
// connection matters because different connections have different models.
|
||||
const connection = options.connection != null ? options.connection : model.db;
|
||||
|
||||
try {
|
||||
Model = originalModel && originalModel[modelSymbol] ?
|
||||
originalModel :
|
||||
modelName[modelSymbol] ? modelName : connection.model(modelName);
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
let ids = ret;
|
||||
const flat = Array.isArray(ret) ? utils.array.flatten(ret) : [];
|
||||
if (isRefPath && Array.isArray(ret) && flat.length === modelNames.length) {
|
||||
ids = flat.filter((val, i) => modelNames[i] === modelName);
|
||||
}
|
||||
|
||||
if (!available[modelName]) {
|
||||
currentOptions = {
|
||||
model: Model
|
||||
};
|
||||
|
||||
if (isVirtual && virtual.options && virtual.options.options) {
|
||||
currentOptions.options = utils.clone(virtual.options.options);
|
||||
}
|
||||
utils.merge(currentOptions, options);
|
||||
|
||||
// Used internally for checking what model was used to populate this
|
||||
// path.
|
||||
options[populateModelSymbol] = Model;
|
||||
|
||||
available[modelName] = {
|
||||
model: Model,
|
||||
options: currentOptions,
|
||||
match: hasMatchFunction ? [match] : match,
|
||||
docs: [doc],
|
||||
ids: [ids],
|
||||
allIds: [ret],
|
||||
localField: new Set([localField]),
|
||||
foreignField: new Set([foreignField]),
|
||||
justOne: justOne,
|
||||
isVirtual: isVirtual,
|
||||
virtual: virtual,
|
||||
count: count,
|
||||
[populateModelSymbol]: Model
|
||||
};
|
||||
map.push(available[modelName]);
|
||||
} else {
|
||||
available[modelName].localField.add(localField);
|
||||
available[modelName].foreignField.add(foreignField);
|
||||
available[modelName].docs.push(doc);
|
||||
available[modelName].ids.push(ids);
|
||||
available[modelName].allIds.push(ret);
|
||||
if (hasMatchFunction) {
|
||||
available[modelName].match.push(match);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _getModelNames(doc, schema) {
|
||||
let modelNames;
|
||||
let discriminatorKey;
|
||||
let isRefPath = false;
|
||||
|
||||
if (schema && schema.caster) {
|
||||
schema = schema.caster;
|
||||
}
|
||||
if (schema && schema.$isSchemaMap) {
|
||||
schema = schema.$__schemaType;
|
||||
}
|
||||
|
||||
if (!schema && model.discriminators) {
|
||||
discriminatorKey = model.schema.discriminatorMapping.key;
|
||||
}
|
||||
|
||||
refPath = schema && schema.options && schema.options.refPath;
|
||||
|
||||
const normalizedRefPath = normalizeRefPath(refPath, doc, options.path);
|
||||
|
||||
if (modelNameFromQuery) {
|
||||
modelNames = [modelNameFromQuery]; // query options
|
||||
} else if (normalizedRefPath) {
|
||||
if (options._queryProjection != null && isPathExcluded(options._queryProjection, normalizedRefPath)) {
|
||||
throw new MongooseError('refPath `' + normalizedRefPath +
|
||||
'` must not be excluded in projection, got ' +
|
||||
util.inspect(options._queryProjection));
|
||||
}
|
||||
|
||||
if (modelSchema.virtuals.hasOwnProperty(normalizedRefPath) && doc.$__ == null) {
|
||||
modelNames = [modelSchema.virtuals[normalizedRefPath].applyGetters(void 0, doc)];
|
||||
} else {
|
||||
modelNames = utils.getValue(normalizedRefPath, doc);
|
||||
}
|
||||
|
||||
if (Array.isArray(modelNames)) {
|
||||
modelNames = utils.array.flatten(modelNames);
|
||||
}
|
||||
|
||||
isRefPath = true;
|
||||
} else {
|
||||
let modelForCurrentDoc = model;
|
||||
let schemaForCurrentDoc;
|
||||
|
||||
if (!schema && discriminatorKey) {
|
||||
modelForFindSchema = utils.getValue(discriminatorKey, doc);
|
||||
|
||||
if (modelForFindSchema) {
|
||||
try {
|
||||
modelForCurrentDoc = model.db.model(modelForFindSchema);
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
schemaForCurrentDoc = modelForCurrentDoc.schema._getSchema(options.path);
|
||||
|
||||
if (schemaForCurrentDoc && schemaForCurrentDoc.caster) {
|
||||
schemaForCurrentDoc = schemaForCurrentDoc.caster;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
schemaForCurrentDoc = schema;
|
||||
}
|
||||
const _virtualRes = getVirtual(modelForCurrentDoc.schema, options.path);
|
||||
const virtual = _virtualRes == null ? null : _virtualRes.virtual;
|
||||
|
||||
let ref;
|
||||
let refPath;
|
||||
if ((ref = get(schemaForCurrentDoc, 'options.ref')) != null) {
|
||||
ref = handleRefFunction(ref, doc);
|
||||
modelNames = [ref];
|
||||
} else if ((ref = get(virtual, 'options.ref')) != null) {
|
||||
ref = handleRefFunction(ref, doc);
|
||||
|
||||
// When referencing nested arrays, the ref should be an Array
|
||||
// of modelNames.
|
||||
if (Array.isArray(ref)) {
|
||||
modelNames = ref;
|
||||
} else {
|
||||
modelNames = [ref];
|
||||
}
|
||||
|
||||
isVirtual = true;
|
||||
} else if ((refPath = get(schemaForCurrentDoc, 'options.refPath')) != null) {
|
||||
isRefPath = true;
|
||||
refPath = normalizeRefPath(refPath, doc, options.path);
|
||||
modelNames = utils.getValue(refPath, doc);
|
||||
if (Array.isArray(modelNames)) {
|
||||
modelNames = utils.array.flatten(modelNames);
|
||||
}
|
||||
} else {
|
||||
// We may have a discriminator, in which case we don't want to
|
||||
// populate using the base model by default
|
||||
modelNames = discriminatorKey ? null : [model.modelName];
|
||||
}
|
||||
}
|
||||
|
||||
if (!modelNames) {
|
||||
return { modelNames: modelNames, isRefPath: isRefPath };
|
||||
}
|
||||
|
||||
if (!Array.isArray(modelNames)) {
|
||||
modelNames = [modelNames];
|
||||
}
|
||||
|
||||
return { modelNames: modelNames, isRefPath: isRefPath };
|
||||
}
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function handleRefFunction(ref, doc) {
|
||||
if (typeof ref === 'function' && !ref[modelSymbol]) {
|
||||
return ref.call(doc, doc);
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Retrieve the _id of `val` if a Document or Array of Documents.
|
||||
*
|
||||
* @param {Array|Document|Any} val
|
||||
* @return {Array|Document|Any}
|
||||
*/
|
||||
|
||||
function convertTo_id(val, schema) {
|
||||
if (val != null && val.$__ != null) return val._id;
|
||||
|
||||
if (Array.isArray(val)) {
|
||||
for (let i = 0; i < val.length; ++i) {
|
||||
if (val[i] != null && val[i].$__ != null) {
|
||||
val[i] = val[i]._id;
|
||||
}
|
||||
}
|
||||
if (val.isMongooseArray && val.$schema()) {
|
||||
return val.$schema().cast(val, val.$parent());
|
||||
}
|
||||
|
||||
return [].concat(val);
|
||||
}
|
||||
|
||||
// `populate('map')` may be an object if populating on a doc that hasn't
|
||||
// been hydrated yet
|
||||
if (val != null &&
|
||||
val.constructor.name === 'Object' &&
|
||||
// The intent here is we should only flatten the object if we expect
|
||||
// to get a Map in the end. Avoid doing this for mixed types.
|
||||
(schema == null || schema[schemaMixedSymbol] == null)) {
|
||||
const ret = [];
|
||||
for (const key of Object.keys(val)) {
|
||||
ret.push(val[key]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
// If doc has already been hydrated, e.g. `doc.populate('map').execPopulate()`
|
||||
// then `val` will already be a map
|
||||
if (val instanceof Map) {
|
||||
return Array.from(val.values());
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
const Mixed = require('../../schema/mixed');
|
||||
const get = require('../get');
|
||||
const leanPopulateMap = require('./leanPopulateMap');
|
||||
const mpath = require('mpath');
|
||||
|
||||
const populateModelSymbol = require('../symbols').populateModelSymbol;
|
||||
|
||||
/*!
|
||||
* @param {Schema} schema
|
||||
* @param {Object} doc POJO
|
||||
* @param {string} path
|
||||
*/
|
||||
|
||||
module.exports = function getSchemaTypes(schema, doc, path) {
|
||||
const pathschema = schema.path(path);
|
||||
const topLevelDoc = doc;
|
||||
|
||||
if (pathschema) {
|
||||
return pathschema;
|
||||
}
|
||||
|
||||
function search(parts, schema, subdoc, nestedPath) {
|
||||
let p = parts.length + 1;
|
||||
let foundschema;
|
||||
let trypath;
|
||||
|
||||
while (p--) {
|
||||
trypath = parts.slice(0, p).join('.');
|
||||
foundschema = schema.path(trypath);
|
||||
|
||||
if (foundschema == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (foundschema.caster) {
|
||||
// array of Mixed?
|
||||
if (foundschema.caster instanceof Mixed) {
|
||||
return foundschema.caster;
|
||||
}
|
||||
|
||||
let schemas = null;
|
||||
if (doc != null && foundschema.schema != null && foundschema.schema.discriminators != null) {
|
||||
const discriminators = foundschema.schema.discriminators;
|
||||
const discriminatorKeyPath = trypath + '.' +
|
||||
foundschema.schema.options.discriminatorKey;
|
||||
const keys = subdoc ? mpath.get(discriminatorKeyPath, subdoc) || [] : [];
|
||||
schemas = Object.keys(discriminators).
|
||||
reduce(function(cur, discriminator) {
|
||||
if (keys.indexOf(discriminator) !== -1) {
|
||||
cur.push(discriminators[discriminator]);
|
||||
}
|
||||
return cur;
|
||||
}, []);
|
||||
}
|
||||
|
||||
// Now that we found the array, we need to check if there
|
||||
// are remaining document paths to look up for casting.
|
||||
// Also we need to handle array.$.path since schema.path
|
||||
// doesn't work for that.
|
||||
// If there is no foundschema.schema we are dealing with
|
||||
// a path like array.$
|
||||
if (p !== parts.length && foundschema.schema) {
|
||||
let ret;
|
||||
if (parts[p] === '$') {
|
||||
if (p + 1 === parts.length) {
|
||||
// comments.$
|
||||
return foundschema;
|
||||
}
|
||||
// comments.$.comments.$.title
|
||||
ret = search(
|
||||
parts.slice(p + 1),
|
||||
schema,
|
||||
subdoc ? mpath.get(trypath, subdoc) : null,
|
||||
nestedPath.concat(parts.slice(0, p))
|
||||
);
|
||||
if (ret) {
|
||||
ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
|
||||
!foundschema.schema.$isSingleNested;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (schemas != null && schemas.length > 0) {
|
||||
ret = [];
|
||||
for (let i = 0; i < schemas.length; ++i) {
|
||||
const _ret = search(
|
||||
parts.slice(p),
|
||||
schemas[i],
|
||||
subdoc ? mpath.get(trypath, subdoc) : null,
|
||||
nestedPath.concat(parts.slice(0, p))
|
||||
);
|
||||
if (_ret != null) {
|
||||
_ret.$isUnderneathDocArray = _ret.$isUnderneathDocArray ||
|
||||
!foundschema.schema.$isSingleNested;
|
||||
if (_ret.$isUnderneathDocArray) {
|
||||
ret.$isUnderneathDocArray = true;
|
||||
}
|
||||
ret.push(_ret);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
ret = search(
|
||||
parts.slice(p),
|
||||
foundschema.schema,
|
||||
subdoc ? mpath.get(trypath, subdoc) : null,
|
||||
nestedPath.concat(parts.slice(0, p))
|
||||
);
|
||||
|
||||
if (ret) {
|
||||
ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
|
||||
!foundschema.schema.$isSingleNested;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
} else if (p !== parts.length &&
|
||||
foundschema.$isMongooseArray &&
|
||||
foundschema.casterConstructor.$isMongooseArray) {
|
||||
// Nested arrays. Drill down to the bottom of the nested array.
|
||||
// Ignore discriminators.
|
||||
let type = foundschema;
|
||||
while (type.$isMongooseArray && !type.$isMongooseDocumentArray) {
|
||||
type = type.casterConstructor;
|
||||
}
|
||||
return search(
|
||||
parts.slice(p),
|
||||
type.schema,
|
||||
null,
|
||||
nestedPath.concat(parts.slice(0, p))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const fullPath = nestedPath.concat([trypath]).join('.');
|
||||
if (topLevelDoc.$__ && topLevelDoc.populated(fullPath) && p < parts.length) {
|
||||
const model = doc.$__.populated[fullPath].options[populateModelSymbol];
|
||||
if (model != null) {
|
||||
const ret = search(
|
||||
parts.slice(p),
|
||||
model.schema,
|
||||
subdoc ? mpath.get(trypath, subdoc) : null,
|
||||
nestedPath.concat(parts.slice(0, p))
|
||||
);
|
||||
|
||||
if (ret) {
|
||||
ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
|
||||
!model.schema.$isSingleNested;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
const _val = get(topLevelDoc, trypath);
|
||||
if (_val != null) {
|
||||
const model = Array.isArray(_val) && _val.length > 0 ?
|
||||
leanPopulateMap.get(_val[0]) :
|
||||
leanPopulateMap.get(_val);
|
||||
// Populated using lean, `leanPopulateMap` value is the foreign model
|
||||
const schema = model != null ? model.schema : null;
|
||||
if (schema != null) {
|
||||
const ret = search(
|
||||
parts.slice(p),
|
||||
schema,
|
||||
subdoc ? mpath.get(trypath, subdoc) : null,
|
||||
nestedPath.concat(parts.slice(0, p))
|
||||
);
|
||||
|
||||
if (ret) {
|
||||
ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
|
||||
!schema.$isSingleNested;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return foundschema;
|
||||
}
|
||||
}
|
||||
|
||||
// look for arrays
|
||||
const parts = path.split('.');
|
||||
for (let i = 0; i < parts.length; ++i) {
|
||||
if (parts[i] === '$') {
|
||||
// Re: gh-5628, because `schema.path()` doesn't take $ into account.
|
||||
parts[i] = '0';
|
||||
}
|
||||
}
|
||||
return search(parts, schema, doc, []);
|
||||
};
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = getVirtual;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function getVirtual(schema, name) {
|
||||
if (schema.virtuals[name]) {
|
||||
return { virtual: schema.virtuals[name], path: void 0 };
|
||||
}
|
||||
const parts = name.split('.');
|
||||
let cur = '';
|
||||
let nestedSchemaPath = '';
|
||||
for (let i = 0; i < parts.length; ++i) {
|
||||
cur += (cur.length > 0 ? '.' : '') + parts[i];
|
||||
if (schema.virtuals[cur]) {
|
||||
if (i === parts.length - 1) {
|
||||
return { virtual: schema.virtuals[cur], path: nestedSchemaPath };
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (schema.nested[cur]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (schema.paths[cur] && schema.paths[cur].schema) {
|
||||
schema = schema.paths[cur].schema;
|
||||
const rest = parts.slice(i + 1).join('.');
|
||||
|
||||
if (schema.virtuals[rest]) {
|
||||
if (i === parts.length - 2) {
|
||||
return {
|
||||
virtual: schema.virtuals[rest],
|
||||
nestedSchemaPath:[nestedSchemaPath, cur].filter(v => !!v).join('.')
|
||||
};
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i + 1 < parts.length && schema.discriminators) {
|
||||
for (const key of Object.keys(schema.discriminators)) {
|
||||
const res = getVirtual(schema.discriminators[key], rest);
|
||||
if (res != null) {
|
||||
const _path = [nestedSchemaPath, cur, res.nestedSchemaPath].
|
||||
filter(v => !!v).join('.');
|
||||
return {
|
||||
virtual: res.virtual,
|
||||
nestedSchemaPath: _path
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nestedSchemaPath += (nestedSchemaPath.length > 0 ? '.' : '') + cur;
|
||||
cur = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = new WeakMap();
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function normalizeRefPath(refPath, doc, populatedPath) {
|
||||
if (refPath == null) {
|
||||
return refPath;
|
||||
}
|
||||
|
||||
if (typeof refPath === 'function') {
|
||||
refPath = refPath.call(doc, doc, populatedPath);
|
||||
}
|
||||
|
||||
// If populated path has numerics, the end `refPath` should too. For example,
|
||||
// if populating `a.0.b` instead of `a.b` and `b` has `refPath = a.c`, we
|
||||
// should return `a.0.c` for the refPath.
|
||||
const hasNumericProp = /(\.\d+$|\.\d+\.)/g;
|
||||
|
||||
if (hasNumericProp.test(populatedPath)) {
|
||||
const chunks = populatedPath.split(hasNumericProp);
|
||||
|
||||
if (chunks[chunks.length - 1] === '') {
|
||||
throw new Error('Can\'t populate individual element in an array');
|
||||
}
|
||||
|
||||
let _refPath = '';
|
||||
let _remaining = refPath;
|
||||
// 2nd, 4th, etc. will be numeric props. For example: `[ 'a', '.0.', 'b' ]`
|
||||
for (let i = 0; i < chunks.length; i += 2) {
|
||||
const chunk = chunks[i];
|
||||
if (_remaining.startsWith(chunk + '.')) {
|
||||
_refPath += _remaining.substr(0, chunk.length) + chunks[i + 1];
|
||||
_remaining = _remaining.substr(chunk.length + 1);
|
||||
} else if (i === chunks.length - 1) {
|
||||
_refPath += _remaining;
|
||||
_remaining = '';
|
||||
break;
|
||||
} else {
|
||||
throw new Error('Could not normalize ref path, chunk ' + chunk + ' not in populated path');
|
||||
}
|
||||
}
|
||||
|
||||
return _refPath;
|
||||
}
|
||||
|
||||
return refPath;
|
||||
};
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
const MongooseError = require('../../error/mongooseError');
|
||||
const util = require('util');
|
||||
|
||||
module.exports = validateRef;
|
||||
|
||||
function validateRef(ref, path) {
|
||||
if (typeof ref === 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof ref === 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new MongooseError('Invalid ref at path "' + path + '". Got ' +
|
||||
util.inspect(ref, { depth: 0 }));
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
if (typeof jest !== 'undefined' && typeof window !== 'undefined') {
|
||||
console.warn('Mongoose: looks like you\'re trying to test a Mongoose app ' +
|
||||
'with Jest\'s default jsdom test environment. Please make sure you read ' +
|
||||
'Mongoose\'s docs on configuring Jest to test Node.js apps: ' +
|
||||
'http://mongoosejs.com/docs/jest.html');
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function isDefiningProjection(val) {
|
||||
if (val == null) {
|
||||
// `undefined` or `null` become exclusive projections
|
||||
return true;
|
||||
}
|
||||
if (typeof val === 'object') {
|
||||
// Only cases where a value does **not** define whether the whole projection
|
||||
// is inclusive or exclusive are `$meta` and `$slice`.
|
||||
return !('$meta' in val) && !('$slice' in val);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
const isDefiningProjection = require('./isDefiningProjection');
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function isExclusive(projection) {
|
||||
const keys = Object.keys(projection);
|
||||
let ki = keys.length;
|
||||
let exclude = null;
|
||||
|
||||
if (ki === 1 && keys[0] === '_id') {
|
||||
exclude = !!projection[keys[ki]];
|
||||
} else {
|
||||
while (ki--) {
|
||||
// Does this projection explicitly define inclusion/exclusion?
|
||||
// Explicitly avoid `$meta` and `$slice`
|
||||
if (keys[ki] !== '_id' && isDefiningProjection(projection[keys[ki]])) {
|
||||
exclude = !projection[keys[ki]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return exclude;
|
||||
};
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
const isDefiningProjection = require('./isDefiningProjection');
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function isInclusive(projection) {
|
||||
if (projection == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const props = Object.keys(projection);
|
||||
const numProps = props.length;
|
||||
if (numProps === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < numProps; ++i) {
|
||||
const prop = props[i];
|
||||
// Plus paths can't define the projection (see gh-7050)
|
||||
if (prop.startsWith('+')) {
|
||||
continue;
|
||||
}
|
||||
// If field is truthy (1, true, etc.) and not an object, then this
|
||||
// projection must be inclusive. If object, assume its $meta, $slice, etc.
|
||||
if (isDefiningProjection(projection[prop]) && !!projection[prop]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
const isDefiningProjection = require('./isDefiningProjection');
|
||||
|
||||
/*!
|
||||
* Determines if `path` is excluded by `projection`
|
||||
*
|
||||
* @param {Object} projection
|
||||
* @param {string} path
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
module.exports = function isPathExcluded(projection, path) {
|
||||
if (path === '_id') {
|
||||
return projection._id === 0;
|
||||
}
|
||||
|
||||
const paths = Object.keys(projection);
|
||||
let type = null;
|
||||
|
||||
for (const _path of paths) {
|
||||
if (isDefiningProjection(projection[_path])) {
|
||||
type = projection[path] === 1 ? 'inclusive' : 'exclusive';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'inclusive') {
|
||||
return projection[path] !== 1;
|
||||
}
|
||||
if (type === 'exclusive') {
|
||||
return projection[path] === 0;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function isPathSelectedInclusive(fields, path) {
|
||||
const chunks = path.split('.');
|
||||
let cur = '';
|
||||
let j;
|
||||
let keys;
|
||||
let numKeys;
|
||||
for (let i = 0; i < chunks.length; ++i) {
|
||||
cur += cur.length ? '.' : '' + chunks[i];
|
||||
if (fields[cur]) {
|
||||
keys = Object.keys(fields);
|
||||
numKeys = keys.length;
|
||||
for (j = 0; j < numKeys; ++j) {
|
||||
if (keys[i].indexOf(cur + '.') === 0 && keys[i].indexOf(path) !== 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
const utils = require('../../utils');
|
||||
|
||||
module.exports = function applyGlobalMaxTimeMS(options, model) {
|
||||
if (utils.hasUserDefinedProperty(options, 'maxTimeMS')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (utils.hasUserDefinedProperty(model.db.options, 'maxTimeMS')) {
|
||||
options.maxTimeMS = model.db.options.maxTimeMS;
|
||||
} else if (utils.hasUserDefinedProperty(model.base.options, 'maxTimeMS')) {
|
||||
options.maxTimeMS = model.base.options.maxTimeMS;
|
||||
}
|
||||
};
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = applyQueryMiddleware;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
applyQueryMiddleware.middlewareFunctions = [
|
||||
'count',
|
||||
'countDocuments',
|
||||
'deleteMany',
|
||||
'deleteOne',
|
||||
'distinct',
|
||||
'estimatedDocumentCount',
|
||||
'find',
|
||||
'findOne',
|
||||
'findOneAndDelete',
|
||||
'findOneAndRemove',
|
||||
'findOneAndReplace',
|
||||
'findOneAndUpdate',
|
||||
'remove',
|
||||
'replaceOne',
|
||||
'update',
|
||||
'updateMany',
|
||||
'updateOne',
|
||||
'validate'
|
||||
];
|
||||
|
||||
/*!
|
||||
* Apply query middleware
|
||||
*
|
||||
* @param {Query} query constructor
|
||||
* @param {Model} model
|
||||
*/
|
||||
|
||||
function applyQueryMiddleware(Query, model) {
|
||||
const kareemOptions = {
|
||||
useErrorHandlers: true,
|
||||
numCallbackParams: 1,
|
||||
nullResultByDefault: true
|
||||
};
|
||||
|
||||
const middleware = model.hooks.filter(hook => {
|
||||
if (hook.name === 'updateOne' || hook.name === 'deleteOne') {
|
||||
return hook.query == null || !!hook.query;
|
||||
}
|
||||
if (hook.name === 'remove') {
|
||||
return !!hook.query;
|
||||
}
|
||||
if (hook.name === 'validate') {
|
||||
return !!hook.query;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// `update()` thunk has a different name because `_update` was already taken
|
||||
Query.prototype._execUpdate = middleware.createWrapper('update',
|
||||
Query.prototype._execUpdate, null, kareemOptions);
|
||||
// `distinct()` thunk has a different name because `_distinct` was already taken
|
||||
Query.prototype.__distinct = middleware.createWrapper('distinct',
|
||||
Query.prototype.__distinct, null, kareemOptions);
|
||||
|
||||
// `validate()` doesn't have a thunk because it doesn't execute a query.
|
||||
Query.prototype.validate = middleware.createWrapper('validate',
|
||||
Query.prototype.validate, null, kareemOptions);
|
||||
|
||||
applyQueryMiddleware.middlewareFunctions.
|
||||
filter(v => v !== 'update' && v !== 'distinct' && v !== 'validate').
|
||||
forEach(fn => {
|
||||
Query.prototype[`_${fn}`] = middleware.createWrapper(fn,
|
||||
Query.prototype[`_${fn}`], null, kareemOptions);
|
||||
});
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function castFilterPath(query, schematype, val) {
|
||||
const ctx = query;
|
||||
const any$conditionals = Object.keys(val).some(function(k) {
|
||||
return k.startsWith('$') && k !== '$id' && k !== '$ref';
|
||||
});
|
||||
|
||||
if (!any$conditionals) {
|
||||
return schematype.castForQueryWrapper({
|
||||
val: val,
|
||||
context: ctx
|
||||
});
|
||||
}
|
||||
|
||||
const ks = Object.keys(val);
|
||||
|
||||
let k = ks.length;
|
||||
|
||||
while (k--) {
|
||||
const $cond = ks[k];
|
||||
const nested = val[$cond];
|
||||
|
||||
if ($cond === '$not') {
|
||||
if (nested && schematype && !schematype.caster) {
|
||||
const _keys = Object.keys(nested);
|
||||
if (_keys.length && _keys[0].startsWith('$')) {
|
||||
for (const key in nested) {
|
||||
nested[key] = schematype.castForQueryWrapper({
|
||||
$conditional: key,
|
||||
val: nested[key],
|
||||
context: ctx
|
||||
});
|
||||
}
|
||||
} else {
|
||||
val[$cond] = schematype.castForQueryWrapper({
|
||||
$conditional: $cond,
|
||||
val: nested,
|
||||
context: ctx
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// cast(schematype.caster ? schematype.caster.schema : schema, nested, options, context);
|
||||
} else {
|
||||
val[$cond] = schematype.castForQueryWrapper({
|
||||
$conditional: $cond,
|
||||
val: nested,
|
||||
context: ctx
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
};
|
||||
+471
@@ -0,0 +1,471 @@
|
||||
'use strict';
|
||||
|
||||
const CastError = require('../../error/cast');
|
||||
const StrictModeError = require('../../error/strict');
|
||||
const ValidationError = require('../../error/validation');
|
||||
const castNumber = require('../../cast/number');
|
||||
const cast = require('../../cast');
|
||||
const getEmbeddedDiscriminatorPath = require('./getEmbeddedDiscriminatorPath');
|
||||
const handleImmutable = require('./handleImmutable');
|
||||
const utils = require('../../utils');
|
||||
|
||||
/*!
|
||||
* Casts an update op based on the given schema
|
||||
*
|
||||
* @param {Schema} schema
|
||||
* @param {Object} obj
|
||||
* @param {Object} options
|
||||
* @param {Boolean} [options.overwrite] defaults to false
|
||||
* @param {Boolean|String} [options.strict] defaults to true
|
||||
* @param {Query} context passed to setters
|
||||
* @return {Boolean} true iff the update is non-empty
|
||||
*/
|
||||
|
||||
module.exports = function castUpdate(schema, obj, options, context, filter) {
|
||||
if (!obj) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const ops = Object.keys(obj);
|
||||
let i = ops.length;
|
||||
const ret = {};
|
||||
let hasKeys;
|
||||
let val;
|
||||
let hasDollarKey = false;
|
||||
const overwrite = options.overwrite;
|
||||
|
||||
filter = filter || {};
|
||||
|
||||
while (i--) {
|
||||
const op = ops[i];
|
||||
// if overwrite is set, don't do any of the special $set stuff
|
||||
if (op[0] !== '$' && !overwrite) {
|
||||
// fix up $set sugar
|
||||
if (!ret.$set) {
|
||||
if (obj.$set) {
|
||||
ret.$set = obj.$set;
|
||||
} else {
|
||||
ret.$set = {};
|
||||
}
|
||||
}
|
||||
ret.$set[op] = obj[op];
|
||||
ops.splice(i, 1);
|
||||
if (!~ops.indexOf('$set')) ops.push('$set');
|
||||
} else if (op === '$set') {
|
||||
if (!ret.$set) {
|
||||
ret[op] = obj[op];
|
||||
}
|
||||
} else {
|
||||
ret[op] = obj[op];
|
||||
}
|
||||
}
|
||||
|
||||
// cast each value
|
||||
i = ops.length;
|
||||
|
||||
// if we get passed {} for the update, we still need to respect that when it
|
||||
// is an overwrite scenario
|
||||
if (overwrite) {
|
||||
hasKeys = true;
|
||||
}
|
||||
|
||||
while (i--) {
|
||||
const op = ops[i];
|
||||
val = ret[op];
|
||||
hasDollarKey = hasDollarKey || op.startsWith('$');
|
||||
|
||||
if (val &&
|
||||
typeof val === 'object' &&
|
||||
!Buffer.isBuffer(val) &&
|
||||
(!overwrite || hasDollarKey)) {
|
||||
hasKeys |= walkUpdatePath(schema, val, op, options, context, filter);
|
||||
} else if (overwrite && ret && typeof ret === 'object') {
|
||||
// if we are just using overwrite, cast the query and then we will
|
||||
// *always* return the value, even if it is an empty object. We need to
|
||||
// set hasKeys above because we need to account for the case where the
|
||||
// user passes {} and wants to clobber the whole document
|
||||
// Also, _walkUpdatePath expects an operation, so give it $set since that
|
||||
// is basically what we're doing
|
||||
walkUpdatePath(schema, ret, '$set', options, context, filter);
|
||||
} else {
|
||||
const msg = 'Invalid atomic update value for ' + op + '. '
|
||||
+ 'Expected an object, received ' + typeof val;
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
if (op.startsWith('$') && utils.isEmptyObject(val)) {
|
||||
delete ret[op];
|
||||
}
|
||||
}
|
||||
|
||||
return hasKeys && ret;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Walk each path of obj and cast its values
|
||||
* according to its schema.
|
||||
*
|
||||
* @param {Schema} schema
|
||||
* @param {Object} obj - part of a query
|
||||
* @param {String} op - the atomic operator ($pull, $set, etc)
|
||||
* @param {Object} options
|
||||
* @param {Boolean|String} [options.strict]
|
||||
* @param {Boolean} [options.omitUndefined]
|
||||
* @param {Query} context
|
||||
* @param {String} pref - path prefix (internal only)
|
||||
* @return {Bool} true if this path has keys to update
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
|
||||
const strict = options.strict;
|
||||
const prefix = pref ? pref + '.' : '';
|
||||
const keys = Object.keys(obj);
|
||||
let i = keys.length;
|
||||
let hasKeys = false;
|
||||
let schematype;
|
||||
let key;
|
||||
let val;
|
||||
|
||||
let aggregatedError = null;
|
||||
|
||||
let useNestedStrict;
|
||||
if (options.useNestedStrict === undefined) {
|
||||
useNestedStrict = schema.options.useNestedStrict;
|
||||
} else {
|
||||
useNestedStrict = options.useNestedStrict;
|
||||
}
|
||||
|
||||
while (i--) {
|
||||
key = keys[i];
|
||||
val = obj[key];
|
||||
|
||||
// `$pull` is special because we need to cast the RHS as a query, not as
|
||||
// an update.
|
||||
if (op === '$pull') {
|
||||
schematype = schema._getSchema(prefix + key);
|
||||
if (schematype != null && schematype.schema != null) {
|
||||
obj[key] = cast(schematype.schema, obj[key], options, context);
|
||||
hasKeys = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (val && val.constructor.name === 'Object') {
|
||||
// watch for embedded doc schemas
|
||||
schematype = schema._getSchema(prefix + key);
|
||||
|
||||
if (handleImmutable(schematype, strict, obj, key, prefix + key, context)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (schematype && schematype.caster && op in castOps) {
|
||||
// embedded doc schema
|
||||
if ('$each' in val) {
|
||||
hasKeys = true;
|
||||
try {
|
||||
obj[key] = {
|
||||
$each: castUpdateVal(schematype, val.$each, op, key, context, prefix + key)
|
||||
};
|
||||
} catch (error) {
|
||||
aggregatedError = _handleCastError(error, context, key, aggregatedError);
|
||||
}
|
||||
|
||||
if (val.$slice != null) {
|
||||
obj[key].$slice = val.$slice | 0;
|
||||
}
|
||||
|
||||
if (val.$sort) {
|
||||
obj[key].$sort = val.$sort;
|
||||
}
|
||||
|
||||
if (!!val.$position || val.$position === 0) {
|
||||
obj[key].$position = val.$position;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
obj[key] = castUpdateVal(schematype, val, op, key, context, prefix + key);
|
||||
} catch (error) {
|
||||
aggregatedError = _handleCastError(error, context, key, aggregatedError);
|
||||
}
|
||||
|
||||
if (options.omitUndefined && obj[key] === void 0) {
|
||||
delete obj[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
hasKeys = true;
|
||||
}
|
||||
} else if ((op === '$currentDate') || (op in castOps && schematype)) {
|
||||
// $currentDate can take an object
|
||||
try {
|
||||
obj[key] = castUpdateVal(schematype, val, op, key, context, prefix + key);
|
||||
} catch (error) {
|
||||
aggregatedError = _handleCastError(error, context, key, aggregatedError);
|
||||
}
|
||||
|
||||
if (options.omitUndefined && obj[key] === void 0) {
|
||||
delete obj[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
hasKeys = true;
|
||||
} else {
|
||||
const pathToCheck = (prefix + key);
|
||||
const v = schema._getPathType(pathToCheck);
|
||||
let _strict = strict;
|
||||
if (useNestedStrict &&
|
||||
v &&
|
||||
v.schema &&
|
||||
'strict' in v.schema.options) {
|
||||
_strict = v.schema.options.strict;
|
||||
}
|
||||
|
||||
if (v.pathType === 'undefined') {
|
||||
if (_strict === 'throw') {
|
||||
throw new StrictModeError(pathToCheck);
|
||||
} else if (_strict) {
|
||||
delete obj[key];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// gh-2314
|
||||
// we should be able to set a schema-less field
|
||||
// to an empty object literal
|
||||
hasKeys |= walkUpdatePath(schema, val, op, options, context, filter, prefix + key) ||
|
||||
(utils.isObject(val) && Object.keys(val).length === 0);
|
||||
}
|
||||
} else {
|
||||
const checkPath = (key === '$each' || key === '$or' || key === '$and' || key === '$in') ?
|
||||
pref : prefix + key;
|
||||
schematype = schema._getSchema(checkPath);
|
||||
|
||||
// You can use `$setOnInsert` with immutable keys
|
||||
if (op !== '$setOnInsert' &&
|
||||
handleImmutable(schematype, strict, obj, key, prefix + key, context)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pathDetails = schema._getPathType(checkPath);
|
||||
|
||||
// If no schema type, check for embedded discriminators
|
||||
if (schematype == null) {
|
||||
const _res = getEmbeddedDiscriminatorPath(schema, obj, filter, checkPath);
|
||||
if (_res.schematype != null) {
|
||||
schematype = _res.schematype;
|
||||
pathDetails = _res.type;
|
||||
}
|
||||
}
|
||||
|
||||
let isStrict = strict;
|
||||
if (useNestedStrict &&
|
||||
pathDetails &&
|
||||
pathDetails.schema &&
|
||||
'strict' in pathDetails.schema.options) {
|
||||
isStrict = pathDetails.schema.options.strict;
|
||||
}
|
||||
|
||||
const skip = isStrict &&
|
||||
!schematype &&
|
||||
!/real|nested/.test(pathDetails.pathType);
|
||||
|
||||
if (skip) {
|
||||
// Even if strict is `throw`, avoid throwing an error because of
|
||||
// virtuals because of #6731
|
||||
if (isStrict === 'throw' && schema.virtuals[checkPath] == null) {
|
||||
throw new StrictModeError(prefix + key);
|
||||
} else {
|
||||
delete obj[key];
|
||||
}
|
||||
} else {
|
||||
// gh-1845 temporary fix: ignore $rename. See gh-3027 for tracking
|
||||
// improving this.
|
||||
if (op === '$rename') {
|
||||
hasKeys = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
obj[key] = castUpdateVal(schematype, val, op, key, context, prefix + key);
|
||||
} catch (error) {
|
||||
aggregatedError = _handleCastError(error, context, key, aggregatedError);
|
||||
}
|
||||
|
||||
if (Array.isArray(obj[key]) && (op === '$addToSet' || op === '$push') && key !== '$each') {
|
||||
if (schematype && schematype.caster && !schematype.caster.$isMongooseArray) {
|
||||
obj[key] = { $each: obj[key] };
|
||||
}
|
||||
}
|
||||
|
||||
if (options.omitUndefined && obj[key] === void 0) {
|
||||
delete obj[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
hasKeys = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (aggregatedError != null) {
|
||||
throw aggregatedError;
|
||||
}
|
||||
|
||||
return hasKeys;
|
||||
}
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function _handleCastError(error, query, key, aggregatedError) {
|
||||
if (typeof query !== 'object' || !query.options.multipleCastError) {
|
||||
throw error;
|
||||
}
|
||||
aggregatedError = aggregatedError || new ValidationError();
|
||||
aggregatedError.addError(key, error);
|
||||
return aggregatedError;
|
||||
}
|
||||
|
||||
/*!
|
||||
* These operators should be cast to numbers instead
|
||||
* of their path schema type.
|
||||
*/
|
||||
|
||||
const numberOps = {
|
||||
$pop: 1,
|
||||
$inc: 1
|
||||
};
|
||||
|
||||
/*!
|
||||
* These ops require no casting because the RHS doesn't do anything.
|
||||
*/
|
||||
|
||||
const noCastOps = {
|
||||
$unset: 1
|
||||
};
|
||||
|
||||
/*!
|
||||
* These operators require casting docs
|
||||
* to real Documents for Update operations.
|
||||
*/
|
||||
|
||||
const castOps = {
|
||||
$push: 1,
|
||||
$addToSet: 1,
|
||||
$set: 1,
|
||||
$setOnInsert: 1
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
const overwriteOps = {
|
||||
$set: 1,
|
||||
$setOnInsert: 1
|
||||
};
|
||||
|
||||
/*!
|
||||
* Casts `val` according to `schema` and atomic `op`.
|
||||
*
|
||||
* @param {SchemaType} schema
|
||||
* @param {Object} val
|
||||
* @param {String} op - the atomic operator ($pull, $set, etc)
|
||||
* @param {String} $conditional
|
||||
* @param {Query} context
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function castUpdateVal(schema, val, op, $conditional, context, path) {
|
||||
if (!schema) {
|
||||
// non-existing schema path
|
||||
if (op in numberOps) {
|
||||
try {
|
||||
return castNumber(val);
|
||||
} catch (err) {
|
||||
throw new CastError('number', val, path);
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
const cond = schema.caster && op in castOps &&
|
||||
(utils.isObject(val) || Array.isArray(val));
|
||||
if (cond && !overwriteOps[op]) {
|
||||
// Cast values for ops that add data to MongoDB.
|
||||
// Ensures embedded documents get ObjectIds etc.
|
||||
let schemaArrayDepth = 0;
|
||||
let cur = schema;
|
||||
while (cur.$isMongooseArray) {
|
||||
++schemaArrayDepth;
|
||||
cur = cur.caster;
|
||||
}
|
||||
let arrayDepth = 0;
|
||||
let _val = val;
|
||||
while (Array.isArray(_val)) {
|
||||
++arrayDepth;
|
||||
_val = _val[0];
|
||||
}
|
||||
|
||||
const additionalNesting = schemaArrayDepth - arrayDepth;
|
||||
while (arrayDepth < schemaArrayDepth) {
|
||||
val = [val];
|
||||
++arrayDepth;
|
||||
}
|
||||
|
||||
let tmp = schema.applySetters(Array.isArray(val) ? val : [val], context);
|
||||
|
||||
for (let i = 0; i < additionalNesting; ++i) {
|
||||
tmp = tmp[0];
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
if (op in noCastOps) {
|
||||
return val;
|
||||
}
|
||||
if (op in numberOps) {
|
||||
// Null and undefined not allowed for $pop, $inc
|
||||
if (val == null) {
|
||||
throw new CastError('number', val, schema.path);
|
||||
}
|
||||
if (op === '$inc') {
|
||||
// Support `$inc` with long, int32, etc. (gh-4283)
|
||||
return schema.castForQueryWrapper({
|
||||
val: val,
|
||||
context: context
|
||||
});
|
||||
}
|
||||
try {
|
||||
return castNumber(val);
|
||||
} catch (error) {
|
||||
throw new CastError('number', val, schema.path);
|
||||
}
|
||||
}
|
||||
if (op === '$currentDate') {
|
||||
if (typeof val === 'object') {
|
||||
return {$type: val.$type};
|
||||
}
|
||||
return Boolean(val);
|
||||
}
|
||||
|
||||
if (/^\$/.test($conditional)) {
|
||||
return schema.castForQueryWrapper({
|
||||
$conditional: $conditional,
|
||||
val: val,
|
||||
context: context
|
||||
});
|
||||
}
|
||||
|
||||
if (overwriteOps[op]) {
|
||||
return schema.castForQueryWrapper({
|
||||
val: val,
|
||||
context: context,
|
||||
$skipQueryCastForUpdate: val != null && schema.$isMongooseArray && schema.$fullPath != null && !schema.$fullPath.match(/\d+$/)
|
||||
});
|
||||
}
|
||||
|
||||
return schema.castForQueryWrapper({ val: val, context: context });
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
'use strict';
|
||||
|
||||
const helpers = require('../../queryhelpers');
|
||||
|
||||
module.exports = completeMany;
|
||||
|
||||
/*!
|
||||
* Given a model and an array of docs, hydrates all the docs to be instances
|
||||
* of the model. Used to initialize docs returned from the db from `find()`
|
||||
*
|
||||
* @param {Model} model
|
||||
* @param {Array} docs
|
||||
* @param {Object} fields the projection used, including `select` from schemas
|
||||
* @param {Object} userProvidedFields the user-specified projection
|
||||
* @param {Object} opts
|
||||
* @param {Array} [opts.populated]
|
||||
* @param {ClientSession} [opts.session]
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
function completeMany(model, docs, fields, userProvidedFields, opts, callback) {
|
||||
const arr = [];
|
||||
let count = docs.length;
|
||||
const len = count;
|
||||
let error = null;
|
||||
|
||||
function init(_error) {
|
||||
if (_error != null) {
|
||||
error = error || _error;
|
||||
}
|
||||
if (error != null) {
|
||||
--count || process.nextTick(() => callback(error));
|
||||
return;
|
||||
}
|
||||
--count || process.nextTick(() => callback(error, arr));
|
||||
}
|
||||
|
||||
for (let i = 0; i < len; ++i) {
|
||||
arr[i] = helpers.createModel(model, docs[i], fields, userProvidedFields);
|
||||
try {
|
||||
arr[i].init(docs[i], opts, init);
|
||||
} catch (error) {
|
||||
init(error);
|
||||
}
|
||||
arr[i].$session(opts.session);
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
'use strict';
|
||||
|
||||
const cleanPositionalOperators = require('../schema/cleanPositionalOperators');
|
||||
const get = require('../get');
|
||||
|
||||
/*!
|
||||
* Like `schema.path()`, except with a document, because impossible to
|
||||
* determine path type without knowing the embedded discriminator key.
|
||||
*/
|
||||
|
||||
module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, path) {
|
||||
const parts = path.split('.');
|
||||
let schematype = null;
|
||||
let type = 'adhocOrUndefined';
|
||||
|
||||
filter = filter || {};
|
||||
update = update || {};
|
||||
|
||||
for (let i = 0; i < parts.length; ++i) {
|
||||
const subpath = cleanPositionalOperators(parts.slice(0, i + 1).join('.'));
|
||||
schematype = schema.path(subpath);
|
||||
if (schematype == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
type = schema.pathType(subpath);
|
||||
if ((schematype.$isSingleNested || schematype.$isMongooseDocumentArrayElement) &&
|
||||
schematype.schema.discriminators != null) {
|
||||
const discriminators = schematype.schema.discriminators;
|
||||
const key = get(schematype, 'schema.options.discriminatorKey');
|
||||
const discriminatorValuePath = subpath + '.' + key;
|
||||
const discriminatorFilterPath =
|
||||
discriminatorValuePath.replace(/\.\d+\./, '.');
|
||||
let discriminatorKey = null;
|
||||
|
||||
if (discriminatorValuePath in filter) {
|
||||
discriminatorKey = filter[discriminatorValuePath];
|
||||
}
|
||||
if (discriminatorFilterPath in filter) {
|
||||
discriminatorKey = filter[discriminatorFilterPath];
|
||||
}
|
||||
const wrapperPath = subpath.replace(/\.\d+$/, '');
|
||||
if (schematype.$isMongooseDocumentArrayElement &&
|
||||
get(filter[wrapperPath], '$elemMatch.' + key) != null) {
|
||||
discriminatorKey = filter[wrapperPath].$elemMatch[key];
|
||||
}
|
||||
|
||||
if (discriminatorKey == null || discriminators[discriminatorKey] == null) {
|
||||
continue;
|
||||
}
|
||||
const rest = parts.slice(i + 1).join('.');
|
||||
schematype = discriminators[discriminatorKey].path(rest);
|
||||
if (schematype != null) {
|
||||
type = discriminators[discriminatorKey]._getPathType(rest);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { type: type, schematype: schematype };
|
||||
};
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
const StrictModeError = require('../../error/strict');
|
||||
|
||||
module.exports = function handleImmutable(schematype, strict, obj, key, fullPath, ctx) {
|
||||
if (schematype == null || !schematype.options || !schematype.options.immutable) {
|
||||
return false;
|
||||
}
|
||||
let immutable = schematype.options.immutable;
|
||||
|
||||
if (typeof immutable === 'function') {
|
||||
immutable = immutable.call(ctx, ctx);
|
||||
}
|
||||
if (!immutable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strict === false) {
|
||||
return false;
|
||||
}
|
||||
if (strict === 'throw') {
|
||||
throw new StrictModeError(null,
|
||||
`Field ${fullPath} is immutable and strict = 'throw'`);
|
||||
}
|
||||
delete obj[key];
|
||||
return true;
|
||||
};
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function(obj) {
|
||||
const keys = Object.keys(obj);
|
||||
const len = keys.length;
|
||||
for (let i = 0; i < len; ++i) {
|
||||
if (keys[i].startsWith('$')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function selectPopulatedFields(query) {
|
||||
const opts = query._mongooseOptions;
|
||||
|
||||
if (opts.populate != null) {
|
||||
const paths = Object.keys(opts.populate);
|
||||
const userProvidedFields = query._userProvidedFields || {};
|
||||
if (query.selectedInclusively()) {
|
||||
for (let i = 0; i < paths.length; ++i) {
|
||||
if (!isPathInFields(userProvidedFields, paths[i])) {
|
||||
query.select(paths[i]);
|
||||
} else if (userProvidedFields[paths[i]] === 0) {
|
||||
delete query._fields[paths[i]];
|
||||
}
|
||||
}
|
||||
} else if (query.selectedExclusively()) {
|
||||
for (let i = 0; i < paths.length; ++i) {
|
||||
if (userProvidedFields[paths[i]] == null) {
|
||||
delete query._fields[paths[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function isPathInFields(userProvidedFields, path) {
|
||||
const pieces = path.split('.');
|
||||
const len = pieces.length;
|
||||
let cur = pieces[0];
|
||||
for (let i = 1; i < len; ++i) {
|
||||
if (userProvidedFields[cur] != null) {
|
||||
return true;
|
||||
}
|
||||
cur += '.' + pieces[i];
|
||||
}
|
||||
return userProvidedFields[cur] != null;
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* A query thunk is the function responsible for sending the query to MongoDB,
|
||||
* like `Query#_findOne()` or `Query#_execUpdate()`. The `Query#exec()` function
|
||||
* calls a thunk. The term "thunk" here is the traditional Node.js definition:
|
||||
* a function that takes exactly 1 parameter, a callback.
|
||||
*
|
||||
* This function defines common behavior for all query thunks.
|
||||
*/
|
||||
|
||||
module.exports = function wrapThunk(fn) {
|
||||
return function _wrappedThunk(cb) {
|
||||
++this._executionCount;
|
||||
|
||||
fn.call(this, cb);
|
||||
};
|
||||
};
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function applyPlugins(schema, plugins, options, cacheKey) {
|
||||
if (schema[cacheKey]) {
|
||||
return;
|
||||
}
|
||||
schema[cacheKey] = true;
|
||||
|
||||
if (!options || !options.skipTopLevel) {
|
||||
for (let i = 0; i < plugins.length; ++i) {
|
||||
schema.plugin(plugins[i][0], plugins[i][1]);
|
||||
}
|
||||
}
|
||||
|
||||
options = Object.assign({}, options);
|
||||
delete options.skipTopLevel;
|
||||
|
||||
if (options.applyPluginsToChildSchemas !== false) {
|
||||
for (const path of Object.keys(schema.paths)) {
|
||||
const type = schema.paths[path];
|
||||
if (type.schema != null) {
|
||||
applyPlugins(type.schema, plugins, options, cacheKey);
|
||||
|
||||
// Recompile schema because plugins may have changed it, see gh-7572
|
||||
type.caster.prototype.$__setSchema(type.schema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const discriminators = schema.discriminators;
|
||||
if (discriminators == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const applyPluginsToDiscriminators = options.applyPluginsToDiscriminators;
|
||||
|
||||
const keys = Object.keys(discriminators);
|
||||
for (let i = 0; i < keys.length; ++i) {
|
||||
const discriminatorKey = keys[i];
|
||||
const discriminatorSchema = discriminators[discriminatorKey];
|
||||
|
||||
applyPlugins(discriminatorSchema, plugins,
|
||||
{ skipTopLevel: !applyPluginsToDiscriminators }, cacheKey);
|
||||
}
|
||||
};
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
const get = require('../get');
|
||||
|
||||
module.exports = function applyWriteConcern(schema, options) {
|
||||
const writeConcern = get(schema, 'options.writeConcern', {});
|
||||
if (!('w' in options) && writeConcern.w != null) {
|
||||
options.w = writeConcern.w;
|
||||
}
|
||||
if (!('j' in options) && writeConcern.j != null) {
|
||||
options.j = writeConcern.j;
|
||||
}
|
||||
if (!('wtimeout' in options) && writeConcern.wtimeout != null) {
|
||||
options.wtimeout = writeConcern.wtimeout;
|
||||
}
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* For consistency's sake, we replace positional operator `$` and array filters
|
||||
* `$[]` and `$[foo]` with `0` when looking up schema paths.
|
||||
*/
|
||||
|
||||
module.exports = function cleanPositionalOperators(path) {
|
||||
return path.
|
||||
replace(/\.\$(\[[^\]]*\])?\./g, '.0.').
|
||||
replace(/\.(\[[^\]]*\])?\$$/g, '.0');
|
||||
};
|
||||
Loaded 100 of 2123 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user