Skip to content

Commit

Permalink
Merge branch 'master' into fixHeaderLevels
Browse files Browse the repository at this point in the history
  • Loading branch information
vkarpov15 committed Mar 22, 2023
2 parents db7c552 + 765b861 commit 27d412c
Show file tree
Hide file tree
Showing 23 changed files with 948 additions and 660 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/stale.yml
Expand Up @@ -15,7 +15,7 @@ jobs:
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 5 days'
close-issue-message: "This issue was closed because it has been inactive for 19 days since being marked as stale."
close-issue-message: "This issue was closed because it has been inactive for 19 days and has been marked as stale."
days-before-stale: 14
days-before-close: 5
any-of-labels: can't reproduce,help,needs clarification
any-of-labels: can't reproduce,help,needs clarification
19 changes: 19 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,22 @@
6.10.4 / 2023-03-21
===================
* fix(document): apply setters on resulting value when calling Document.prototype.$inc() #13178 #13158
* fix(model): add results property to unordered insertMany() to make it easy to identify exactly which documents were inserted #13163 #12791
* docs(guide+schematypes): add UUID to schematypes guide #13184

7.0.2 / 2023-03-15
==================
* fix: validate array elements when passing array path to validateSync() in pathsToValidate #13167 #13159
* fix(schema): propagate typeKey down to implicitly created subdocuments #13164 #13154
* fix(types): add index param to eachAsync fn #13153 [krosenk729](https://github.com/krosenk729)
* fix(types/documentarray): type DocumentArray constructor parameter as object #13089 #13087 [lpizzinidev](https://github.com/lpizzinidev)
* fix(types): type query `select()` as string, string[], or record; not `any` #13146 #13142 [rbereziuk](https://github.com/rbereziuk)
* fix(types/query): change QueryOptions lean type to Record<string, any> #13150 [lpizzinidev](https://github.com/lpizzinidev)
* docs: add and run eslint-plugin-markdown #13156 [hasezoey](https://github.com/hasezoey)
* docs(generateSearch): fix search generation for API #13161 [hasezoey](https://github.com/hasezoey)
* docs(generateSearch): move config missing error to require #13160 [hasezoey](https://github.com/hasezoey)
* chore: remove unused docs libraries #13172 [hasezoey](https://github.com/hasezoey)

6.10.3 / 2023-03-13
===================
* fix(connection): add stub implementation of doClose to base connection class #13157
Expand Down
1 change: 1 addition & 0 deletions docs/guide.md
Expand Up @@ -79,6 +79,7 @@ The permitted SchemaTypes are:
* [Array](schematypes.html#arrays)
* [Decimal128](api/mongoose.html#mongoose_Mongoose-Decimal128)
* [Map](schematypes.html#maps)
* [UUID](schematypes.html#uuid)

Read more about [SchemaTypes here](schematypes.html).

Expand Down
6 changes: 0 additions & 6 deletions docs/js/cookies.min.js

This file was deleted.

4 changes: 0 additions & 4 deletions docs/js/zepto.min.js

This file was deleted.

1 change: 0 additions & 1 deletion docs/layout.pug
Expand Up @@ -168,7 +168,6 @@ html(lang='en')
.location #{job.location}
.button.jobs-view-more
a(href='/docs/jobs.html') View more jobs!


script(type="text/javascript" src="/docs/js/navbar-search.js")
script(type="text/javascript" src="/docs/js/mobile-navbar-toggle.js")
44 changes: 42 additions & 2 deletions docs/schematypes.md
Expand Up @@ -53,6 +53,7 @@ Check out [Mongoose's plugins search](http://plugins.mongoosejs.io) to find plug
- [Decimal128](api/mongoose.html#mongoose_Mongoose-Decimal128)
- [Map](#maps)
- [Schema](#schemas)
- [UUID](#uuid)

<h3>Example</h3>

Expand Down Expand Up @@ -506,8 +507,6 @@ const Empty4 = new Schema({ any: [{}] });

<h3 id="maps">Maps</h3>

_New in Mongoose 5.1.0_

A `MongooseMap` is a subclass of [JavaScript's `Map` class](http://thecodebarbarian.com/the-80-20-guide-to-maps-in-javascript.html).
In these docs, we'll use the terms 'map' and `MongooseMap` interchangeably.
In Mongoose, maps are how you create a nested document with arbitrary keys.
Expand Down Expand Up @@ -592,6 +591,47 @@ on `socialMediaHandles.$*.oauth`:
const user = await User.findOne().populate('socialMediaHandles.$*.oauth');
```

<h3 id="uuid">UUID</h3>

Mongoose also supports a UUID type that stores UUID instances as [Node.js buffers](https://thecodebarbarian.com/an-overview-of-buffers-in-node-js.html).
We recommend using [ObjectIds](#objectids) rather than UUIDs for unique document ids in Mongoose, but you may use UUIDs if you need to.

In Node.js, a UUID is represented as an instance of `bson.Binary` type with a [getter](./tutorials/getters-setters.html) that converts the binary to a string when you access it.
Mongoose stores UUIDs as [binary data with subtype 4 in MongoDB](https://www.mongodb.com/docs/manual/reference/bson-types/#binary-data).

```javascript
const authorSchema = new Schema({
_id: Schema.Types.UUID, // Can also do `_id: 'UUID'`
name: String
});

const Author = mongoose.model('Author', authorSchema);

const bookSchema = new Schema({
authorId: { type: Schema.Types.UUID, ref: 'Author' }
});
const Book = mongoose.model('Book', bookSchema);

const author = new Author({ name: 'Martin Fowler' });
console.log(typeof author._id); // 'string'
console.log(author.toObject()._id instanceof mongoose.mongo.BSON.Binary); // true

const book = new Book({ authorId: '09190f70-3d30-11e5-8814-0f4df9a59c41' });
```

To create UUIDs, we recommend using [Node's built-in UUIDv4 generator](https://nodejs.org/api/crypto.html#cryptorandomuuidoptions).

```javascript
const { randomUUID } = require('crypto');

const schema = new mongoose.Schema({
docId: {
type: 'UUID',
default: () => randomUUID()
}
});
```

<h2 id="getters"><a href="#getters">Getters</a></h2>

Getters are like virtuals for paths defined in your schema. For example,
Expand Down
27 changes: 17 additions & 10 deletions lib/document.js
Expand Up @@ -17,7 +17,6 @@ const ValidationError = require('./error/validation');
const ValidatorError = require('./error/validator');
const VirtualType = require('./virtualtype');
const $__hasIncludedChildren = require('./helpers/projection/hasIncludedChildren');
const castNumber = require('./cast/number');
const applyDefaults = require('./helpers/document/applyDefaults');
const cleanModifiedSubpaths = require('./helpers/document/cleanModifiedSubpaths');
const clone = require('./helpers/clone');
Expand Down Expand Up @@ -1747,18 +1746,26 @@ Document.prototype.$inc = function $inc(path, val) {
return this;
}

const currentValue = this.$__getValue(path) || 0;
let shouldSet = false;
let valToSet = null;
let valToInc = val;

try {
val = castNumber(val);
val = schemaType.cast(val);
valToSet = schemaType.applySetters(currentValue + val, this);
valToInc = valToSet - currentValue;
shouldSet = true;
} catch (err) {
this.invalidate(path, new MongooseError.CastError('number', val, path, err));
}

const currentValue = this.$__getValue(path) || 0;

this.$__.primitiveAtomics = this.$__.primitiveAtomics || {};
this.$__.primitiveAtomics[path] = { $inc: val };
this.markModified(path);
this.$__setValue(path, currentValue + val);
if (shouldSet) {
this.$__.primitiveAtomics = this.$__.primitiveAtomics || {};
this.$__.primitiveAtomics[path] = { $inc: valToInc };
this.markModified(path);
this.$__setValue(path, valToSet);
}

return this;
};
Expand Down Expand Up @@ -2786,7 +2793,7 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) {

// only validate required fields when necessary
const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip);
let paths = shouldValidateModifiedOnly ?
const paths = shouldValidateModifiedOnly ?
pathDetails[0].filter((path) => this.$isModified(path)) :
pathDetails[0];
const skipSchemaValidators = pathDetails[1];
Expand Down Expand Up @@ -2986,7 +2993,7 @@ Document.prototype.validateSync = function(pathsToValidate, options) {

// only validate required fields when necessary
const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip);
let paths = shouldValidateModifiedOnly ?
const paths = shouldValidateModifiedOnly ?
pathDetails[0].filter((path) => this.$isModified(path)) :
pathDetails[0];
const skipSchemaValidators = pathDetails[1];
Expand Down
7 changes: 6 additions & 1 deletion lib/helpers/timestamps/setupTimestamps.js
Expand Up @@ -7,6 +7,11 @@ const handleTimestampOption = require('../schema/handleTimestampOption');
const setDocumentTimestamps = require('./setDocumentTimestamps');
const symbols = require('../../schema/symbols');

const replaceOps = new Set([
'replaceOne',
'findOneAndReplace'
]);

module.exports = function setupTimestamps(schema, timestamps) {
const childHasTimestamp = schema.childSchemas.find(withTimestamp);
function withTimestamp(s) {
Expand Down Expand Up @@ -90,7 +95,7 @@ module.exports = function setupTimestamps(schema, timestamps) {
currentTime() :
this.model.base.now();
// Replacing with null update should still trigger timestamps
if (this.op === 'findOneAndReplace' && this.getUpdate() == null) {
if (replaceOps.has(this.op) && this.getUpdate() == null) {
this.setUpdate({});
}
applyTimestampsToUpdate(now, createdAt, updatedAt, this.getUpdate(),
Expand Down
35 changes: 29 additions & 6 deletions lib/model.js
Expand Up @@ -2951,7 +2951,7 @@ Model.startSession = function() {
* @param {Array|Object|*} doc(s)
* @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#insertMany)
* @param {Boolean} [options.ordered=true] if true, will fail fast on the first error encountered. If false, will insert all the documents it can and report errors later. An `insertMany()` with `ordered = false` is called an "unordered" `insertMany()`.
* @param {Boolean} [options.rawResult=false] if false, the returned promise resolves to the documents that passed mongoose document validation. If `true`, will return the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/InsertManyResult.html) with a `mongoose` property that contains `validationErrors` if this is an unordered `insertMany`.
* @param {Boolean} [options.rawResult=false] if false, the returned promise resolves to the documents that passed mongoose document validation. If `true`, will return the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/InsertManyResult.html) with a `mongoose` property that contains `validationErrors` and `results` if this is an unordered `insertMany`.
* @param {Boolean} [options.lean=false] if `true`, skips hydrating and validating the documents. This option is useful if you need the extra performance, but Mongoose won't validate the documents before inserting.
* @param {Number} [options.limit=null] this limits the number of documents being processed (validation/casting) by mongoose in parallel, this does **NOT** send the documents in batches to MongoDB. Use this option if you're processing a large number of documents and your app is running out of memory.
* @param {String|Object|Array} [options.populate=null] populates the result documents. This option is a no-op if `rawResult` is set.
Expand Down Expand Up @@ -3008,6 +3008,7 @@ Model.$__insertMany = function(arr, options, callback) {

const validationErrors = [];
const validationErrorsToOriginalOrder = new Map();
const results = ordered ? null : new Array(arr.length);
const toExecute = arr.map((doc, index) =>
callback => {
if (!(doc instanceof _this)) {
Expand All @@ -3033,8 +3034,8 @@ Model.$__insertMany = function(arr, options, callback) {
if (ordered === false) {
validationErrors.push(error);
validationErrorsToOriginalOrder.set(error, index);
callback(null, null);
return;
results[index] = error;
return callback(null, null);
}
callback(error);
}
Expand Down Expand Up @@ -3107,10 +3108,17 @@ Model.$__insertMany = function(arr, options, callback) {

if (rawResult) {
if (ordered === false) {
for (let i = 0; i < results.length; ++i) {
if (results[i] === void 0) {
results[i] = docs[i];
}
}

// Decorate with mongoose validation errors in case of unordered,
// because then still do `insertMany()`
res.mongoose = {
validationErrors: validationErrors
validationErrors: validationErrors,
results: results
};
}
return callback(null, res);
Expand Down Expand Up @@ -3142,10 +3150,24 @@ Model.$__insertMany = function(arr, options, callback) {
const erroredIndexes = new Set((error && error.writeErrors || []).map(err => err.index));

for (let i = 0; i < error.writeErrors.length; ++i) {
const originalIndex = validDocIndexToOriginalIndex.get(error.writeErrors[i].index);
error.writeErrors[i] = {
...error.writeErrors[i],
index: validDocIndexToOriginalIndex.get(error.writeErrors[i].index)
index: originalIndex
};
if (!ordered) {
results[originalIndex] = error.writeErrors[i];
}
}

if (!ordered) {
for (let i = 0; i < results.length; ++i) {
if (results[i] === void 0) {
results[i] = docs[i];
}
}

error.results = results;
}

let firstErroredIndex = -1;
Expand Down Expand Up @@ -3173,7 +3195,8 @@ Model.$__insertMany = function(arr, options, callback) {

if (rawResult && ordered === false) {
error.mongoose = {
validationErrors: validationErrors
validationErrors: validationErrors,
results: results
};
}

Expand Down
8 changes: 4 additions & 4 deletions lib/query.js
Expand Up @@ -4319,6 +4319,10 @@ Query.prototype.exec = async function exec(op) {
try {
await _executePreHooks(this);
res = await this[thunk]();

for (const fn of this._transforms) {
res = fn(res);
}
} catch (err) {
if (err instanceof Kareem.skipWrappedFunction) {
res = err.args[0];
Expand All @@ -4327,10 +4331,6 @@ Query.prototype.exec = async function exec(op) {
}
}

for (const fn of this._transforms) {
res = fn(res);
}

res = await _executePostHooks(this, res, error);

await _executePostExecHooks(this);
Expand Down
4 changes: 2 additions & 2 deletions package.json
@@ -1,7 +1,7 @@
{
"name": "mongoose",
"description": "Mongoose MongoDB ODM",
"version": "7.0.1",
"version": "7.0.2",
"author": "Guillermo Rauch <guillermo@learnboost.com>",
"keywords": [
"mongodb",
Expand Down Expand Up @@ -76,7 +76,7 @@
"docs:clean:stable": "rimraf index.html && rimraf -rf ./docs/*.html && rimraf -rf ./docs/api && rimraf -rf ./docs/tutorials/*.html && rimraf -rf ./docs/typescript/*.html && rimraf -rf ./docs/*.html && rimraf -rf ./docs/source/_docs && rimraf -rf ./tmp",
"docs:clean:5x": "rimraf index.html && rimraf -rf ./docs/5.x && rimraf -rf ./docs/source/_docs && rimraf -rf ./tmp",
"docs:clean:6x": "rimraf index.html && rimraf -rf ./docs/6.x && rimraf -rf ./docs/source/_docs && rimraf -rf ./tmp",
"docs:copy:tmp": "mkdirp ./tmp/docs/css && mkdirp ./tmp/docs/js && mkdirp ./tmp/docs/images && mkdirp ./tmp/docs/tutorials && mkdirp ./tmp/docs/typescript && ncp ./docs/css ./tmp/docs/css --filter=.css$ && ncp ./docs/js ./tmp/docs/js --filter=.js$ && ncp ./docs/images ./tmp/docs/images && ncp ./docs/tutorials ./tmp/docs/tutorials && ncp ./docs/typescript ./tmp/docs/typescript && cp index.html ./tmp && cp docs/*.html ./tmp/docs/",
"docs:copy:tmp": "mkdirp ./tmp/docs/css && mkdirp ./tmp/docs/js && mkdirp ./tmp/docs/images && mkdirp ./tmp/docs/tutorials && mkdirp ./tmp/docs/typescript && mkdirp ./tmp/docs/api && ncp ./docs/css ./tmp/docs/css --filter=.css$ && ncp ./docs/js ./tmp/docs/js --filter=.js$ && ncp ./docs/images ./tmp/docs/images && ncp ./docs/tutorials ./tmp/docs/tutorials && ncp ./docs/typescript ./tmp/docs/typescript && ncp ./docs/api ./tmp/docs/api && cp index.html ./tmp && cp docs/*.html ./tmp/docs/",
"docs:copy:tmp:5x": "rimraf ./docs/5.x && ncp ./tmp ./docs/5.x",
"docs:copy:tmp:6x": "rimraf ./docs/6.x && ncp ./tmp ./docs/6.x",
"docs:checkout:gh-pages": "git checkout gh-pages",
Expand Down
18 changes: 18 additions & 0 deletions test/common.js
Expand Up @@ -152,6 +152,24 @@ module.exports.mongodVersion = async function() {
async function dropDBs() {
this.timeout(60000);

// retry the "dropDBs" actions if the error is "operation was interrupted", which can often happen in replset CI tests
let retries = 5;
while (retries > 0) {
retries -= 1;
try {
await _dropDBs();
} catch (err) {
if (err instanceof mongoose.mongo.MongoWriteConcernError && /operation was interrupted/.test(err.message)) {
console.log('DropDB operation interrupted, retrying'); // log that a error was thrown to know that it is going to re-try
continue;
}

throw err;
}
}
}

async function _dropDBs() {
const db = await module.exports({ noErrorListener: true }).asPromise();
await db.dropDatabase();
await db.close();
Expand Down

0 comments on commit 27d412c

Please sign in to comment.