First Commit

This commit is contained in:
SowinskiBraeden committed 2020-11-25 09:10:06 -08:00
commit 601e886c14
2123 files changed
+334815

No files matched your search

+1112
View File
File diff suppressed because it is too large. Load diff
+144
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+26
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+30
View File
@@ -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
View File
@@ -0,0 +1,15 @@
'use strict';
/*!
* ignore
*/
let driver = null;
module.exports.get = function() {
return driver;
};
module.exports.set = function(v) {
driver = v;
};
+4
View File
@@ -0,0 +1,4 @@
# Driver Spec
TODO
+7
View File
@@ -0,0 +1,7 @@
/*!
* ignore
*/
'use strict';
module.exports = function() {};
+14
View File
@@ -0,0 +1,14 @@
/*!
* Module dependencies.
*/
'use strict';
const Binary = require('bson').Binary;
/*!
* Module exports.
*/
module.exports = exports = Binary;
+7
View File
@@ -0,0 +1,7 @@
/*!
* ignore
*/
'use strict';
module.exports = require('bson').Decimal128;
+13
View File
@@ -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
View File
@@ -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;
@@ -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
View File
@@ -0,0 +1,10 @@
/*!
* Module dependencies.
*/
'use strict';
const Binary = require('mongodb').Binary;
module.exports = exports = Binary;
+352
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,7 @@
/*!
* ignore
*/
'use strict';
module.exports = require('mongodb').Decimal128;
+11
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
};
}
};
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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();
}
};
}
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,7 @@
'use strict';
/*!
* ignore
*/
module.exports = new WeakMap();
+45
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
};
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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');
};
+129
View File
@@ -0,0 +1,129 @@
'use strict';
const get = require('../get');
const utils = require('../../utils');
/*!
* Gather all indexes defined in the schema, including single nested,
* document arrays, and embedded discriminators.
*/
module.exports = function getIndexes(schema) {
let indexes = [];
const schemaStack = new WeakMap();
const indexTypes = schema.constructor.indexTypes;
const collectIndexes = function(schema, prefix, baseSchema) {
// Ignore infinitely nested schemas, if we've already seen this schema
// along this path there must be a cycle
if (schemaStack.has(schema)) {
return;
}
schemaStack.set(schema, true);
prefix = prefix || '';
const keys = Object.keys(schema.paths);
const length = keys.length;
for (let i = 0; i < length; ++i) {
const key = keys[i];
const path = schema.paths[key];
if (baseSchema != null && baseSchema.paths[key]) {
// If looking at an embedded discriminator schema, don't look at paths
// that the
continue;
}
if (path.$isMongooseDocumentArray || path.$isSingleNested) {
if (get(path, 'options.excludeIndexes') !== true &&
get(path, 'schemaOptions.excludeIndexes') !== true) {
collectIndexes(path.schema, prefix + key + '.');
}
if (path.schema.discriminators != null) {
const discriminators = path.schema.discriminators;
const discriminatorKeys = Object.keys(discriminators);
for (const discriminatorKey of discriminatorKeys) {
collectIndexes(discriminators[discriminatorKey],
prefix + key + '.', path.schema);
}
}
// Retained to minimize risk of backwards breaking changes due to
// gh-6113
if (path.$isMongooseDocumentArray) {
continue;
}
}
const index = path._index || (path.caster && path.caster._index);
if (index !== false && index !== null && index !== undefined) {
const field = {};
const isObject = utils.isObject(index);
const options = isObject ? index : {};
const type = typeof index === 'string' ? index :
isObject ? index.type :
false;
if (type && indexTypes.indexOf(type) !== -1) {
field[prefix + key] = type;
} else if (options.text) {
field[prefix + key] = 'text';
delete options.text;
} else {
field[prefix + key] = 1;
}
delete options.type;
if (!('background' in options)) {
options.background = true;
}
indexes.push([field, options]);
}
}
schemaStack.delete(schema);
if (prefix) {
fixSubIndexPaths(schema, prefix);
} else {
schema._indexes.forEach(function(index) {
if (!('background' in index[1])) {
index[1].background = true;
}
});
indexes = indexes.concat(schema._indexes);
}
};
collectIndexes(schema);
return indexes;
/*!
* Checks for indexes added to subdocs using Schema.index().
* These indexes need their paths prefixed properly.
*
* schema._indexes = [ [indexObj, options], [indexObj, options] ..]
*/
function fixSubIndexPaths(schema, prefix) {
const subindexes = schema._indexes;
const len = subindexes.length;
for (let i = 0; i < len; ++i) {
const indexObj = subindexes[i][0];
const keys = Object.keys(indexObj);
const klen = keys.length;
const newindex = {};
// use forward iteration, order matters
for (let j = 0; j < klen; ++j) {
const key = keys[j];
newindex[prefix + key] = indexObj[key];
}
indexes.push([newindex, subindexes[i][1]]);
}
}
};
+35
View File
@@ -0,0 +1,35 @@
'use strict';
/*!
* Behaves like `Schema#path()`, except for it also digs into arrays without
* needing to put `.0.`, so `getPath(schema, 'docArr.elProp')` works.
*/
module.exports = function getPath(schema, path) {
let schematype = schema.path(path);
if (schematype != null) {
return schematype;
}
const pieces = path.split('.');
let cur = '';
let isArray = false;
for (const piece of pieces) {
if (/^\d+$/.test(piece) && isArray) {
continue;
}
cur = cur.length === 0 ? piece : cur + '.' + piece;
schematype = schema.path(cur);
if (schematype != null && schematype.schema) {
schema = schematype.schema;
cur = '';
if (schematype.$isMongooseDocumentArray) {
isArray = true;
}
}
}
return schematype;
};
+24
View File
@@ -0,0 +1,24 @@
'use strict';
module.exports = handleTimestampOption;
/*!
* ignore
*/
function handleTimestampOption(arg, prop) {
if (arg == null) {
return null;
}
if (typeof arg === 'boolean') {
return prop;
}
if (typeof arg[prop] === 'boolean') {
return arg[prop] ? prop : null;
}
if (!(prop in arg)) {
return prop;
}
return arg[prop];
}
+19
View File
@@ -0,0 +1,19 @@
'use strict';
module.exports = function merge(s1, s2) {
s1.add(s2.obj || {});
s1.callQueue = s1.callQueue.concat(s2.callQueue);
s1.method(s2.methods);
s1.static(s2.statics);
for (const query in s2.query) {
s1.query[query] = s2.query[query];
}
for (const virtual in s2.virtuals) {
s1.virtuals[virtual] = s2.virtuals[virtual].clone();
}
s1.s.hooks.merge(s2.s.hooks, false);
};
+43
View File
@@ -0,0 +1,43 @@
'use strict';
const StrictModeError = require('../../error/strict');
/*!
* ignore
*/
module.exports = function(schematype) {
if (schematype.$immutable) {
schematype.$immutableSetter = createImmutableSetter(schematype.path,
schematype.options.immutable);
schematype.set(schematype.$immutableSetter);
} else if (schematype.$immutableSetter) {
schematype.setters = schematype.setters.
filter(fn => fn !== schematype.$immutableSetter);
delete schematype.$immutableSetter;
}
};
function createImmutableSetter(path, immutable) {
return function immutableSetter(v) {
if (this == null || this.$__ == null) {
return v;
}
if (this.isNew) {
return v;
}
const _immutable = typeof immutable === 'function' ?
immutable.call(this, this) :
immutable;
if (!_immutable) {
return v;
}
if (this.$__.strictMode === 'throw' && v !== this[path]) {
throw new StrictModeError(path, 'Path `' + path + '` is immutable ' +
'and strict mode is set to throw.', true);
}
return this[path];
};
}
+120
View File
@@ -0,0 +1,120 @@
'use strict';
const modifiedPaths = require('./common').modifiedPaths;
/**
* Applies defaults to update and findOneAndUpdate operations.
*
* @param {Object} filter
* @param {Schema} schema
* @param {Object} castedDoc
* @param {Object} options
* @method setDefaultsOnInsert
* @api private
*/
module.exports = function(filter, schema, castedDoc, options) {
const keys = Object.keys(castedDoc || {});
const updatedKeys = {};
const updatedValues = {};
const numKeys = keys.length;
const modified = {};
let hasDollarUpdate = false;
options = options || {};
if (!options.upsert || !options.setDefaultsOnInsert) {
return castedDoc;
}
for (let i = 0; i < numKeys; ++i) {
if (keys[i].startsWith('$')) {
modifiedPaths(castedDoc[keys[i]], '', modified);
hasDollarUpdate = true;
}
}
if (!hasDollarUpdate) {
modifiedPaths(castedDoc, '', modified);
}
const paths = Object.keys(filter);
const numPaths = paths.length;
for (let i = 0; i < numPaths; ++i) {
const path = paths[i];
const condition = filter[path];
if (condition && typeof condition === 'object') {
const conditionKeys = Object.keys(condition);
const numConditionKeys = conditionKeys.length;
let hasDollarKey = false;
for (let j = 0; j < numConditionKeys; ++j) {
if (conditionKeys[j].startsWith('$')) {
hasDollarKey = true;
break;
}
}
if (hasDollarKey) {
continue;
}
}
updatedKeys[path] = true;
modified[path] = true;
}
if (options && options.overwrite && !hasDollarUpdate) {
// Defaults will be set later, since we're overwriting we'll cast
// the whole update to a document
return castedDoc;
}
schema.eachPath(function(path, schemaType) {
// Skip single nested paths if underneath a map
const isUnderneathMap = schemaType.path.endsWith('.$*') ||
schemaType.path.indexOf('.$*.') !== -1;
if (schemaType.$isSingleNested && !isUnderneathMap) {
// Only handle nested schemas 1-level deep to avoid infinite
// recursion re: https://github.com/mongodb-js/mongoose-autopopulate/issues/11
schemaType.schema.eachPath(function(_path, _schemaType) {
if (_path === '_id' && _schemaType.auto) {
// Ignore _id if auto id so we don't create subdocs
return;
}
const def = _schemaType.getDefault(null, true);
if (!isModified(modified, path + '.' + _path) &&
typeof def !== 'undefined') {
castedDoc = castedDoc || {};
castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
castedDoc.$setOnInsert[path + '.' + _path] = def;
updatedValues[path + '.' + _path] = def;
}
});
} else {
const def = schemaType.getDefault(null, true);
if (!isModified(modified, path) && typeof def !== 'undefined') {
castedDoc = castedDoc || {};
castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
castedDoc.$setOnInsert[path] = def;
updatedValues[path] = def;
}
}
});
return castedDoc;
};
function isModified(modified, path) {
if (modified[path]) {
return true;
}
const sp = path.split('.');
let cur = sp[0];
for (let i = 1; i < sp.length; ++i) {
if (modified[cur]) {
return true;
}
cur += '.' + sp[i];
}
return false;
}
Loaded 100 of 2123 files, more files were not shown because too many files have changed in this diff. Show more