database and ajax test added
This commit is contained in:
+87
@@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const ObjectId = require('../types/objectid');
|
||||
const utils = require('../utils');
|
||||
|
||||
exports.flatten = flatten;
|
||||
exports.modifiedPaths = modifiedPaths;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function flatten(update, path, options) {
|
||||
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;
|
||||
if (shouldFlatten(val)) {
|
||||
if (options && options.skipArrays && Array.isArray(val)) {
|
||||
continue;
|
||||
}
|
||||
const flat = flatten(val, path + key, options);
|
||||
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);
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const async = require('async');
|
||||
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 nextQueue = async.queue(function(task, cb) {
|
||||
if (drained) return cb();
|
||||
next(function(err, doc) {
|
||||
if (err) return cb(err);
|
||||
if (!doc) drained = true;
|
||||
cb(null, doc);
|
||||
});
|
||||
}, 1);
|
||||
|
||||
const getAndRun = function(cb) {
|
||||
nextQueue.push({}, function(err, doc) {
|
||||
if (err) return cb(err);
|
||||
if (!doc) return cb();
|
||||
handleNextResult(doc, function(err) {
|
||||
if (err) return cb(err);
|
||||
// Make sure to clear the stack re: gh-4697
|
||||
setTimeout(function() {
|
||||
getAndRun(cb);
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
async.times(parallel, function(n, cb) {
|
||||
getAndRun(cb);
|
||||
}, callback);
|
||||
};
|
||||
|
||||
return utils.promiseOrCallback(callback, cb => {
|
||||
iterate(cb);
|
||||
});
|
||||
};
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function cleanModifiedSubpaths(doc, path, options) {
|
||||
options = options || {};
|
||||
const skipDocArrays = options.skipDocArrays;
|
||||
|
||||
let deleted = 0;
|
||||
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.indexOf(path + '.') === 0) {
|
||||
delete doc.$__.activePaths.states.modify[modifiedPath];
|
||||
++deleted;
|
||||
}
|
||||
}
|
||||
return deleted;
|
||||
};
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
'use strict';
|
||||
|
||||
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.getFunctionName(limb.constructor) === 'Object' &&
|
||||
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, '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
|
||||
});
|
||||
|
||||
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'].indexOf(key) === -1;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
const get = require('../get');
|
||||
|
||||
/*!
|
||||
* Like `schema.path()`, except with a document, because impossible to
|
||||
* determine path type without knowing the embedded discriminator key.
|
||||
*/
|
||||
|
||||
module.exports = function getEmbeddedDiscriminatorPath(doc, path, options) {
|
||||
options = options || {};
|
||||
const typeOnly = options.typeOnly;
|
||||
const parts = path.split('.');
|
||||
let schema = null;
|
||||
let type = 'adhocOrUndefined';
|
||||
|
||||
for (let i = 0; i < parts.length; ++i) {
|
||||
const subpath = parts.slice(0, i + 1).join('.');
|
||||
schema = doc.schema.path(subpath);
|
||||
if (schema == null) {
|
||||
continue;
|
||||
}
|
||||
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('.');
|
||||
schema = discriminators[discriminatorKey].path(rest);
|
||||
if (schema != null) {
|
||||
type = discriminators[discriminatorKey].pathType(rest);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Are we getting the whole schema or just the type, 'real', 'nested', etc.
|
||||
return typeOnly ? type : schema;
|
||||
};
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Simplified lodash.get to work around the annoying null quirk. See:
|
||||
* https://github.com/lodash/lodash/issues/3659
|
||||
*/
|
||||
|
||||
module.exports = function get(obj, path, def) {
|
||||
const parts = path.split('.');
|
||||
let rest = path;
|
||||
let cur = obj;
|
||||
for (const part of parts) {
|
||||
if (cur == null) {
|
||||
return def;
|
||||
}
|
||||
|
||||
// `lib/cast.js` depends on being able to get dotted paths in updates,
|
||||
// like `{ $set: { 'a.b': 42 } }`
|
||||
if (cur[rest] != null) {
|
||||
return cur[rest];
|
||||
}
|
||||
|
||||
cur = getProperty(cur, part);
|
||||
|
||||
rest = rest.substr(part.length + 1);
|
||||
}
|
||||
|
||||
return cur == null ? def : cur;
|
||||
};
|
||||
|
||||
function getProperty(obj, prop) {
|
||||
if (obj == null) {
|
||||
return obj;
|
||||
}
|
||||
if (obj instanceof Map) {
|
||||
return obj.get(prop);
|
||||
}
|
||||
return obj[prop];
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/*!
|
||||
* Centralize this so we can more easily work around issues with people
|
||||
* stubbing out `process.nextTick()` in tests using sinon:
|
||||
* https://github.com/sinonjs/lolex#automatically-incrementing-mocked-time
|
||||
* See gh-6074
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = function immediate(cb) {
|
||||
return process.nextTick(cb);
|
||||
};
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
'use strict';
|
||||
|
||||
const symbols = require('../../schema/symbols');
|
||||
const utils = require('../../utils');
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = applyHooks;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
applyHooks.middlewareFunctions = [
|
||||
'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 (let i = 0; i < schema.childSchemas.length; ++i) {
|
||||
const childModel = schema.childSchemas[i].model;
|
||||
if (childModel.$appliedHooks) {
|
||||
continue;
|
||||
}
|
||||
applyHooks(childModel, schema.childSchemas[i].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') {
|
||||
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.$__validate = middleware.
|
||||
createWrapper('validate', objToDecorate.$__validate, null, kareemOptions);
|
||||
objToDecorate.$__remove = middleware.
|
||||
createWrapper('remove', objToDecorate.$__remove, 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 = cb == null ? args :
|
||||
args.slice(0, args.length - 1);
|
||||
return utils.promiseOrCallback(cb, callback => {
|
||||
this[`$__${method}`].apply(this,
|
||||
argsWithoutCallback.concat([callback]));
|
||||
}, model.events);
|
||||
};
|
||||
objToDecorate[`$__${method}`] = middleware.
|
||||
createWrapper(method, originalMethod, null, customMethodOptions);
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
'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 (let i = 0; i < schema.childSchemas.length; ++i) {
|
||||
if (schema.childSchemas[i].model.$appliedMethods) {
|
||||
continue;
|
||||
}
|
||||
applyMethods(schema.childSchemas[i].model, schema.childSchemas[i].schema);
|
||||
}
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Register statics for this model
|
||||
* @param {Model} model
|
||||
* @param {Schema} schema
|
||||
*/
|
||||
module.exports = function applyStatics(model, schema) {
|
||||
for (const i in schema.statics) {
|
||||
model[i] = schema.statics[i];
|
||||
}
|
||||
};
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
'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) {
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
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);
|
||||
};
|
||||
}
|
||||
};
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
'use strict';
|
||||
|
||||
const defineKey = require('../document/compile').defineKey;
|
||||
const get = require('../../helpers/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) {
|
||||
if (!(schema && schema.instanceOfSchema)) {
|
||||
throw new Error('You must pass a valid discriminator Schema');
|
||||
}
|
||||
|
||||
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 });
|
||||
|
||||
if (model.schema.discriminatorMapping &&
|
||||
!model.schema.discriminatorMapping.isRoot) {
|
||||
throw new Error('Discriminator "' + name +
|
||||
'" can only be a discriminator of the root model');
|
||||
}
|
||||
|
||||
const key = model.schema.options.discriminatorKey;
|
||||
|
||||
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._originalSchema = schema.clone();
|
||||
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, $parentSchema: 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]];
|
||||
}
|
||||
|
||||
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] = String;
|
||||
schema.add(obj);
|
||||
schema.discriminatorMapping = {key: key, value: value, isRoot: false};
|
||||
|
||||
if (baseSchema.options.collection) {
|
||||
schema.options.collection = baseSchema.options.collection;
|
||||
}
|
||||
|
||||
const toJSON = schema.options.toJSON;
|
||||
const toObject = schema.options.toObject;
|
||||
const _id = schema.options._id;
|
||||
const id = schema.options.id;
|
||||
|
||||
const keys = Object.keys(schema.options);
|
||||
schema.options.discriminatorKey = baseSchema.options.discriminatorKey;
|
||||
|
||||
for (let i = 0; i < keys.length; ++i) {
|
||||
const _key = keys[i];
|
||||
if (!CUSTOMIZABLE_DISCRIMINATOR_OPTIONS[_key]) {
|
||||
if (!utils.deepEqual(schema.options[_key], baseSchema.options[_key])) {
|
||||
throw new Error('Can\'t customize discriminator option ' + _key +
|
||||
' (can only modify ' +
|
||||
Object.keys(CUSTOMIZABLE_DISCRIMINATOR_OPTIONS).join(', ') +
|
||||
')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
schema.options = utils.clone(baseSchema.options);
|
||||
if (toJSON) schema.options.toJSON = toJSON;
|
||||
if (toObject) schema.options.toObject = toObject;
|
||||
if (typeof _id !== 'undefined') {
|
||||
schema.options._id = _id;
|
||||
}
|
||||
schema.options.id = id;
|
||||
schema.s.hooks = model.schema.s.hooks.merge(schema.s.hooks);
|
||||
|
||||
schema.plugins = Array.prototype.slice(baseSchema.plugins);
|
||||
schema.callQueue = baseSchema.callQueue.concat(schema.callQueue);
|
||||
delete schema._requiredpaths; // reset just in case Schema#requiredPaths() was called on either schema
|
||||
}
|
||||
|
||||
// merges base schema into new discriminator schema and sets new type field.
|
||||
merge(schema, model.schema);
|
||||
|
||||
if (!model.discriminators) {
|
||||
model.discriminators = {};
|
||||
}
|
||||
|
||||
if (!model.schema.discriminatorMapping) {
|
||||
model.schema.discriminatorMapping = {key: key, value: null, isRoot: true};
|
||||
}
|
||||
if (!model.schema.discriminators) {
|
||||
model.schema.discriminators = {};
|
||||
}
|
||||
|
||||
model.schema.discriminators[name] = schema;
|
||||
|
||||
if (model.discriminators[name]) {
|
||||
throw new Error('Discriminator with name "' + name + '" already exists');
|
||||
}
|
||||
|
||||
return schema;
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function once(fn) {
|
||||
let called = false;
|
||||
return function() {
|
||||
if (called) {
|
||||
return;
|
||||
}
|
||||
called = true;
|
||||
return fn.apply(null, arguments);
|
||||
};
|
||||
};
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
'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;
|
||||
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(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;
|
||||
});
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
const Mixed = require('../../schema/mixed');
|
||||
const get = require('../get');
|
||||
const leanPopulateMap = require('./leanPopulateMap');
|
||||
const mpath = require('mpath');
|
||||
|
||||
/*!
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fullPath = nestedPath.concat([trypath]).join('.');
|
||||
if (topLevelDoc.$__ && topLevelDoc.populated(fullPath) && p < parts.length) {
|
||||
const schema = get(doc.$__.populated[fullPath], 'options.model.schema');
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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, []);
|
||||
};
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = getVirtual;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function getVirtual(schema, name) {
|
||||
if (schema.virtuals[name]) {
|
||||
return schema.virtuals[name];
|
||||
}
|
||||
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) {
|
||||
schema.virtuals[cur].$nestedSchemaPath = nestedSchemaPath;
|
||||
return schema.virtuals[cur];
|
||||
}
|
||||
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) {
|
||||
schema.virtuals[rest].$nestedSchemaPath =
|
||||
[nestedSchemaPath, cur].filter(v => !!v).join('.');
|
||||
return schema.virtuals[rest];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i + 1 < parts.length && schema.discriminators) {
|
||||
for (const key of Object.keys(schema.discriminators)) {
|
||||
const _virtual = getVirtual(schema.discriminators[key], rest);
|
||||
if (_virtual != null) {
|
||||
_virtual.$nestedSchemaPath = [nestedSchemaPath, cur].
|
||||
filter(v => !!v).join('.');
|
||||
return _virtual;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nestedSchemaPath += (nestedSchemaPath.length > 0 ? '.' : '') + cur;
|
||||
cur = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = new WeakMap();
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function normalizeRefPath(refPath, doc, populatedPath) {
|
||||
if (refPath == null) {
|
||||
return refPath;
|
||||
}
|
||||
|
||||
if (typeof refPath === 'function') {
|
||||
refPath = refPath.call(doc, doc, populatedPath);
|
||||
}
|
||||
|
||||
// If populated path has numerics, the end `refPath` should too. For example,
|
||||
// if populating `a.0.b` instead of `a.b` and `b` has `refPath = a.c`, we
|
||||
// should return `a.0.c` for the refPath.
|
||||
const hasNumericProp = /(\.\d+$|\.\d+\.)/g;
|
||||
|
||||
if (hasNumericProp.test(populatedPath)) {
|
||||
const chunks = populatedPath.split(hasNumericProp);
|
||||
|
||||
if (chunks[chunks.length - 1] === '') {
|
||||
throw new Error('Can\'t populate individual element in an array');
|
||||
}
|
||||
|
||||
let _refPath = '';
|
||||
let _remaining = refPath;
|
||||
// 2nd, 4th, etc. will be numeric props. For example: `[ 'a', '.0.', 'b' ]`
|
||||
for (let i = 0; i < chunks.length; i += 2) {
|
||||
const chunk = chunks[i];
|
||||
if (_remaining.startsWith(chunk + '.')) {
|
||||
_refPath += _remaining.substr(0, chunk.length) + chunks[i + 1];
|
||||
_remaining = _remaining.substr(chunk.length + 1);
|
||||
} else if (i === chunks.length - 1) {
|
||||
_refPath += _remaining;
|
||||
_remaining = '';
|
||||
break;
|
||||
} else {
|
||||
throw new Error('Could not normalize ref path, chunk ' + chunk + ' not in populated path');
|
||||
}
|
||||
}
|
||||
|
||||
return _refPath;
|
||||
}
|
||||
|
||||
return refPath;
|
||||
};
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
const MongooseError = require('../../error/mongooseError');
|
||||
const util = require('util');
|
||||
|
||||
module.exports = validateRef;
|
||||
|
||||
function validateRef(ref, path) {
|
||||
if (typeof ref === 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof ref === 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new MongooseError('Invalid ref at path "' + path + '". Got ' +
|
||||
util.inspect(ref, { depth: 0 }));
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
if (typeof jest !== 'undefined' && typeof window !== 'undefined') {
|
||||
console.warn('Mongoose: looks like you\'re trying to test a Mongoose app ' +
|
||||
'with Jest\'s default jsdom test environment. Please make sure you read ' +
|
||||
'Mongoose\'s docs on configuring Jest to test Node.js apps: ' +
|
||||
'http://mongoosejs.com/docs/jest.html');
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function isDefiningProjection(val) {
|
||||
if (val == null) {
|
||||
// `undefined` or `null` become exclusive projections
|
||||
return true;
|
||||
}
|
||||
if (typeof val === 'object') {
|
||||
// Only cases where a value does **not** define whether the whole projection
|
||||
// is inclusive or exclusive are `$meta` and `$slice`.
|
||||
return !('$meta' in val) && !('$slice' in val);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
const isDefiningProjection = require('./isDefiningProjection');
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function isExclusive(projection) {
|
||||
const keys = Object.keys(projection);
|
||||
let ki = keys.length;
|
||||
let exclude = null;
|
||||
|
||||
if (ki === 1 && keys[0] === '_id') {
|
||||
exclude = !!projection[keys[ki]];
|
||||
} else {
|
||||
while (ki--) {
|
||||
// Does this projection explicitly define inclusion/exclusion?
|
||||
// Explicitly avoid `$meta` and `$slice`
|
||||
if (keys[ki] !== '_id' && isDefiningProjection(projection[keys[ki]])) {
|
||||
exclude = !projection[keys[ki]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return exclude;
|
||||
};
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
const isDefiningProjection = require('./isDefiningProjection');
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function isInclusive(projection) {
|
||||
if (projection == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const props = Object.keys(projection);
|
||||
const numProps = props.length;
|
||||
if (numProps === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < numProps; ++i) {
|
||||
const prop = props[i];
|
||||
// Plus paths can't define the projection (see gh-7050)
|
||||
if (prop.charAt(0) === '+') {
|
||||
continue;
|
||||
}
|
||||
// If field is truthy (1, true, etc.) and not an object, then this
|
||||
// projection must be inclusive. If object, assume its $meta, $slice, etc.
|
||||
if (isDefiningProjection(projection[prop]) && !!projection[prop]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
const isDefiningProjection = require('./isDefiningProjection');
|
||||
|
||||
/*!
|
||||
* Determines if `path` is excluded by `projection`
|
||||
*
|
||||
* @param {Object} projection
|
||||
* @param {string} path
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
module.exports = function isPathExcluded(projection, path) {
|
||||
if (path === '_id') {
|
||||
return projection._id === 0;
|
||||
}
|
||||
|
||||
const paths = Object.keys(projection);
|
||||
let type = null;
|
||||
|
||||
for (const _path of paths) {
|
||||
if (isDefiningProjection(projection[_path])) {
|
||||
type = projection[path] === 1 ? 'inclusive' : 'exclusive';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'inclusive') {
|
||||
return projection[path] !== 1;
|
||||
}
|
||||
if (type === 'exclusive') {
|
||||
return projection[path] === 0;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function isPathSelectedInclusive(fields, path) {
|
||||
const chunks = path.split('.');
|
||||
let cur = '';
|
||||
let j;
|
||||
let keys;
|
||||
let numKeys;
|
||||
for (let i = 0; i < chunks.length; ++i) {
|
||||
cur += cur.length ? '.' : '' + chunks[i];
|
||||
if (fields[cur]) {
|
||||
keys = Object.keys(fields);
|
||||
numKeys = keys.length;
|
||||
for (j = 0; j < numKeys; ++j) {
|
||||
if (keys[i].indexOf(cur + '.') === 0 && keys[i].indexOf(path) !== 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = applyQueryMiddleware;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
applyQueryMiddleware.middlewareFunctions = [
|
||||
'count',
|
||||
'countDocuments',
|
||||
'deleteMany',
|
||||
'deleteOne',
|
||||
'estimatedDocumentCount',
|
||||
'find',
|
||||
'findOne',
|
||||
'findOneAndDelete',
|
||||
'findOneAndRemove',
|
||||
'findOneAndReplace',
|
||||
'findOneAndUpdate',
|
||||
'remove',
|
||||
'replaceOne',
|
||||
'update',
|
||||
'updateMany',
|
||||
'updateOne'
|
||||
];
|
||||
|
||||
/*!
|
||||
* 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') {
|
||||
return hook.query == null || !!hook.query;
|
||||
}
|
||||
if (hook.name === 'remove') {
|
||||
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);
|
||||
|
||||
applyQueryMiddleware.middlewareFunctions.
|
||||
filter(v => v !== 'update').
|
||||
forEach(fn => {
|
||||
Query.prototype[`_${fn}`] = middleware.createWrapper(fn,
|
||||
Query.prototype[`_${fn}`], null, kareemOptions);
|
||||
});
|
||||
}
|
||||
+428
@@ -0,0 +1,428 @@
|
||||
'use strict';
|
||||
|
||||
const CastError = require('../../error/cast');
|
||||
const StrictModeError = require('../../error/strict');
|
||||
const ValidationError = require('../../error/validation');
|
||||
const castNumber = require('../../cast/number');
|
||||
const getEmbeddedDiscriminatorPath = require('./getEmbeddedDiscriminatorPath');
|
||||
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.charAt(0) === '$';
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
if (val && val.constructor.name === 'Object') {
|
||||
// watch for embedded doc schemas
|
||||
schematype = schema._getSchema(prefix + key);
|
||||
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);
|
||||
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 && op !== '$set') {
|
||||
// Cast values for ops that add data to MongoDB.
|
||||
// Ensures embedded documents get ObjectIds etc.
|
||||
const tmp = schema.cast(val);
|
||||
if (Array.isArray(val)) {
|
||||
val = tmp;
|
||||
} else if (Array.isArray(tmp)) {
|
||||
val = tmp[0];
|
||||
} else {
|
||||
val = tmp;
|
||||
}
|
||||
return val;
|
||||
} else if (cond && op === '$set') {
|
||||
return schema.cast(val);
|
||||
}
|
||||
|
||||
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.$parentSchema
|
||||
});
|
||||
}
|
||||
|
||||
return schema.castForQueryWrapper({ val: val, context: context });
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
'use strict';
|
||||
|
||||
const helpers = require('../../queryhelpers');
|
||||
|
||||
module.exports = completeMany;
|
||||
|
||||
/*!
|
||||
* Given a model and an array of docs, hydrates all the docs to be instances
|
||||
* of the model. Used to initialize docs returned from the db from `find()`
|
||||
*
|
||||
* @param {Model} model
|
||||
* @param {Array} docs
|
||||
* @param {Object} fields the projection used, including `select` from schemas
|
||||
* @param {Object} userProvidedFields the user-specified projection
|
||||
* @param {Object} opts
|
||||
* @param {Array} [opts.populated]
|
||||
* @param {ClientSession} [opts.session]
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
function completeMany(model, docs, fields, userProvidedFields, opts, callback) {
|
||||
const arr = [];
|
||||
let count = docs.length;
|
||||
const len = count;
|
||||
let error = null;
|
||||
|
||||
function init(_error) {
|
||||
if (_error != null) {
|
||||
error = error || _error;
|
||||
}
|
||||
if (error != null) {
|
||||
--count || process.nextTick(() => callback(error));
|
||||
return;
|
||||
}
|
||||
--count || process.nextTick(() => callback(error, arr));
|
||||
}
|
||||
|
||||
for (let i = 0; i < len; ++i) {
|
||||
arr[i] = helpers.createModel(model, docs[i], fields, userProvidedFields);
|
||||
try {
|
||||
arr[i].init(docs[i], opts, init);
|
||||
} catch (error) {
|
||||
init(error);
|
||||
}
|
||||
arr[i].$session(opts.session);
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
'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(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 = parts.slice(0, i + 1).join('.').
|
||||
replace(/\.\$\./i, '.0.').replace(/\.\$$/, '.0');
|
||||
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 discriminatorValuePath = subpath + '.' +
|
||||
get(schematype, 'schema.options.discriminatorKey');
|
||||
const discriminatorFilterPath =
|
||||
discriminatorValuePath.replace(/\.\d+\./, '.');
|
||||
let discriminatorKey = null;
|
||||
if (discriminatorValuePath in filter) {
|
||||
discriminatorKey = filter[discriminatorValuePath];
|
||||
}
|
||||
if (discriminatorFilterPath in filter) {
|
||||
discriminatorKey = filter[discriminatorFilterPath];
|
||||
}
|
||||
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 };
|
||||
};
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function(obj) {
|
||||
const keys = Object.keys(obj);
|
||||
const len = keys.length;
|
||||
for (let i = 0; i < len; ++i) {
|
||||
if (keys[i].charAt(0) === '$') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function selectPopulatedFields(query) {
|
||||
const opts = query._mongooseOptions;
|
||||
|
||||
if (opts.populate != null) {
|
||||
const paths = Object.keys(opts.populate);
|
||||
const userProvidedFields = query._userProvidedFields || {};
|
||||
if (query.selectedInclusively()) {
|
||||
for (let i = 0; i < paths.length; ++i) {
|
||||
if (!isPathInFields(userProvidedFields, paths[i])) {
|
||||
query.select(paths[i]);
|
||||
} else if (userProvidedFields[paths[i]] === 0) {
|
||||
delete query._fields[paths[i]];
|
||||
}
|
||||
}
|
||||
} else if (query.selectedExclusively()) {
|
||||
for (let i = 0; i < paths.length; ++i) {
|
||||
if (userProvidedFields[paths[i]] == null) {
|
||||
delete query._fields[paths[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function isPathInFields(userProvidedFields, path) {
|
||||
const pieces = path.split('.');
|
||||
const len = pieces.length;
|
||||
let cur = pieces[0];
|
||||
for (let i = 1; i < len; ++i) {
|
||||
if (userProvidedFields[cur] != null) {
|
||||
return true;
|
||||
}
|
||||
cur += '.' + pieces[i];
|
||||
}
|
||||
return userProvidedFields[cur] != null;
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* A query thunk is the function responsible for sending the query to MongoDB,
|
||||
* like `Query#_findOne()` or `Query#_execUpdate()`. The `Query#exec()` function
|
||||
* calls a thunk. The term "thunk" here is the traditional Node.js definition:
|
||||
* a function that takes exactly 1 parameter, a callback.
|
||||
*
|
||||
* This function defines common behavior for all query thunks.
|
||||
*/
|
||||
|
||||
module.exports = function wrapThunk(fn) {
|
||||
return function _wrappedThunk(cb) {
|
||||
++this._executionCount;
|
||||
|
||||
fn.call(this, cb);
|
||||
};
|
||||
};
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
const get = require('../get');
|
||||
|
||||
module.exports = function applyWriteConcern(schema, options) {
|
||||
const writeConcern = get(schema, 'options.writeConcern', {});
|
||||
if (!('w' in options) && writeConcern.w != null) {
|
||||
options.w = writeConcern.w;
|
||||
}
|
||||
if (!('j' in options) && writeConcern.j != null) {
|
||||
options.j = writeConcern.j;
|
||||
}
|
||||
if (!('wtimeout' in options) && writeConcern.wtimeout != null) {
|
||||
options.wtimeout = writeConcern.wtimeout;
|
||||
}
|
||||
};
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
'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) {
|
||||
// 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 (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]._originalSchema,
|
||||
prefix + key + '.');
|
||||
}
|
||||
}
|
||||
|
||||
// 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]]);
|
||||
}
|
||||
}
|
||||
};
|
||||
+24
@@ -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
@@ -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.virtual[virtual] = s2.virtual[virtual].clone();
|
||||
}
|
||||
|
||||
s1.s.hooks.merge(s2.s.hooks, false);
|
||||
};
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Set `$parentSchema` on all schema types, and `$schemaType` on single
|
||||
* nested docs.
|
||||
*
|
||||
* This is a slow path function, should only run when model is compiled
|
||||
*/
|
||||
|
||||
module.exports = function setParentPointers(schema, skipRecursion) {
|
||||
for (const path of Object.keys(schema.paths)) {
|
||||
const schemaType = schema.paths[path];
|
||||
if (schemaType.schema != null) {
|
||||
Object.defineProperty(schemaType.schema, '$schemaType', {
|
||||
configurable: true,
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
value: schemaType
|
||||
});
|
||||
}
|
||||
Object.defineProperty(schemaType, '$parentSchema', {
|
||||
configurable: true,
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
value: schema
|
||||
});
|
||||
}
|
||||
|
||||
// `childSchemas` contains all descendant schemas, so no need to recurse
|
||||
// further.
|
||||
if (skipRecursion) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const obj of schema.childSchemas) {
|
||||
setParentPointers(obj.schema, true);
|
||||
}
|
||||
};
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
'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].charAt(0) === '$') {
|
||||
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].charAt(0) === '$') {
|
||||
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) {
|
||||
if (schemaType.$isSingleNested) {
|
||||
// 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;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
exports.validatorErrorSymbol = Symbol.for('mongoose:validatorError');
|
||||
|
||||
exports.documentArrayParent = Symbol.for('mongoose:documentArrayParent');
|
||||
|
||||
exports.modelSymbol = Symbol.for('mongoose#Model');
|
||||
|
||||
exports.getSymbol = Symbol.for('mongoose#Document#get');
|
||||
|
||||
exports.objectIdSymbol = Symbol.for('mongoose#ObjectId');
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
'use strict';
|
||||
|
||||
const handleTimestampOption = require('../schema/handleTimestampOption');
|
||||
|
||||
module.exports = applyTimestampsToChildren;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function applyTimestampsToChildren(now, update, schema) {
|
||||
if (update == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const keys = Object.keys(update);
|
||||
let key;
|
||||
let createdAt;
|
||||
let updatedAt;
|
||||
let timestamps;
|
||||
let path;
|
||||
|
||||
const hasDollarKey = keys.length && keys[0].charAt(0) === '$';
|
||||
|
||||
if (hasDollarKey) {
|
||||
if (update.$push) {
|
||||
for (key in update.$push) {
|
||||
const $path = schema.path(key);
|
||||
if (update.$push[key] &&
|
||||
$path &&
|
||||
$path.$isMongooseDocumentArray &&
|
||||
$path.schema.options.timestamps) {
|
||||
timestamps = $path.schema.options.timestamps;
|
||||
createdAt = handleTimestampOption(timestamps, 'createdAt');
|
||||
updatedAt = handleTimestampOption(timestamps, 'updatedAt');
|
||||
if (update.$push[key].$each) {
|
||||
update.$push[key].$each.forEach(function(subdoc) {
|
||||
if (updatedAt != null) {
|
||||
subdoc[updatedAt] = now;
|
||||
}
|
||||
if (createdAt != null) {
|
||||
subdoc[createdAt] = now;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (updatedAt != null) {
|
||||
update.$push[key][updatedAt] = now;
|
||||
}
|
||||
if (createdAt != null) {
|
||||
update.$push[key][createdAt] = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (update.$set != null) {
|
||||
const keys = Object.keys(update.$set);
|
||||
for (key of keys) {
|
||||
// Replace positional operator `$` and array filters `$[]` and `$[.*]`
|
||||
const keyToSearch = key.
|
||||
replace(/\.\$(\[[^\]]*\])?\./g, '.0.').
|
||||
replace(/\.(\[[^\]]*\])?\$$/, '.0');
|
||||
path = schema.path(keyToSearch);
|
||||
if (!path) {
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(update.$set[key]) && path.$isMongooseDocumentArray) {
|
||||
applyTimestampsToDocumentArray(update.$set[key], path, now);
|
||||
} else if (update.$set[key] && path.$isSingleNested) {
|
||||
applyTimestampsToSingleNested(update.$set[key], path, now);
|
||||
} else if (path.$parentSchema !== schema && path.$parentSchema != null) {
|
||||
const parentPath = path.$parentSchema.$schemaType;
|
||||
|
||||
if (parentPath == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
timestamps = parentPath.schema.options.timestamps;
|
||||
createdAt = handleTimestampOption(timestamps, 'createdAt');
|
||||
updatedAt = handleTimestampOption(timestamps, 'updatedAt');
|
||||
|
||||
if (updatedAt == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parentPath.$isSingleNested) {
|
||||
// Single nested is easy
|
||||
update.$set[parentPath.path + '.' + updatedAt] = now;
|
||||
continue;
|
||||
}
|
||||
|
||||
let childPath = key.substr(parentPath.path.length + 1);
|
||||
const firstDot = childPath.indexOf('.');
|
||||
|
||||
// Shouldn't happen, but if it does ignore this path
|
||||
if (firstDot === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
childPath = childPath.substr(0, firstDot);
|
||||
|
||||
update.$set[parentPath.path + '.' + childPath + '.' + updatedAt] = now;
|
||||
} else if (path.schema != null && path.schema != schema) {
|
||||
timestamps = path.schema.options.timestamps;
|
||||
createdAt = handleTimestampOption(timestamps, 'createdAt');
|
||||
updatedAt = handleTimestampOption(timestamps, 'updatedAt');
|
||||
|
||||
if (updatedAt != null) {
|
||||
update.$set[key][updatedAt] = now;
|
||||
}
|
||||
if (createdAt != null) {
|
||||
update.$set[key][createdAt] = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const keys = Object.keys(update).filter(key => !key.startsWith('$'));
|
||||
for (key of keys) {
|
||||
// Replace positional operator `$` and array filters `$[]` and `$[.*]`
|
||||
const keyToSearch = key.
|
||||
replace(/\.\$(\[[^\]]*\])?\./g, '.0.').
|
||||
replace(/\.(\[[^\]]*\])?\$$/, '.0');
|
||||
path = schema.path(keyToSearch);
|
||||
if (!path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Array.isArray(update[key]) && path.$isMongooseDocumentArray) {
|
||||
applyTimestampsToDocumentArray(update[key], path, now);
|
||||
} else if (update[key] != null && path.$isSingleNested) {
|
||||
applyTimestampsToSingleNested(update[key], path, now);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyTimestampsToDocumentArray(arr, schematype, now) {
|
||||
const timestamps = schematype.schema.options.timestamps;
|
||||
|
||||
if (!timestamps) {
|
||||
return;
|
||||
}
|
||||
|
||||
const len = arr.length;
|
||||
|
||||
const createdAt = handleTimestampOption(timestamps, 'createdAt');
|
||||
const updatedAt = handleTimestampOption(timestamps, 'updatedAt');
|
||||
for (let i = 0; i < len; ++i) {
|
||||
if (updatedAt != null) {
|
||||
arr[i][updatedAt] = now;
|
||||
}
|
||||
if (createdAt != null) {
|
||||
arr[i][createdAt] = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyTimestampsToSingleNested(subdoc, schematype, now) {
|
||||
const timestamps = schematype.schema.options.timestamps;
|
||||
if (!timestamps) {
|
||||
return;
|
||||
}
|
||||
|
||||
const createdAt = handleTimestampOption(timestamps, 'createdAt');
|
||||
const updatedAt = handleTimestampOption(timestamps, 'updatedAt');
|
||||
if (updatedAt != null) {
|
||||
subdoc[updatedAt] = now;
|
||||
}
|
||||
if (createdAt != null) {
|
||||
subdoc[createdAt] = now;
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
const get = require('../get');
|
||||
|
||||
module.exports = applyTimestampsToUpdate;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, options) {
|
||||
const updates = currentUpdate;
|
||||
let _updates = updates;
|
||||
const overwrite = get(options, 'overwrite', false);
|
||||
const timestamps = get(options, 'timestamps', true);
|
||||
|
||||
// Support skipping timestamps at the query level, see gh-6980
|
||||
if (!timestamps || updates == null) {
|
||||
return currentUpdate;
|
||||
}
|
||||
|
||||
if (overwrite) {
|
||||
if (currentUpdate && currentUpdate.$set) {
|
||||
currentUpdate = currentUpdate.$set;
|
||||
updates.$set = {};
|
||||
_updates = updates.$set;
|
||||
}
|
||||
if (updatedAt && !currentUpdate[updatedAt]) {
|
||||
_updates[updatedAt] = now;
|
||||
}
|
||||
if (createdAt && !currentUpdate[createdAt]) {
|
||||
_updates[createdAt] = now;
|
||||
}
|
||||
return updates;
|
||||
}
|
||||
updates.$set = updates.$set || {};
|
||||
currentUpdate = currentUpdate || {};
|
||||
|
||||
if (updatedAt &&
|
||||
(!currentUpdate.$currentDate || !currentUpdate.$currentDate[updatedAt])) {
|
||||
updates.$set[updatedAt] = now;
|
||||
}
|
||||
|
||||
if (createdAt) {
|
||||
if (currentUpdate[createdAt]) {
|
||||
delete currentUpdate[createdAt];
|
||||
}
|
||||
if (currentUpdate.$set && currentUpdate.$set[createdAt]) {
|
||||
delete currentUpdate.$set[createdAt];
|
||||
}
|
||||
|
||||
updates.$setOnInsert = updates.$setOnInsert || {};
|
||||
updates.$setOnInsert[createdAt] = now;
|
||||
}
|
||||
|
||||
if (Object.keys(updates.$set).length === 0) {
|
||||
delete updates.$set;
|
||||
}
|
||||
|
||||
return updates;
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
const _modifiedPaths = require('../common').modifiedPaths;
|
||||
|
||||
/**
|
||||
* Given an update document with potential update operators (`$set`, etc.)
|
||||
* returns an object whose keys are the directly modified paths.
|
||||
*
|
||||
* If there are any top-level keys that don't start with `$`, we assume those
|
||||
* will get wrapped in a `$set`. The Mongoose Query is responsible for wrapping
|
||||
* top-level keys in `$set`.
|
||||
*
|
||||
* @param {Object} update
|
||||
* @return {Object} modified
|
||||
*/
|
||||
|
||||
module.exports = function modifiedPaths(update) {
|
||||
const keys = Object.keys(update);
|
||||
const res = {};
|
||||
|
||||
const withoutDollarKeys = {};
|
||||
for (const key of keys) {
|
||||
if (key.startsWith('$')) {
|
||||
_modifiedPaths(update[key], '', res);
|
||||
continue;
|
||||
}
|
||||
withoutDollarKeys[key] = update[key];
|
||||
}
|
||||
|
||||
_modifiedPaths(withoutDollarKeys, '', res);
|
||||
|
||||
return res;
|
||||
};
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const Mixed = require('../schema/mixed');
|
||||
const ValidationError = require('../error/validation');
|
||||
const flatten = require('./common').flatten;
|
||||
const modifiedPaths = require('./common').modifiedPaths;
|
||||
const parallel = require('async/parallel');
|
||||
|
||||
/**
|
||||
* Applies validators and defaults to update and findOneAndUpdate operations,
|
||||
* specifically passing a null doc as `this` to validators and defaults
|
||||
*
|
||||
* @param {Query} query
|
||||
* @param {Schema} schema
|
||||
* @param {Object} castedDoc
|
||||
* @param {Object} options
|
||||
* @method runValidatorsOnUpdate
|
||||
* @api private
|
||||
*/
|
||||
|
||||
module.exports = function(query, schema, castedDoc, options) {
|
||||
let _keys;
|
||||
const keys = Object.keys(castedDoc || {});
|
||||
let updatedKeys = {};
|
||||
let updatedValues = {};
|
||||
const isPull = {};
|
||||
const arrayAtomicUpdates = {};
|
||||
const numKeys = keys.length;
|
||||
let hasDollarUpdate = false;
|
||||
const modified = {};
|
||||
let currentUpdate;
|
||||
let key;
|
||||
let i;
|
||||
|
||||
for (i = 0; i < numKeys; ++i) {
|
||||
if (keys[i].charAt(0) === '$') {
|
||||
hasDollarUpdate = true;
|
||||
if (keys[i] === '$push' || keys[i] === '$addToSet') {
|
||||
_keys = Object.keys(castedDoc[keys[i]]);
|
||||
for (let ii = 0; ii < _keys.length; ++ii) {
|
||||
currentUpdate = castedDoc[keys[i]][_keys[ii]];
|
||||
if (currentUpdate && currentUpdate.$each) {
|
||||
arrayAtomicUpdates[_keys[ii]] = (arrayAtomicUpdates[_keys[ii]] || []).
|
||||
concat(currentUpdate.$each);
|
||||
} else {
|
||||
arrayAtomicUpdates[_keys[ii]] = (arrayAtomicUpdates[_keys[ii]] || []).
|
||||
concat([currentUpdate]);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
modifiedPaths(castedDoc[keys[i]], '', modified);
|
||||
const flat = flatten(castedDoc[keys[i]]);
|
||||
const paths = Object.keys(flat);
|
||||
const numPaths = paths.length;
|
||||
for (let j = 0; j < numPaths; ++j) {
|
||||
let updatedPath = paths[j].replace('.$.', '.0.');
|
||||
updatedPath = updatedPath.replace(/\.\$$/, '.0');
|
||||
key = keys[i];
|
||||
// With `$pull` we might flatten `$in`. Skip stuff nested under `$in`
|
||||
// for the rest of the logic, it will get handled later.
|
||||
if (updatedPath.indexOf('$') !== -1) {
|
||||
continue;
|
||||
}
|
||||
if (key === '$set' || key === '$setOnInsert' ||
|
||||
key === '$pull' || key === '$pullAll') {
|
||||
updatedValues[updatedPath] = flat[paths[j]];
|
||||
isPull[updatedPath] = key === '$pull' || key === '$pullAll';
|
||||
} else if (key === '$unset') {
|
||||
updatedValues[updatedPath] = undefined;
|
||||
}
|
||||
updatedKeys[updatedPath] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasDollarUpdate) {
|
||||
modifiedPaths(castedDoc, '', modified);
|
||||
updatedValues = flatten(castedDoc);
|
||||
updatedKeys = Object.keys(updatedValues);
|
||||
}
|
||||
|
||||
const updates = Object.keys(updatedValues);
|
||||
const numUpdates = updates.length;
|
||||
const validatorsToExecute = [];
|
||||
const validationErrors = [];
|
||||
|
||||
const alreadyValidated = [];
|
||||
|
||||
const context = options && options.context === 'query' ? query : null;
|
||||
function iter(i, v) {
|
||||
const schemaPath = schema._getSchema(updates[i]);
|
||||
if (schemaPath) {
|
||||
// gh-4305: `_getSchema()` will report all sub-fields of a 'Mixed' path
|
||||
// as 'Mixed', so avoid double validating them.
|
||||
if (schemaPath instanceof Mixed && schemaPath.$fullPath !== updates[i]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (v && Array.isArray(v.$in)) {
|
||||
v.$in.forEach((v, i) => {
|
||||
validatorsToExecute.push(function(callback) {
|
||||
schemaPath.doValidate(
|
||||
v,
|
||||
function(err) {
|
||||
if (err) {
|
||||
err.path = updates[i] + '.$in.' + i;
|
||||
validationErrors.push(err);
|
||||
}
|
||||
callback(null);
|
||||
},
|
||||
context,
|
||||
{updateValidator: true});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
if (isPull[updates[i]] &&
|
||||
!Array.isArray(v) &&
|
||||
schemaPath.$isMongooseArray) {
|
||||
v = [v];
|
||||
}
|
||||
|
||||
if (schemaPath.$isMongooseDocumentArrayElement && v != null && v.$__ != null) {
|
||||
alreadyValidated.push(updates[i]);
|
||||
validatorsToExecute.push(function(callback) {
|
||||
schemaPath.doValidate(v, function(err) {
|
||||
if (err) {
|
||||
err.path = updates[i];
|
||||
validationErrors.push(err);
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
v.validate(function(err) {
|
||||
if (err) {
|
||||
if (err.errors) {
|
||||
for (const key of Object.keys(err.errors)) {
|
||||
const _err = err.errors[key];
|
||||
_err.path = updates[i] + '.' + key;
|
||||
validationErrors.push(_err);
|
||||
}
|
||||
}
|
||||
}
|
||||
callback(null);
|
||||
});
|
||||
}, context, { updateValidator: true });
|
||||
});
|
||||
} else {
|
||||
validatorsToExecute.push(function(callback) {
|
||||
for (const path of alreadyValidated) {
|
||||
if (updates[i].startsWith(path + '.')) {
|
||||
return callback(null);
|
||||
}
|
||||
}
|
||||
schemaPath.doValidate(v, function(err) {
|
||||
if (err) {
|
||||
err.path = updates[i];
|
||||
validationErrors.push(err);
|
||||
}
|
||||
callback(null);
|
||||
}, context, { updateValidator: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i = 0; i < numUpdates; ++i) {
|
||||
iter(i, updatedValues[updates[i]]);
|
||||
}
|
||||
|
||||
const arrayUpdates = Object.keys(arrayAtomicUpdates);
|
||||
const numArrayUpdates = arrayUpdates.length;
|
||||
for (i = 0; i < numArrayUpdates; ++i) {
|
||||
(function(i) {
|
||||
let schemaPath = schema._getSchema(arrayUpdates[i]);
|
||||
if (schemaPath && schemaPath.$isMongooseDocumentArray) {
|
||||
validatorsToExecute.push(function(callback) {
|
||||
schemaPath.doValidate(
|
||||
arrayAtomicUpdates[arrayUpdates[i]],
|
||||
function(err) {
|
||||
if (err) {
|
||||
err.path = arrayUpdates[i];
|
||||
validationErrors.push(err);
|
||||
}
|
||||
callback(null);
|
||||
},
|
||||
options && options.context === 'query' ? query : null);
|
||||
});
|
||||
} else {
|
||||
schemaPath = schema._getSchema(arrayUpdates[i] + '.0');
|
||||
for (let j = 0; j < arrayAtomicUpdates[arrayUpdates[i]].length; ++j) {
|
||||
(function(j) {
|
||||
validatorsToExecute.push(function(callback) {
|
||||
schemaPath.doValidate(
|
||||
arrayAtomicUpdates[arrayUpdates[i]][j],
|
||||
function(err) {
|
||||
if (err) {
|
||||
err.path = arrayUpdates[i];
|
||||
validationErrors.push(err);
|
||||
}
|
||||
callback(null);
|
||||
},
|
||||
options && options.context === 'query' ? query : null,
|
||||
{ updateValidator: true });
|
||||
});
|
||||
})(j);
|
||||
}
|
||||
}
|
||||
})(i);
|
||||
}
|
||||
|
||||
return function(callback) {
|
||||
parallel(validatorsToExecute, function() {
|
||||
if (validationErrors.length) {
|
||||
const err = new ValidationError(null);
|
||||
for (let i = 0; i < validationErrors.length; ++i) {
|
||||
err.addError(validationErrors[i].path, validationErrors[i]);
|
||||
}
|
||||
return callback(err);
|
||||
}
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user