diff --git a/.eslintrc.js b/.eslintrc.js index 0b98da74134..5377c64c6c2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -11,7 +11,7 @@ module.exports = { 'test/files/*', 'benchmarks', '*.min.js', - 'docs/js/native.js' + '**/docs/js/native.js' ], overrides: [ { @@ -77,7 +77,7 @@ module.exports = { }, { files: [ - 'docs/js/**/*.js' + '**/docs/js/**/*.js' ], env: { node: false, diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 388b9b78a21..8f3d17723bf 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-20.04 name: Benchmark TypeScript Types steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 with: fetch-depth: 0 - name: Setup node diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7df61155d4d..4ad56eaf27b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 8c1f24dae12..d77be106206 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -28,7 +28,8 @@ jobs: runs-on: ubuntu-20.04 name: Test Generating Docs steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 + - run: git fetch --depth=1 --tags # download all tags for documentation - name: Setup node uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 6e9944677fd..3e4e9bd3678 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: stale - uses: actions/stale@v7 + uses: actions/stale@v8 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' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 748be925cf4..fd20721f7b2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest name: Lint JS-Files steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - name: Setup node uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 @@ -57,7 +57,7 @@ jobs: MONGOMS_VERSION: ${{ matrix.mongodb }} MONGOMS_PREFER_GLOBAL_PATH: 1 steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - name: Setup node uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 @@ -92,7 +92,7 @@ jobs: MONGOMS_VERSION: 6.0.4 MONGOMS_PREFER_GLOBAL_PATH: 1 steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - name: Setup node uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 with: @@ -118,7 +118,7 @@ jobs: runs-on: ubuntu-latest name: Replica Set tests steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - name: Setup node uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 with: @@ -135,6 +135,6 @@ jobs: contents: read steps: - name: Check out repo - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - name: Dependency review uses: actions/dependency-review-action@v3 diff --git a/.github/workflows/tidelift-alignment.yml b/.github/workflows/tidelift-alignment.yml index b3b245f2248..4641af81863 100644 --- a/.github/workflows/tidelift-alignment.yml +++ b/.github/workflows/tidelift-alignment.yml @@ -15,7 +15,7 @@ jobs: if: github.repository == 'Automattic/mongoose' steps: - name: Checkout - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - name: Setup node uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 with: diff --git a/.github/workflows/tsd.yml b/.github/workflows/tsd.yml index 2a220877fea..87e3b97048e 100644 --- a/.github/workflows/tsd.yml +++ b/.github/workflows/tsd.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest name: Lint TS-Files steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - name: Setup node uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-latest name: Test Typescript Types steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - name: Setup node uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 diff --git a/.gitignore b/.gitignore index eccf511e6e9..47c0742bb12 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,8 @@ docs/*.html docs/tutorials/*.html docs/typescript/*.html docs/api/*.html +# the below excludes things like "0test.x" too, but gitignore does not have something like js regex "[0-9]+", so this is the best for future versions +docs/[0-9]*.x/ index.html # Local Netlify folder @@ -64,4 +66,4 @@ mongoose-*.tgz examples/ecommerce-netlify-functions/.netlify/state.json notes.md -list.out \ No newline at end of file +list.out diff --git a/CHANGELOG.md b/CHANGELOG.md index b9487427a93..04d1db05fef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,37 @@ +7.0.5 / 2023-04-24 +================== + * fix(schema): correctly handle uuids with populate() #13317 #13267 + * fix(schema): add clusteredIndex to schema options #13286 [jakesjews](https://github.com/jakesjews) + * fix(document): use collection.findOne() for saving docs with no changes to avoid firing findOne middleware #13298 + * types(schema): avoid circular constraint in TSchemaOptions with --incremental by deferring ResolveSchemaOptions<> #13291 #13129 + * docs(subdocs): fix mention of subdocument ".remove" function #13312 [hasezoey](https://github.com/hasezoey) + * docs: add mongoose.Promise removal to migrating to 7 guide #13295 + * docs: updated formatting of Error Handling section to better highlight the two kinds of possible errors #13279 [Ankit-Mandal](https://github.com/Ankit-Mandal) + * docs: fix broken link #13301 #13281 + +7.0.4 / 2023-04-17 +================== + * fix(schema): fix dangling reference to virtual in tree after removeVirtual() #13255 #13085 + * fix(query): cast query filters on `findOneAndUpdate()` #13220 #13219 [dermasmid](https://github.com/dermasmid) + * types(model): aligned watch() type for mongodb 4.6.0 #13208 #13206 + * docs: fix async function anchors #13226 [hasezoey](https://github.com/hasezoey) + * docs: fix schema syntax in exemple #13262 [c-marc](https://github.com/c-marc) + * docs: rework scripts to allow easier setting of current and past versions #13222 +#13148 [hasezoey](https://github.com/hasezoey) + +6.10.5 / 2023-04-06 +=================== + * perf(document): avoid unnecessary loops, conditionals, string manipulation on Document.prototype.get() for 10x speedup on top-level properties #12953 + * fix(model): execute valid write operations if calling bulkWrite() with ordered: false #13218 #13176 + * fix(array): pass-through all parameters #13202 #13201 [hasezoey](https://github.com/hasezoey) + * fix: improve error message when sorting by empty string #13249 #10182 + * docs: add version support and check version docs #13251 #13193 + +5.13.17 / 2023-04-04 +==================== + * fix: backport fix for array filters handling $or and $and #13195 #13192 #10696 [raj-goguardian](https://github.com/raj-goguardian) + * fix: update the isIndexEqual function to take into account non-text indexes when checking compound indexes that include both text and non-text indexes #13138 #13136 [rdeavila94](https://github.com/rdeavila94) + 7.0.3 / 2023-03-23 ================== * fix(query): avoid executing transforms if query wasn't executed #13185 #13165 diff --git a/benchmarks/get.js b/benchmarks/get.js index c0c7dae58be..765569194f3 100644 --- a/benchmarks/get.js +++ b/benchmarks/get.js @@ -1,40 +1,72 @@ 'use strict'; -const get = require('../lib/helpers/get'); - -let obj; - -// Single string -obj = {}; - -let start = Date.now(); -for (let i = 0; i < 10000000; ++i) { - get(obj, 'test', null); -} -console.log('Single string', Date.now() - start); - -// Array of length 1 -obj = {}; -start = Date.now(); -let arr = ['test']; -for (let i = 0; i < 10000000; ++i) { - get(obj, arr, null); -} -console.log('Array of length 1', Date.now() - start); - -// String with dots -obj = { a: { b: 1 } }; -start = Date.now(); -for (let i = 0; i < 10000000; ++i) { - get(obj, 'a.b', null); -} -console.log('String with dots', Date.now() - start); - -// Multi element array -obj = { a: { b: 1 } }; -start = Date.now(); -arr = ['a', 'b']; -for (let i = 0; i < 10000000; ++i) { - get(obj, arr, null); -} -console.log('Multi element array', Date.now() - start); \ No newline at end of file +const mongoose = require('../'); + +run().catch(err => { + console.error(err); + process.exit(-1); +}); + +async function run() { + const schema = new mongoose.Schema({ + field1: {type: String, default: Math.random().toString(36).slice(2, 16)}, + field2: {type: String, default: Math.random().toString(36).slice(2, 16)}, + field3: {type: String, default: Math.random().toString(36).slice(2, 16)}, + field4: {type: String, default: Math.random().toString(36).slice(2, 16)}, + field5: {type: String, default: Math.random().toString(36).slice(2, 16)}, + field6: {type: String, default: Math.random().toString(36).slice(2, 16)}, + field7: {type: String, default: Math.random().toString(36).slice(2, 16)}, + field8: {type: String, default: Math.random().toString(36).slice(2, 16)}, + }, { versionKey: false }); + const TestModel = mongoose.model('Test', schema); + + let tests = []; + for (let i = 0; i < 10000; i++) { + tests.push(new TestModel()); + } + + let loopStart = Date.now(); + + // run loop with mongoose objects + for (let k = 0; k < 100; k++) { + for (let test of tests) { + test.field1; + test.field2; + test.field3; + test.field4; + test.field5; + test.field6; + test.field7; + test.field8; + } + } + + const results = { + 'Model loop ms': Date.now() - loopStart + }; + + const plainTests = []; + for (let test of tests) { + plainTests.push(test.toObject()); + } + + loopStart = Date.now(); + + // run loop with plain objects + for (let k = 0; k < 100; k++) { + for (let test of plainTests) { + test.field1; + test.field2; + test.field3; + test.field4; + test.field5; + test.field6; + test.field7; + test.field8; + } + } + + results['POJO loop ms'] = Date.now() - loopStart; + + console.log(JSON.stringify(results, null, ' ')); +} \ No newline at end of file diff --git a/docs/api_split.pug b/docs/api_split.pug index 94c8cf4f10b..2078d1b28ee 100644 --- a/docs/api_split.pug +++ b/docs/api_split.pug @@ -1,27 +1,27 @@ extends layout append style - link(rel="stylesheet", href="/docs/css/api.css") - script(src="/docs/js/api-bold-current-nav.js") - script(src="/docs/js/convert-old-anchorid.js") + link(rel="stylesheet", href=`${versions.versionedPath}/docs/css/api.css`) + script(src=`${versions.versionedPath}/docs/js/api-bold-current-nav.js`) + script(src=`${versions.versionedPath}/docs/js/convert-old-anchorid.js`) block content - + - h1 #{name} + h1 #{title} include includes/native div.api-nav div.api-nav-content each item in docs - - if (!item.hideFromNav || item.name === name) - div.nav-item(id='nav-' + item.name) - - if (item.name === name) + - if (!item.hideFromNav || item.title === title) + div.nav-item(id='nav-' + item.title) + - if (item.title === title) div.nav-item-title(style="font-weight: bold") a(href=item.fileName + '.html') - | #{item.name} + | #{item.title} ul.nav-item-sub each prop in item.props li @@ -30,7 +30,7 @@ block content - else div.nav-item-title a(href=item.fileName + '.html') - | #{item.name} + | #{item.title} div.api-content ul diff --git a/docs/check-version.md b/docs/check-version.md new file mode 100644 index 00000000000..59c97b8c2bc --- /dev/null +++ b/docs/check-version.md @@ -0,0 +1,34 @@ +# How to Check Your Mongoose Version + +To check what version of Mongoose you are using in Node.js, print out the [`mongoose.version` property](./api/mongoose.html#Mongoose.prototype.version) as follows. + +```javascript +const mongoose = require('mongoose'); + +console.log(mongoose.version); // '7.x.x' +``` + +We recommend printing the Mongoose version from Node.js, because that better handles cases where you have multiple versions of Mongoose installed. +You can also execute the above logic from your terminal using Node.js' `-e` flag as follows. + +``` +# Prints current Mongoose version, e.g. 7.0.3 +node -e "console.log(require('mongoose').version)" +``` + +## Using `npm list` + +You can also [get the installed version of the Mongoose npm package](https://masteringjs.io/tutorials/npm/version) using `npm list`. + +``` +$ npm list mongoose +test@ /path/to/test +└── mongoose@7.0.3 +``` + +`npm list` is helpful because it can identify if you have multiple versions of Mongoose installed. + +Other package managers also support similar functions: + +- [`yarn list --pattern "mongoose"`](https://classic.yarnpkg.com/lang/en/docs/cli/list/) +- [`pnpm list "mongoose"`](https://pnpm.io/cli/list) diff --git a/docs/connections.md b/docs/connections.md index 5f296c16a8c..ab7b588ed3d 100644 --- a/docs/connections.md +++ b/docs/connections.md @@ -95,8 +95,8 @@ await Model.createCollection(); There are two classes of errors that can occur with a Mongoose connection. -- Error on initial connection. If initial connection fails, Mongoose will emit an 'error' event and the promise `mongoose.connect()` returns will reject. However, Mongoose will **not** automatically try to reconnect. -- Error after initial connection was established. Mongoose will attempt to reconnect, and it will emit an 'error' event. +- **Error on initial connection**: If initial connection fails, Mongoose will emit an 'error' event and the promise `mongoose.connect()` returns will reject. However, Mongoose will **not** automatically try to reconnect. +- **Error after initial connection was established**: Mongoose will attempt to reconnect, and it will emit an 'error' event. To handle initial connection errors, you should use `.catch()` or `try/catch` with async/await. diff --git a/docs/css/flexcpc.css b/docs/css/flexcpc.css deleted file mode 100644 index 4b0b8dbe92f..00000000000 --- a/docs/css/flexcpc.css +++ /dev/null @@ -1,124 +0,0 @@ -/* CPC ads */ - -.native-js { - visibility: hidden; - transition: all .25s ease-in-out; - opacity: 0; - flex-flow: column nowrap; - transform: translateY(calc(100% - 35px)); -} - -.native-show { - visibility: visible; - position: fixed; - width: 100%; - bottom: 0; - box-shadow: 0 -1px 4px 1px hsla(0, 0%, 0%, .15); - opacity: 1; - z-index: 10; -} - -.native-show:hover { - transform: translateY(0); -} - -.native-img { - margin-right: 20px; - max-height: 50px; - border-radius: 3px; - width: auto; -} - -.native-sponsor { - margin: 10px 20px; - text-align: center; - text-transform: uppercase; - transform-origin: left; - letter-spacing: .5px; - transition: all .3s ease-in-out; - font-size: 12px; -} - -.native-show:hover .native-sponsor { - transform: scaleY(0); - margin: 0 20px; - opacity: 0; -} - -.native-flex { - display: flex; - padding: 20px 20px 40px; - text-decoration: none; - - flex-flow: row nowrap; - justify-content: space-between; - align-items: center; -} - -.native-main { - display: flex; - - flex-flow: row nowrap; - align-items: center; -} - -.native-details { - display: flex; - margin-right: 30px; - - flex-flow: column nowrap; -} - -.native-company { - margin-bottom: 4px; - text-transform: uppercase; - letter-spacing: 2px; - font-size: 10px; -} - -.native-desc { - letter-spacing: 1px; - font-weight: 300; - line-height: 1.4; -} - -.native-cta { - padding: 10px 14px; - border-radius: 3px; - box-shadow: 0 6px 13px 0 hsla(0, 0%, 0%, .15); - text-transform: uppercase; - white-space: nowrap; - letter-spacing: 1px; - font-weight: 400; - font-size: 12px; - transition: all .3s ease-in-out; - transform: translateY(-1px); -} - -.native-cta:hover { - box-shadow: none; - transform: translateY(1px); -} - -@media only screen and (min-width: 320px) and (max-width: 759px) { - .native-flex { - flex-wrap: wrap; - flex-direction: column; - } - - .native-img { - margin: 0; - } - - .native-details { - margin: 0; - } - - .native-main { - margin-bottom: 20px; - flex-wrap: wrap; - flex-direction: column; - text-align: center; - align-content: center; - } -} diff --git a/docs/css/guide.css b/docs/css/guide.css deleted file mode 100644 index d18ea6af618..00000000000 --- a/docs/css/guide.css +++ /dev/null @@ -1,332 +0,0 @@ -html, body, #content { - height: 100%; -} -:target::before { - content: ">>> "; - color: #1371C9; - font-weight: bold; - font-size: 20px; -} -.module { - min-height: 100%; - box-sizing: border-box; - overflow-x: hidden; -} -body { - background: #d8e2d8 url(/docs/images/square_bg.png) fixed; - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; - color: #333; - -webkit-font-smoothing: antialiased; - -webkit-text-size-adjust: 100%; - padding: 0; - margin: 0; - font-size: 14px; - line-height: 22px; -} -a { - color: #800; - -webkit-transition-property: opacity, -webkit-transform, color, background-color, padding, -webkit-box-shadow; - -webkit-transition-duration: 0.15s; - -webkit-transition-timing-function: ease-out; -} -a:hover { - opacity: 0.8; -} -h1 { - font-family: 'Helvetica Nueue', Helvetica, Arial, FreeSans, sans-serif; - text-rendering: geometricPrecision; -} -pre { - background: rgba(255,255,255,.8); - border: 1px solid #bbb; - padding:5px; - border-radius: 3px; - box-shadow: 1px 3px 6px #ddd; -} -code { - background: rgba(255,255,255,.8); - color: #333; - border-radius: 3px; - font-size: 13px; - font-family: Consolas, "Liberation Mono", Courier, monospace; - /*text-shadow: 1px 2px 2px #555;*/ -} -pre code { - border: 0 none; - padding: 1.2em; - overflow-x: auto; -} -h2 { - margin-top: 0; -} -h2 a { - font-size: 12px; - position: relative; - bottom: 3px; - font-weight: normal; -} -h3 { padding-top: 35px; } -h3 code { - font-weight: normal; -} -hr { - display: none; - height: 1px; - border: 0 none; - padding: 0; - margin: 90px 0; - background: -webkit-gradient(linear, left top, right top, from(rgba(57, 172, 57, 0.0)), color-stop(0.5, rgba(57, 172, 57, 0.33)), to(rgba(57, 172, 57, 0.0))) -} -.doclinks hr { - margin: 10px 0; -} -li { - list-style: square; -} -#header { - padding-top: 22px; - padding-bottom: 25px; - text-transform: lowercase; -} -#header h1 { - margin-top: 0; - margin-bottom: 0; -} -#header h1 a { - text-decoration: none; -} -#header .mongoose { - font-size: 48px; - font-weight: 100; - color: #fff; - letter-spacing: -5px; -} -#links { - position: fixed; - top: 0; - left: 0; - bottom: 0; - width: 210px; - overflow-x: hidden; - overflow-y: auto; - padding: 15px 0 30px 20px; - border-right: 1px solid #ddd; - background: -webkit-gradient(linear, left top, right top, from(transparent), color-stop(0.92, transparent), color-stop(0.9201, rgba(172,172,172, 0.0)), to(rgba(172,172,172, 0.4))), transparent; -} -#links .schematypes span { - display: none; -} -#content { - padding: 0; - margin: 0 0 0 230px; -} -#content .controls { - padding: 5px 15px 5px 10px; - position: fixed; - background: #fff; - border: 3px solid #eee; - border-radius: 0 0 12px 0; - border-width: 0 3px 3px 10px; - width: 100%; - bottom: 0; - opacity: 0.75; - -webkit-transition-property: opacity; - -webkit-transition-duration: 0.15s; - -webkit-transition-timing-function: ease-out; -} -#content .controls:hover { - opacity: .9; -} -#content p { - word-wrap: break-word; -} -#content > ul { - margin: 0; - padding: 0; -} -.private { - display: none; -} -.doclinks li.private a:before, -.doclinks .module.private a:before, -.doclinks item.private a:before { - content: "p"; - background: #333; - color: #fff; - font-size: 11px; - line-height: 15px; - font-weight: normal; - padding: 0 2px; - border-radius: 3px; - border: 1px solid #333; - display: inline-block; - margin-right: 5px; -} -#content .private h3:after { - content: "private"; - background: #333; - color: #fff; - font-size: 11px; - line-height: 15px; - font-weight: normal; - padding: 0 2px; - border-radius: 3px; - border: 1px solid #333; - display: inline-block; - margin-left: 5px; -} -.module { - list-style: none; - padding: 30px 0 0 30px; - border-color: #eee; - border-width: 9px 10px; - border-style: solid; - background-color: #fff; -} -.module > * { - max-width: 700px; -} -.item { - margin-bottom: 175px; -} -.item h3 a { - color: #333; - text-decoration: none; -} -.property h3 span { - color: #444; -} -.description { - margin-top: 25px; -} -.sourcecode { - display: none; -} -.showcode { - font-size: 12px; - cursor: pointer; - display: none; -} -.load .showcode { - display: block; -} -.types a { - text-decoration: none; -} -li.guide ul { - padding-left: 16px; -} - -.important { - background-color: #FBFF94; - margin-left: 5px; -} -.important p { - padding: 22px; -} - -ul.inthewild { - margin: 30px 0 0 -30px; - padding: 0; - width: 125%; - max-width: 125%; -} -ul.inthewild li { - display: inline-block; - list-style: none; -} -ul.inthewild img { - width: 200px; -} - -@media only screen and (device-width: 768px) { - ul.inthewild { - margin-left: 0px; - } -} - -@media only screen and (max-width: 480px) { - ul.inthewild { - margin-left: 0px; - } - ul.inthewild li { - margin: 5px; - border-width: 2px 2px 0 2px; - border-style: solid; - border-color: #eee; - } - ul.inthewild li img { - width: 140px; - } - h2 a { - white-space: nowrap; - } - #forkbanner { display: none } - #header .mongoose { - font-size: 65px; - text-align: center; - } - html, body, #content { - height: auto; - } - #links { - position: static; - width: auto; - border: 0 none; - border-right: 0 none; - border-bottom: 1px solid #ddd; - background: -webkit-gradient(linear, left top, left bottom, from(transparent), color-stop(0.92, transparent), color-stop(0.9201, rgba(172,172,172, 0.0)), to(rgba(172,172,172, 0.4))), transparent; - padding: 15px 0; - } - #links, #links ul, #links li { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } - #links ul { padding: 0 10px 0 0; } - #links li { - list-style: none; - display: inline-block; - width: 25%; - text-align: center; - } - #links .home, #links .support, #links .fork { - display: none; - } - .doclinks { - display: none; - } - #content { margin-left: 0; } - .module { - padding-left: 5px; - border-width: 3px; - } - #links li.guide { - display: block; - width: 390px; - margin-bottom: 15px; - } - #links li.guide > a { - display: none; - } - #links li ul li { - width: 44%; - text-align: left; - } - #links li ul li ul li { - width: 95%; - } - #links .plugins, - #links .changelog { - display: none; - } - #links .schematypes span { - display: inline; - } - #links .double { - width: 332px; - } - #links .double > ul { - display: inline; - float: right; - } - #links .double > ul li { - width: 155px; - } -} diff --git a/docs/css/mongoose5.css b/docs/css/mongoose5.css index 036f1fc1765..842c64dbcf6 100644 --- a/docs/css/mongoose5.css +++ b/docs/css/mongoose5.css @@ -82,23 +82,31 @@ pre code { } .pure-menu-item { - height: 28px; font-size: 12pt; padding-top: 0px; } .pure-menu-link { padding-top: 2px; + padding-bottom: 2px; +} + +/* change sub-item lists to be more dense */ +.sub-item > .pure-menu-link, .tertiary-item > .pure-menu-link { + padding-top: 0px; + padding-bottom: 0px; +} + +.pure-menu-link:hover, .pure-menu-link.selected { + background-color: rgba(0,0,0, 0.1); } li.sub-item { - height: 23px; font-size: 11pt; margin-left: 15px; } li.tertiary-item { - height: 23px; font-size: 11pt; margin-left: 30px; } @@ -207,6 +215,10 @@ pre { font-weight: bold; } +.pure-menu-item:not(:first-child), .pure-menu-item.sub-item, .pure-menu-item.tertiary-item { + margin-top: 2px; +} + /* Mobile */ #mobile-menu { @@ -233,7 +245,7 @@ pre { #menu { display: none; - position: absolute; + position: fixed; top: 45px; border-top: 1px solid #ddd; border-right: 1px solid #ddd; @@ -251,6 +263,9 @@ pre { height: 45px; background-color: #eee; border-bottom: 1px solid #ddd; + position: sticky; + top: 0; + z-index: 1; } #logo { @@ -277,7 +292,6 @@ pre { top: 0px; left: 0; background-color: #eee; - font-size: 10px; /* change this value to increase/decrease button size */ z-index: 10; width: 2em; height: 3px; @@ -289,28 +303,11 @@ pre { background: #ddd; } - .menu-link span { - position: relative; - display: block; - } - - .menu-link span, - .menu-link span:before, - .menu-link span:after { - background-color: #333; - width: 100%; - height: 0.2em; - } - - .menu-link span:before, - .menu-link span:after { - position: absolute; - margin-top: -0.6em; - content: " "; - } - - .menu-link span:after { - margin-top: 0.6em; + #menuLink { + width: 40px; + height: 40px; + padding: 2.5px; + color: rgb(0,0,0); } .active { @@ -323,7 +320,12 @@ pre { } .pure-menu-item:last-of-type { - padding-bottom: 20px; + padding-bottom: 5px; +} + +/* dont add padding if it also has other elements (like a sub-list) */ +.pure-menu-link:not(:last-child) { + padding-bottom: 0px; } /* CPM ads */ @@ -422,4 +424,4 @@ pre { #jobs .jobs-view-more a { color: black; -} \ No newline at end of file +} diff --git a/docs/guides.md b/docs/guides.md index 5362d0b4213..d24aff91797 100644 --- a/docs/guides.md +++ b/docs/guides.md @@ -42,6 +42,10 @@ integrating Mongoose with external tools and frameworks. * [Testing with Jest](jest.html) * [SSL Connections](tutorials/ssl.html) +## Other Guides + +* [How to Check Your Mongoose Version](check-version.html) + ## Migration Guides * [Mongoose 6.x to 7.x](migrating_to_7.html) diff --git a/docs/includes/favicon.pug b/docs/includes/favicon.pug index 4bc3e53c60a..4bb0f3e66d9 100644 --- a/docs/includes/favicon.pug +++ b/docs/includes/favicon.pug @@ -1,13 +1,14 @@ -link(rel='apple-touch-icon', sizes='57x57', href='images/favicon/apple-icon-57x57.png') -link(rel='apple-touch-icon', sizes='60x60', href='images/favicon/apple-icon-60x60.png') -link(rel='apple-touch-icon', sizes='72x72', href='images/favicon/apple-icon-72x72.png') -link(rel='apple-touch-icon', sizes='76x76', href='images/favicon/apple-icon-76x76.png') -link(rel='apple-touch-icon', sizes='114x114', href='images/favicon/apple-icon-114x114.png') -link(rel='apple-touch-icon', sizes='120x120', href='images/favicon/apple-icon-120x120.png') -link(rel='apple-touch-icon', sizes='144x144', href='images/favicon/apple-icon-144x144.png') -link(rel='apple-touch-icon', sizes='152x152', href='images/favicon/apple-icon-152x152.png') -link(rel='apple-touch-icon', sizes='180x180', href='images/favicon/apple-icon-180x180.png') -link(rel='icon', type='image/png', sizes='192x192', href='images/favicon/android-icon-192x192.png') -link(rel='icon', type='image/png', sizes='32x32', href='images/favicon/favicon-32x32.png') -link(rel='icon', type='image/png', sizes='96x96', href='images/favicon/favicon-96x96.png') -link(rel='icon', type='image/png', sizes='16x16', href='images/favicon/favicon-16x16.png') +link(rel='apple-touch-icon', sizes='57x57', href=`${versions.versionedPath}/docs/images/favicon/apple-icon-57x57.png`) +link(rel='apple-touch-icon', sizes='60x60', href=`${versions.versionedPath}/docs/images/favicon/apple-icon-60x60.png`) +link(rel='apple-touch-icon', sizes='72x72', href=`${versions.versionedPath}/docs/images/favicon/apple-icon-72x72.png`) +link(rel='apple-touch-icon', sizes='76x76', href=`${versions.versionedPath}/docs/images/favicon/apple-icon-76x76.png`) +link(rel='apple-touch-icon', sizes='114x114', href=`${versions.versionedPath}/docs/images/favicon/apple-icon-114x114.png`) +link(rel='apple-touch-icon', sizes='120x120', href=`${versions.versionedPath}/docs/images/favicon/apple-icon-120x120.png`) +link(rel='apple-touch-icon', sizes='144x144', href=`${versions.versionedPath}/docs/images/favicon/apple-icon-144x144.png`) +link(rel='apple-touch-icon', sizes='152x152', href=`${versions.versionedPath}/docs/images/favicon/apple-icon-152x152.png`) +link(rel='apple-touch-icon', sizes='180x180', href=`${versions.versionedPath}/docs/images/favicon/apple-icon-180x180.png`) +link(rel='icon', type='image/png', sizes='192x192', href=`${versions.versionedPath}/docs/images/favicon/android-icon-192x192.png`) +link(rel='icon', type='image/png', sizes='32x32', href=`${versions.versionedPath}/docs/images/favicon/favicon-32x32.png`) +link(rel='icon', type='image/png', sizes='96x96', href=`${versions.versionedPath}/docs/images/favicon/favicon-96x96.png`) +link(rel='icon', type='image/png', sizes='16x16', href=`${versions.versionedPath}/docs/images/favicon/favicon-16x16.png`) +link(rel='manifest', href=`${versions.versionedPath}/docs/images/favicon/manifest.json`) diff --git a/docs/includes/native.pug b/docs/includes/native.pug index a60fcbd9a6b..11e6653c685 100644 --- a/docs/includes/native.pug +++ b/docs/includes/native.pug @@ -1,6 +1,6 @@ append style script(type="text/javascript" src="//m.servedby-buysellads.com/monetization.custom.js") - link(rel="stylesheet", href="/docs/css/inlinecpc.css") + link(rel="stylesheet", href=`${versions.versionedPath}/docs/css/inlinecpc.css`) #native-direct script. diff --git a/docs/js/convert-old-anchorid.js b/docs/js/convert-old-anchorid.js index fd8054019c2..5f651092f6e 100644 --- a/docs/js/convert-old-anchorid.js +++ b/docs/js/convert-old-anchorid.js @@ -5,7 +5,7 @@ window.addEventListener('DOMContentLoaded', () => { // only operate on the old id's if (!/^#\w+_\w+(?:-\w+)?$/i.test(anchor)) { - return; + return fixNoAsyncFn(); } // in case there is no anchor, return without modifying the anchor @@ -68,4 +68,25 @@ window.addEventListener('DOMContentLoaded', () => { window.location.hash = `#${test}`; } } + + // function to fix dox not recognizing async functions and resulting in inproper anchors + function fixNoAsyncFn() { + const anchorSlice = anchor.slice(1); + // dont modify anchor if it already exists + if (document.querySelector(`h3[id="${anchorSlice}"`)) { + return; + } + + const tests = [ + `${anchorSlice}()` + ]; + + for (const test of tests) { + // have to use the "[id=]" selector because "#Something()" is not a valid selector (the "()" part) + const header = document.querySelector(`h3[id="${test}"]`); + if (header) { + window.location.hash = `#${test}`; + } + } + } }, { once: true }); diff --git a/docs/js/navbar-search.js b/docs/js/navbar-search.js index feeab61638e..c117663fea5 100644 --- a/docs/js/navbar-search.js +++ b/docs/js/navbar-search.js @@ -1,5 +1,4 @@ 'use strict'; - (function() { const versionFromUrl = window.location.pathname.match(/^\/docs\/(\d+\.x)/); const version = versionFromUrl ? versionFromUrl[1] : null; diff --git a/docs/layout.pug b/docs/layout.pug index ab6876d5a03..5774b6c4ebd 100644 --- a/docs/layout.pug +++ b/docs/layout.pug @@ -10,25 +10,11 @@ html(lang='en') link(rel="stylesheet", href="https://unpkg.com/purecss@1.0.1/build/pure-min.css", integrity="sha384-oAOxQR6DkCoMliIh8yFnu25d7Eq/PHS21PClpwjOTeU2jRSq11vu66rf90/cZr47", crossorigin="anonymous") link(rel="stylesheet", href="https://fonts.googleapis.com/css?family=Open+Sans") - link(rel="stylesheet", href="/docs/css/github.css") - link(rel="stylesheet", href="/docs/css/mongoose5.css") + link(rel="stylesheet", href=`${versions.versionedPath}/docs/css/github.css`) + link(rel="stylesheet", href=`${versions.versionedPath}/docs/css/mongoose5.css`) - link(rel='apple-touch-icon', sizes='57x57', href='images/favicon/apple-icon-57x57.png') - link(rel='apple-touch-icon', sizes='60x60', href='images/favicon/apple-icon-60x60.png') - link(rel='apple-touch-icon', sizes='72x72', href='images/favicon/apple-icon-72x72.png') - link(rel='apple-touch-icon', sizes='76x76', href='images/favicon/apple-icon-76x76.png') - link(rel='apple-touch-icon', sizes='114x114', href='images/favicon/apple-icon-114x114.png') - link(rel='apple-touch-icon', sizes='120x120', href='images/favicon/apple-icon-120x120.png') - link(rel='apple-touch-icon', sizes='144x144', href='images/favicon/apple-icon-144x144.png') - link(rel='apple-touch-icon', sizes='152x152', href='images/favicon/apple-icon-152x152.png') - link(rel='apple-touch-icon', sizes='180x180', href='images/favicon/apple-icon-180x180.png') - link(rel='icon', type='image/png', sizes='192x192', href='images/favicon/android-icon-192x192.png') - link(rel='icon', type='image/png', sizes='32x32', href='images/favicon/favicon-32x32.png') - link(rel='icon', type='image/png', sizes='96x96', href='images/favicon/favicon-96x96.png') - link(rel='icon', type='image/png', sizes='16x16', href='images/favicon/favicon-16x16.png') - link(rel='manifest', href='images/favicon/manifest.json') meta(name='msapplication-TileColor', content='#ffffff') - meta(name='msapplication-TileImage', content='images/favicon/ms-icon-144x144.png') + meta(name='msapplication-TileImage', content=`${versions.versionedPath}/docs/images/favicon/ms-icon-144x144.png`) meta(name='theme-color', content='#ffffff') body @@ -36,130 +22,140 @@ html(lang='en') #layout #mobile-menu a#menuLink.menu-link(href='#menu') - span + #mobile-logo-container a(href="/") - img#logo(src="/docs/images/mongoose5_62x30_transparent.png") + img#logo(src=`${versions.versionedPath}/docs/images/mongoose5_62x30_transparent.png`) span.logo-text mongoose #menu - .pure-menu + nav.pure-menu #logo-container.pure-menu-heading a(href="/") - img#logo(src="/docs/images/mongoose5_62x30_transparent.png") + img#logo(src=`${versions.versionedPath}/docs/images/mongoose5_62x30_transparent.png`) span.logo-text mongoose ul.pure-menu-list#navbar li.pure-menu-horizontal.pure-menu-item.pure-menu-has-children.pure-menu-allow-hover.version - a(href="#").pure-menu-link Version #{package.version} + a(href=`${versions.versionedPath}/docs/index.html`).pure-menu-link Version #{versions.currentVersion.listed} ul.pure-menu-children - li.pure-menu-item - a.pure-menu-link(href="/docs/6.x") Version #{package.latest6x} - li.pure-menu-item - a.pure-menu-link(href="/docs/5.x") Version #{package.latest5x} + - if (versions.currentVersion.listed !== versions.latestVersion.listed) + li.pure-menu-item + a.pure-menu-link(href=`${versions.latestVersion.path}/docs/index.html`) Version #{versions.latestVersion.listed} + each pastVersion in versions.pastVersions + li.pure-menu-item + a.pure-menu-link(href=`/docs/${pastVersion.path}/index.html`) Version #{pastVersion.listed} li.pure-menu-item.search input#search-input-nav(type="text", placeholder="Search") button#search-button-nav - img(src="/docs/images/search.svg") + img(src=`${versions.versionedPath}/docs/images/search.svg`) li.pure-menu-item - a.pure-menu-link(href="/docs/index.html", class=outputUrl === '/docs/index.html' ? 'selected' : '') Quick Start + a.pure-menu-link(href=`${versions.versionedPath}/docs/index.html`, class=outputUrl === `${versions.versionedPath}/docs/index.html` ? 'selected' : '') Quick Start li.pure-menu-item - a.pure-menu-link(href="/docs/guides.html", class=outputUrl === '/docs/guides.html' ? 'selected' : '') Guides - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/guide.html", class=outputUrl === '/docs/schemas.html' ? 'selected' : '') Schemas - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/schematypes.html", class=outputUrl === '/docs/schematypes.html' ? 'selected' : '') SchemaTypes - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/connections.html", class=outputUrl === '/docs/connections.html' ? 'selected' : '') Connections - - if (['/docs/connections', '/docs/tutorials/ssl'].some(path => outputUrl.startsWith(path))) - li.pure-menu-item.tertiary-item - a.pure-menu-link(href="/docs/tutorials/ssl.html", class=outputUrl === '/docs/tutorials/ssl.html' ? 'selected' : '') SSL Connections - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/models.html", class=outputUrl === '/docs/models.html' ? 'selected' : '') Models - - if (['/docs/models', '/docs/change-streams'].some(path => outputUrl.startsWith(path))) - li.pure-menu-item.tertiary-item - a.pure-menu-link(href="/docs/change-streams.html", class=outputUrl === '/docs/change-streams.html' ? 'selected' : '') Change Streams - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/documents.html", class=outputUrl === '/docs/documents.html' ? 'selected' : '') Documents - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/subdocs.html", class=outputUrl === '/docs/subdocs.html' ? 'selected' : '') Subdocuments - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/queries.html", class=outputUrl === '/docs/queries.html' ? 'selected' : '') Queries - - if (['/docs/queries', '/docs/tutorials/findoneandupdate', '/docs/tutorials/lean', '/docs/tutorials/query_casting'].some(path => outputUrl.startsWith(path))) - li.pure-menu-item.tertiary-item - a.pure-menu-link(href="/docs/tutorials/query_casting.html", class=outputUrl === '/docs/tutorials/query_casting.html' ? 'selected' : '') Query Casting - li.pure-menu-item.tertiary-item - a.pure-menu-link(href="/docs/tutorials/findoneandupdate.html", class=outputUrl === '/docs/tutorials/findoneandupdate.html' ? 'selected' : '') findOneAndUpdate - li.pure-menu-item.tertiary-item - a.pure-menu-link(href="/docs/tutorials/lean.html", class=outputUrl === '/docs/tutorials/lean.html' ? 'selected' : '') The Lean Option - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/validation.html", class=outputUrl === '/docs/validation.html' ? 'selected' : '') Validation - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/middleware.html", class=outputUrl === '/docs/middleware.html' ? 'selected' : '') Middleware - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/populate.html", class=outputUrl === '/docs/populate.html' ? 'selected' : '') Populate - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/discriminators.html", class=outputUrl === '/docs/discriminators.html' ? 'selected' : '') Discriminators - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/plugins.html", class=outputUrl === '/docs/plugins.html' ? 'selected' : '') Plugins - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/timestamps.html", class=outputUrl === '/docs/timestamps.html' ? 'selected' : '') Timestamps - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/transactions.html", class=outputUrl === '/docs/transactions.html' ? 'selected' : '') Transactions - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/typescript.html", class=outputUrl === '/docs/typescript.html' ? 'selected' : '') TypeScript - - if (outputUrl.startsWith('/docs/typescript')) - li.pure-menu-item.tertiary-item - a.pure-menu-link(href="/docs/typescript/schemas.html", class=outputUrl === '/docs/typescript/schemas.html' ? 'selected' : '') Schemas - li.pure-menu-item.tertiary-item - a.pure-menu-link(href="/docs/typescript/statics-and-methods.html", class=outputUrl === '/docs/typescript/statics-and-methods.html' ? 'selected' : '') Statics - li.pure-menu-item.tertiary-item - a.pure-menu-link(href="/docs/typescript/query-helpers.html", class=outputUrl === '/docs/typescript/query-helpers.html' ? 'selected' : '') Query Helpers - li.pure-menu-item.tertiary-item - a.pure-menu-link(href="/docs/typescript/populate.html", class=outputUrl === '/docs/typescript/populate.html' ? 'selected' : '') Populate - li.pure-menu-item.tertiary-item - a.pure-menu-link(href="/docs/typescript/subdocuments.html", class=outputUrl === '/docs/typescript/subdocuments.html' ? 'selected' : '') Subdocuments + a.pure-menu-link(href=`${versions.versionedPath}/docs/guides.html`, class=outputUrl === `${versions.versionedPath}/docs/guides.html` ? 'selected' : '') Guides + ul.pure-menu-list + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/guide.html`, class=outputUrl === `${versions.versionedPath}/docs/schemas.html` ? 'selected' : '') Schemas + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/schematypes.html`, class=outputUrl === `${versions.versionedPath}/docs/schematypes.html` ? 'selected' : '') SchemaTypes + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/connections.html`, class=outputUrl === `${versions.versionedPath}/docs/connections.html` ? 'selected' : '') Connections + - if ([`${versions.versionedPath}/docs/connections`, `${versions.versionedPath}/docs/tutorials/ssl`].some(path => outputUrl.startsWith(path))) + ul.pure-menu-list + li.pure-menu-item.tertiary-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/tutorials/ssl.html`, class=outputUrl === `${versions.versionedPath}/docs/tutorials/ssl.html` ? 'selected' : '') SSL Connections + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/models.html`, class=outputUrl === `${versions.versionedPath}/docs/models.html` ? 'selected' : '') Models + - if ([`${versions.versionedPath}/docs/models`, `${versions.versionedPath}/docs/change-streams`].some(path => outputUrl.startsWith(path))) + ul.pure-menu-list + li.pure-menu-item.tertiary-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/change-streams.html`, class=outputUrl === `${versions.versionedPath}/docs/change-streams.html` ? 'selected' : '') Change Streams + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/documents.html`, class=outputUrl === `${versions.versionedPath}/docs/documents.html` ? 'selected' : '') Documents + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/subdocs.html`, class=outputUrl === `${versions.versionedPath}/docs/subdocs.html` ? 'selected' : '') Subdocuments + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/queries.html`, class=outputUrl === `${versions.versionedPath}/docs/queries.html` ? 'selected' : '') Queries + - if ([`${versions.versionedPath}/docs/queries`, `${versions.versionedPath}/docs/tutorials/findoneandupdate`, `${versions.versionedPath}/docs/tutorials/lean`, `${versions.versionedPath}/docs/tutorials/query_casting`].some(path => outputUrl.startsWith(path))) + ul.pure-menu-list + li.pure-menu-item.tertiary-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/tutorials/query_casting.html`, class=outputUrl === `${versions.versionedPath}/docs/tutorials/query_casting.html` ? 'selected' : '') Query Casting + li.pure-menu-item.tertiary-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/tutorials/findoneandupdate.html`, class=outputUrl === `${versions.versionedPath}/docs/tutorials/findoneandupdate.html` ? 'selected' : '') findOneAndUpdate + li.pure-menu-item.tertiary-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/tutorials/lean.html`, class=outputUrl === `${versions.versionedPath}/docs/tutorials/lean.html` ? 'selected' : '') The Lean Option + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/validation.html`, class=outputUrl === `${versions.versionedPath}/docs/validation.html` ? 'selected' : '') Validation + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/middleware.html`, class=outputUrl === `${versions.versionedPath}/docs/middleware.html` ? 'selected' : '') Middleware + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/populate.html`, class=outputUrl === `${versions.versionedPath}/docs/populate.html` ? 'selected' : '') Populate + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/discriminators.html`, class=outputUrl === `${versions.versionedPath}/docs/discriminators.html` ? 'selected' : '') Discriminators + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/plugins.html`, class=outputUrl === `${versions.versionedPath}/docs/plugins.html` ? 'selected' : '') Plugins + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/timestamps.html`, class=outputUrl === `${versions.versionedPath}/docs/timestamps.html` ? 'selected' : '') Timestamps + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/transactions.html`, class=outputUrl === `${versions.versionedPath}/docs/transactions.html` ? 'selected' : '') Transactions + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/typescript.html`, class=outputUrl === `${versions.versionedPath}/docs/typescript.html` ? 'selected' : '') TypeScript + - if (outputUrl.startsWith(`${versions.versionedPath}/docs/typescript`)) + ul.pure-menu-list + li.pure-menu-item.tertiary-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/typescript/schemas.html`, class=outputUrl === `${versions.versionedPath}/docs/typescript/schemas.html` ? 'selected' : '') Schemas + li.pure-menu-item.tertiary-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/typescript/statics-and-methods.html`, class=outputUrl === `${versions.versionedPath}/docs/typescript/statics-and-methods.html` ? 'selected' : '') Statics + li.pure-menu-item.tertiary-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/typescript/query-helpers.html`, class=outputUrl === `${versions.versionedPath}/docs/typescript/query-helpers.html` ? 'selected' : '') Query Helpers + li.pure-menu-item.tertiary-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/typescript/populate.html`, class=outputUrl === `${versions.versionedPath}/docs/typescript/populate.html` ? 'selected' : '') Populate + li.pure-menu-item.tertiary-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/typescript/subdocuments.html`, class=outputUrl === `${versions.versionedPath}/docs/typescript/subdocuments.html` ? 'selected' : '') Subdocuments li.pure-menu-item - a.pure-menu-link(href="/docs/api/mongoose.html", class=outputUrl === '/docs/api/mongoose.html' ? 'selected' : '') API - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/api/mongoose.html", class=outputUrl === '/docs/api/mongoose.html' ? 'selected' : '') Mongoose - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/api/schema.html", class=outputUrl === '/docs/api/schema.html' ? 'selected' : '') Schema - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/api/connection.html", class=outputUrl === '/docs/api/connection.html' ? 'selected' : '') Connection - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/api/document.html", class=outputUrl === '/docs/api/document.html' ? 'selected' : '') Document - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/api/model.html", class=outputUrl === '/docs/api/model.html' ? 'selected' : '') Model - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/api/query.html", class=outputUrl === '/docs/api/query.html' ? 'selected' : '') Query - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/api/aggregate.html", class=outputUrl === '/docs/api/aggregate.html' ? 'selected' : '') Aggregate - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/api/schematype.html", class=outputUrl === '/docs/api/schematype.html' ? 'selected' : '') SchemaType - li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/api/virtualtype.html", class=outputUrl === '/docs/api/virtualtype.html' ? 'selected' : '') VirtualType + a.pure-menu-link(href=`${versions.versionedPath}/docs/api/mongoose.html`, class=outputUrl === `${versions.versionedPath}/docs/api/mongoose.html` ? 'selected' : '') API + ul.pure-menu-list + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/api/mongoose.html`, class=outputUrl === `${versions.versionedPath}/docs/api/mongoose.html` ? 'selected' : '') Mongoose + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/api/schema.html`, class=outputUrl === `${versions.versionedPath}/docs/api/schema.html` ? 'selected' : '') Schema + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/api/connection.html`, class=outputUrl === `${versions.versionedPath}/docs/api/connection.html` ? 'selected' : '') Connection + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/api/document.html`, class=outputUrl === `${versions.versionedPath}/docs/api/document.html` ? 'selected' : '') Document + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/api/model.html`, class=outputUrl === `${versions.versionedPath}/docs/api/model.html` ? 'selected' : '') Model + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/api/query.html`, class=outputUrl === `${versions.versionedPath}/docs/api/query.html` ? 'selected' : '') Query + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/api/aggregate.html`, class=outputUrl === `${versions.versionedPath}/docs/api/aggregate.html` ? 'selected' : '') Aggregate + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/api/schematype.html`, class=outputUrl === `${versions.versionedPath}/docs/api/schematype.html` ? 'selected' : '') SchemaType + li.pure-menu-item.sub-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/api/virtualtype.html`, class=outputUrl === `${versions.versionedPath}/docs/api/virtualtype.html` ? 'selected' : '') VirtualType li.pure-menu-item - a.pure-menu-link(href="/docs/migrating_to_7.html", class=outputUrl === '/docs/migrating_to_7.html' ? 'selected' : '') Migration Guide + a.pure-menu-link(href=`${versions.versionedPath}/docs/migrating_to_7.html`, class=outputUrl === `${versions.versionedPath}/docs/migrating_to_7.html` ? 'selected' : '') Migration Guide li.pure-menu-item - a.pure-menu-link(href="/docs/compatibility.html", class=outputUrl === '/docs/compatibility.html' ? 'selected' : '') Version Compatibility + a.pure-menu-link(href=`${versions.versionedPath}/docs/compatibility.html`, class=outputUrl === `${versions.versionedPath}/docs/compatibility.html` ? 'selected' : '') Version Compatibility li.pure-menu-item - a.pure-menu-link(href="/docs/faq.html", class=outputUrl === '/docs/faq.html' ? 'selected' : '') FAQ + a.pure-menu-link(href=`${versions.versionedPath}/docs/version-support.html`, class=outputUrl === `${versions.versionedPath}/docs/version-support.html` ? 'selected' : '') Version Support li.pure-menu-item - a.pure-menu-link(href="/docs/further_reading.html", class=outputUrl === '/docs/further_reading.html' ? 'selected' : '') Further Reading + a.pure-menu-link(href=`${versions.versionedPath}/docs/faq.html`, class=outputUrl === `${versions.versionedPath}/docs/faq.html` ? 'selected' : '') FAQ li.pure-menu-item - a.pure-menu-link(href="/docs/enterprise.html", class=outputUrl === '/docs/enterprise.html' ? 'selected' : '') For Enterprise + a.pure-menu-link(href=`${versions.versionedPath}/docs/further_reading.html`, class=outputUrl === `${versions.versionedPath}/docs/further_reading.html` ? 'selected' : '') Further Reading li.pure-menu-item - a.pure-menu-link(href="/docs/sponsors.html", , class=outputUrl === '/docs/sponsors.html' ? 'selected' : '') Sponsors + a.pure-menu-link(href=`${versions.versionedPath}/docs/enterprise.html`, class=outputUrl === `${versions.versionedPath}/docs/enterprise.html` ? 'selected' : '') For Enterprise + li.pure-menu-item + a.pure-menu-link(href=`${versions.versionedPath}/docs/sponsors.html`, , class=outputUrl === `${versions.versionedPath}/docs/sponsors.html` ? 'selected' : '') Sponsors div.cpc-ad .container #content block content - - if (!outputUrl.startsWith('/docs/jobs.html')) + - if (!outputUrl.startsWith(`${versions.versionedPath}/docs/jobs.html`)) div#jobs each job in jobs .job-listing - a(href='/docs/jobs.html#' + job._id) + a(href=`${versions.versionedPath}/docs/jobs.html#` + job._id) .company-logo img(src=job.logo) .description @@ -167,7 +163,7 @@ html(lang='en') .title #{job.title} .location #{job.location} .button.jobs-view-more - a(href='/docs/jobs.html') View more jobs! + a(href=`${versions.versionedPath}/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") + script(type="text/javascript" src=`${versions.versionedPath}/docs/js/navbar-search.js`) + script(type="text/javascript" src=`${versions.versionedPath}/docs/js/mobile-navbar-toggle.js`) diff --git a/docs/migrating_to_5.md b/docs/migrating_to_5.md index 37006720568..aa9a3f60748 100644 --- a/docs/migrating_to_5.md +++ b/docs/migrating_to_5.md @@ -1,5 +1,8 @@ # Migrating from 4.x to 5.x +Please note: we plan to discontinue Mongoose 5 support on March 1, 2024. +Please see our [version support guide](./version-support.html). + There are several [backwards-breaking changes](https://github.com/Automattic/mongoose/blob/master/History.md) you should be aware of when migrating from Mongoose 4.x to Mongoose 5.x. diff --git a/docs/migrating_to_6.md b/docs/migrating_to_6.md index ba3c291d037..fe2067fc15d 100644 --- a/docs/migrating_to_6.md +++ b/docs/migrating_to_6.md @@ -6,6 +6,9 @@ } +Please note: we plan to discontinue Mongoose 5 support on March 1, 2024. +Please see our [version support guide](./version-support.html). + There are several [backwards-breaking changes](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md) you should be aware of when migrating from Mongoose 5.x to Mongoose 6.x. diff --git a/docs/migrating_to_7.md b/docs/migrating_to_7.md index 35a8c07d337..096e640037c 100644 --- a/docs/migrating_to_7.md +++ b/docs/migrating_to_7.md @@ -19,6 +19,7 @@ If you're still on Mongoose 5.x, please read the [Mongoose 5.x to 6.x migration * [Removed `castForQueryWrapper()`, updated `castForQuery()` signature](#removed-castforquerywrapper) * [Copy schema options in `Schema.prototype.add()`](#copy-schema-options-in-schema-prototype-add) * [ObjectId bsontype now has lowercase d](#objectid-bsontype-now-has-lowercase-d) +* [Removed support for custom promise libraries](#removed-support-for-custom-promise-libraries) * [TypeScript-specific changes](#typescript-specific-changes) * [Removed `LeanDocument` and support for `extends Document`](#removed-leandocument-and-support-for-extends-document) * [New parameters for `HydratedDocument`](#new-parameters-for-hydrateddocument) @@ -252,6 +253,23 @@ oid._bsontype; // 'ObjectId' in Mongoose 7, 'ObjectID' in older versions of Mong Please update any places where you use `_bsontype` to check if an object is an ObjectId. This may also affect libraries that use Mongoose. +

Removed Support for custom promise libraries

+ +Mongoose 7 no longer supports plugging in custom promise libraries. So the following no longer makes Mongoose return Bluebird promises in Mongoose 7. + +```javascript +const mongoose = require('mongoose'); + +// No-op on Mongoose 7 +mongoose.Promise = require('bluebird'); +``` + +If you want to use Bluebird for all promises globally, you can do the following: + +```javascript +global.Promise = require('bluebird'); +``` +

TypeScript-specific Changes

Removed LeanDocument and support for extends Document

diff --git a/docs/models.md b/docs/models.md index 30f179744f5..200556b96c2 100644 --- a/docs/models.md +++ b/docs/models.md @@ -19,7 +19,7 @@ When you call `mongoose.model()` on a schema, Mongoose _compiles_ a model for you. ```javascript -const schema = new mongoose.Schema({ name: 'string', size: 'string' }); +const schema = new mongoose.Schema({ name: String, size: String }); const Tank = mongoose.model('Tank', schema); ``` diff --git a/docs/search.pug b/docs/search.pug index 9a56ae3fe85..1902035fa56 100644 --- a/docs/search.pug +++ b/docs/search.pug @@ -57,7 +57,7 @@ block content div.search-bar input#search-input(type="text", placeholder="Search") button#search-button - img(src="/docs/images/search.svg") + img(src=`${versions.versionedPath}/docs/images/search.svg`) div#results - script(type="text/javascript" src="/docs/js/search.js") + script(type="text/javascript" src=`${versions.versionedPath}/docs/js/search.js`) diff --git a/docs/source/api.js b/docs/source/api.js index 4eab51320fe..5c13d003d44 100644 --- a/docs/source/api.js +++ b/docs/source/api.js @@ -24,6 +24,11 @@ const files = [ 'lib/schema/array.js', 'lib/schema/documentarray.js', 'lib/schema/SubdocumentPath.js', + 'lib/schema/boolean.js', + 'lib/schema/buffer.js', + 'lib/schema/number.js', + 'lib/schema/objectid.js', + 'lib/schema/string.js', 'lib/options/SchemaTypeOptions.js', 'lib/options/SchemaArrayOptions.js', 'lib/options/SchemaBufferOptions.js', @@ -36,25 +41,61 @@ const files = [ 'lib/types/ArraySubdocument.js', 'lib/types/buffer.js', 'lib/types/decimal128.js', - 'lib/types/map.js' + 'lib/types/map.js', + 'lib/types/array/methods/index.js' ]; -const out = module.exports.docs = []; - -const combinedFiles = []; -for (const file of files) { - try { - const comments = dox.parseComments(fs.readFileSync(`./${file}`, 'utf8'), { raw: true }); - comments.file = file; - combinedFiles.push(comments); - } catch (err) { - // show log of which file has thrown a error for easier debugging - console.error('Error while trying to parseComments for ', file); - throw err; - } +/** @type {Map.} */ +const out = module.exports.docs = new Map(); + +// add custom matchers to dox, to recognize things it does not know about +// see https://github.com/tj/dox/issues/198 +{ + // Some matchers need to be in a specific order, like the "prototype" matcher must be before the static matcher (and inverted because "unshift") + + // "unshift" is used, because the first function to return a object from "contextPatternMatchers" is used (and we need to "overwrite" those specific functions) + + // push a matcher to recognize "Class.fn = async function" as a method + dox.contextPatternMatchers.unshift(function(str) { + const match = /^\s*([\w$.]+)\s*\.\s*([\w$]+)\s*=\s*(?:async\s+)?function/.exec(str); + if (match) { + return { + type: 'method', + receiver: match[1], + name: match[2], + string: match[1] + '.' + match[2] + '()' + }; + } + }); + + // push a matcher to recognize "Class.prototype.fn = async function" as a method + dox.contextPatternMatchers.unshift(function(str) { + const match = /^\s*([\w$.]+)\s*\.\s*prototype\s*\.\s*([\w$]+)\s*=\s*(?:async\s+)?function/.exec(str); + if (match) { + return { + type: 'method', + constructor: match[1], + cons: match[1], + name: match[2], + string: match[1] + '.prototype.' + match[2] + '()' + }; + } + }); + + // push a matcher to recognize "async function" as a function + dox.contextPatternMatchers.unshift(function(str) { + const match = /^\s*(export(\s+default)?\s+)?(?:async\s+)?function\s+([\w$]+)\s*\(/.exec(str); + if (match) { + return { + type: 'function', + name: match[3], + string: match[3] + '()' + }; + } + }); } -parse(); +parseAllFiles(); /** * @typedef {Object} TagObject @@ -103,6 +144,16 @@ parse(); * @property {string} docFileName */ +/** + * @typedef {Object} DocsObj + * @property {string} title The Title of the page + * @property {string} fileName The name of the resulting file + * @property {PropContext[]} props All the functions and values + * @property {string} file The original file (relative to the root of the repository) + * @property {string} editLink The link used for edits + * @property {boolean} [hideFromNav] Indicate that the entry should not be listed in the navigation + */ + /** * Process a file name to a documentation name * @param {string} input @@ -116,20 +167,32 @@ function processName(input) { replace('/methods', ''); const lastSlash = name.lastIndexOf('/'); const fullName = name; - name = name.substr(lastSlash === -1 ? 0 : lastSlash + 1); - if (name === 'core_array') { + const basename = name.substr(lastSlash === -1 ? 0 : lastSlash + 1); + name = basename; + if (basename === 'core_array') { name = 'array'; } - if (fullName === 'schema/array') { - name = 'SchemaArray'; + if (fullName.startsWith('schema/')) { + name = 'Schema'; + if (basename.charAt(0) !== basename.charAt(0).toUpperCase()) { + name += basename.charAt(0).toUpperCase() + basename.substring(1); + } else { + name += basename; + } + } + if (fullName === 'types/array/methods/index') { + name = 'Array'; + } + if (basename === 'SubdocumentPath') { + name = 'SubdocumentPath'; } - if (name === 'documentarray') { + if (basename === 'documentarray') { name = 'DocumentArrayPath'; } - if (name === 'DocumentArray') { + if (basename === 'DocumentArray') { name = 'MongooseDocumentArray'; } - if (name === 'index') { + if (basename === 'index') { name = 'Mongoose'; } @@ -148,215 +211,242 @@ function convertTypesToString(types) { return Array.isArray(types) ? types.join('|') : types; } -function parse() { - for (const props of combinedFiles) { - const { docName: name, docFileName } = processName(props.file); - const data = { - name: name, - fileName: docFileName, - props: [] - }; - - for (const prop of props) { - if (prop.ignore || prop.isPrivate) { - continue; - } +/** + * Parse all files defined in "files" + */ +function parseAllFiles() { + for (const file of files) { + parseFile(file, true); + } +} - /** @type {PropContext} */ - const ctx = prop.ctx || {}; +/** + * Parse a specific file + * @param {String} file The file to parse + * @param {Boolean} throwErr throw the error if one is encountered? + */ +function parseFile(file, throwErr = true) { + try { + const comments = dox.parseComments(fs.readFileSync(file, 'utf8'), { raw: true }); + comments.file = file; + processFile(comments); + } catch (err) { + // show log of which file has thrown a error for easier debugging + console.error('Error while trying to parseComments for ', file); + if (throwErr) { + throw err; + } + } +} - // somehow in "dox", it is named "receiver" sometimes, not "constructor" - // this is used as a fall-back if the handling below does not overwrite it - if ('receiver' in ctx) { - ctx.constructor = ctx.receiver; - delete ctx.receiver; - } +function processFile(props) { + const { docName: name, docFileName } = processName(props.file); + /** @type {DocsObj} */ + const data = { + title: name, + fileName: docFileName, + props: [] + }; - // in some cases "dox" has "ctx.constructor" defined but set to "undefined", which will later be used for setting "ctx.string" - if ('constructor' in ctx && ctx.constructor === undefined) { - ctx.constructorWasUndefined = true; - } + for (const prop of props) { + if (prop.ignore || prop.isPrivate) { + continue; + } - for (const __tag of prop.tags) { - // the following has been done, because otherwise no type could be applied for intellisense - /** @type {TagObject} */ - const tag = __tag; - switch (tag.type) { - case 'see': - if (!Array.isArray(ctx.see)) { - ctx.see = []; - } + /** @type {PropContext} */ + const ctx = prop.ctx || {}; - // for this type, it needs to be parsed from the string itself to support more than 1 word - // this is required because "@see" is kinda badly defined and mongoose uses a slightly customized way (longer text and different kinds of links) - - ctx.see.push(extractTextUrlFromTag(tag, ctx, true)); - break; - case 'receiver': - console.warn(`Found "@receiver" tag in ${ctx.constructor} ${ctx.name}`); - break; - case 'property': - ctx.type = 'property'; - - // using "name" over "string" because "string" also contains the type and maybe other stuff - ctx.name = tag.name; - // only assign "type" if there are types - if (tag.types.length > 0) { - ctx.type = convertTypesToString(tag.types); - } + // somehow in "dox", it is named "receiver" sometimes, not "constructor" + // this is used as a fall-back if the handling below does not overwrite it + if ('receiver' in ctx) { + ctx.constructor = ctx.receiver; + delete ctx.receiver; + } - break; - case 'type': + // in some cases "dox" has "ctx.constructor" defined but set to "undefined", which will later be used for setting "ctx.string" + if ('constructor' in ctx && ctx.constructor === undefined) { + ctx.constructorWasUndefined = true; + } + + for (const __tag of prop.tags) { + // the following has been done, because otherwise no type could be applied for intellisense + /** @type {TagObject} */ + const tag = __tag; + switch (tag.type) { + case 'see': + if (!Array.isArray(ctx.see)) { + ctx.see = []; + } + + // for this type, it needs to be parsed from the string itself to support more than 1 word + // this is required because "@see" is kinda badly defined and mongoose uses a slightly customized way (longer text and different kinds of links) + + ctx.see.push(extractTextUrlFromTag(tag, ctx, true)); + break; + case 'receiver': + console.warn(`Found "@receiver" tag in ${ctx.constructor} ${ctx.name}`); + break; + case 'property': + ctx.type = 'property'; + + // using "name" over "string" because "string" also contains the type and maybe other stuff + ctx.name = tag.name; + // only assign "type" if there are types + if (tag.types.length > 0) { ctx.type = convertTypesToString(tag.types); - break; - case 'static': - ctx.type = 'property'; - ctx.isStatic = true; - // dont take "string" as "name" from here, because jsdoc definitions of "static" do not have parameters, also its defined elsewhere anyway - // ctx.name = tag.string; - break; - case 'function': - ctx.type = 'function'; - ctx.isStatic = true; - ctx.name = tag.string; - // extra parameter to make function definitions independant of where "@function" is defined - // like "@static" could have overwritten "ctx.string" again if defined after "@function" - ctx.isFunction = true; - break; - case 'return': - tag.description = tag.description ? - md.parse(tag.description).replace(/^

/, '').replace(/<\/p>\n?$/, '') : - ''; - - // dox does not add "void" / "undefined" to types, so in the documentation it would result in a empty "«»" - if (tag.string.includes('void') || tag.string.includes('undefined')) { - tag.types.push('void'); - } + } - ctx.return = tag; - break; - case 'inherits': { - const obj = extractTextUrlFromTag(tag, ctx); - // try to get the documentation name for the "@inherits" value - // example: "@inherits SchemaType" -> "schematype.html" - if (!obj.url || obj.url === obj.text) { - let match = undefined; - for (const file of files) { - const { docName, docFileName } = processName(file); - if (docName.toLowerCase().includes(obj.text.toLowerCase())) { - match = docFileName; - break; - } - } + break; + case 'type': + ctx.type = convertTypesToString(tag.types); + break; + case 'static': + ctx.type = 'property'; + ctx.isStatic = true; + // dont take "string" as "name" from here, because jsdoc definitions of "static" do not have parameters, also its defined elsewhere anyway + // ctx.name = tag.string; + break; + case 'function': + ctx.type = 'function'; + ctx.isStatic = true; + ctx.name = tag.string; + // extra parameter to make function definitions independant of where "@function" is defined + // like "@static" could have overwritten "ctx.string" again if defined after "@function" + ctx.isFunction = true; + break; + case 'return': + tag.description = tag.description ? + md.parse(tag.description).replace(/^

/, '').replace(/<\/p>\n?$/, '') : + ''; + + // dox does not add "void" / "undefined" to types, so in the documentation it would result in a empty "«»" + if (tag.string.includes('void') || tag.string.includes('undefined')) { + tag.types.push('void'); + } - if (match) { - obj.url = match + '.html'; - } else { - console.warn(`no match found in files for inherits "${obj.text}" on "${ctx.constructor}.${ctx.name}"`); + ctx.return = tag; + break; + case 'inherits': { + const obj = extractTextUrlFromTag(tag, ctx); + // try to get the documentation name for the "@inherits" value + // example: "@inherits SchemaType" -> "schematype.html" + if (!obj.url || obj.url === obj.text) { + let match = undefined; + for (const file of files) { + const { docName, docFileName } = processName(file); + if (docName.toLowerCase().includes(obj.text.toLowerCase())) { + match = docFileName; + break; } } - ctx.inherits = obj; - break; - } - case 'event': - case 'param': - ctx[tag.type] = (ctx[tag.type] || []); - // the following is required, because in newer "dox" version "null" is not included in "types" anymore, but a seperate property - if (tag.nullable) { - tag.types.push('null'); - } - if (tag.types) { - tag.types = convertTypesToString(tag.types); - } - ctx[tag.type].push(tag); - if (tag.name != null && tag.name.startsWith('[') && tag.name.endsWith(']') && tag.name.includes('.')) { - tag.nested = true; - } - if (tag.variable) { - if (tag.name.startsWith('[')) { - tag.name = '[...' + tag.name.slice(1); - } else { - tag.name = '...' + tag.name; - } + + if (match) { + obj.url = match + '.html'; + } else { + console.warn(`no match found in files for inherits "${obj.text}" on "${ctx.constructor}.${ctx.name}"`); } - tag.description = tag.description ? - md.parse(tag.description).replace(/^

/, '').replace(/<\/p>$/, '') : - ''; - break; - case 'method': - ctx.type = 'method'; - ctx.name = tag.string; - ctx.isFunction = true; - break; - case 'memberOf': - ctx.constructor = tag.parent; - break; - case 'constructor': - ctx.string = tag.string; - ctx.name = tag.string; - ctx.isFunction = true; - break; - case 'instance': - ctx.isInstance = true; - break; - case 'deprecated': - ctx.deprecated = true; - break; + } + ctx.inherits = obj; + break; } + case 'event': + case 'param': + ctx[tag.type] = (ctx[tag.type] || []); + // the following is required, because in newer "dox" version "null" is not included in "types" anymore, but a seperate property + if (tag.nullable) { + tag.types.push('null'); + } + if (tag.types) { + tag.types = convertTypesToString(tag.types); + } + ctx[tag.type].push(tag); + if (tag.name != null && tag.name.startsWith('[') && tag.name.endsWith(']') && tag.name.includes('.')) { + tag.nested = true; + } + if (tag.variable) { + if (tag.name.startsWith('[')) { + tag.name = '[...' + tag.name.slice(1); + } else { + tag.name = '...' + tag.name; + } + } + tag.description = tag.description ? + md.parse(tag.description).replace(/^

/, '').replace(/<\/p>$/, '') : + ''; + break; + case 'method': + ctx.type = 'method'; + ctx.name = tag.string; + ctx.isFunction = true; + break; + case 'memberOf': + ctx.constructor = tag.parent; + break; + case 'constructor': + ctx.string = tag.string; + ctx.name = tag.string; + ctx.isFunction = true; + break; + case 'instance': + ctx.isInstance = true; + break; + case 'deprecated': + ctx.deprecated = true; + break; } + } - if (ctx.isInstance && ctx.isStatic) { - console.warn(`Property "${ctx.name}" in "${ctx.constructor}" has both instance and static JSDOC markings (most likely both @instance and @static)! (File: "${props.file}")`); - } - - // the following if-else-if statement is in this order, because there are more "instance" methods thans static - // the following condition will be true if "isInstance = true" or if "isInstance = false && isStatic = false" AND "ctx.string" are empty or not defined - // if "isStatic" and "isInstance" are falsy and "ctx.string" is not falsy, then rely on the "ctx.string" set by "dox" - if (ctx.isInstance || (!ctx.isStatic && !ctx.isInstance && (!ctx.string || ctx.constructorWasUndefined))) { - // to transform things like "[Symbol.toStringTag]" to ".prototype[Symbol.toStringTag]" instead of ".prototype.[Symbol.toStringTag]" - if (ctx.name.startsWith('[')) { - ctx.string = `${ctx.constructor}.prototype${ctx.name}`; + if (ctx.isInstance && ctx.isStatic) { + console.warn(`Property "${ctx.name}" in "${ctx.constructor}" has both instance and static JSDOC markings (most likely both @instance and @static)! (File: "${props.file}")`); + } - } else { - ctx.string = `${ctx.constructor}.prototype.${ctx.name}`; - } - } else if (ctx.isStatic) { - ctx.string = `${ctx.constructor}.${ctx.name}`; - } + // the following if-else-if statement is in this order, because there are more "instance" methods thans static + // the following condition will be true if "isInstance = true" or if "isInstance = false && isStatic = false" AND "ctx.string" are empty or not defined + // if "isStatic" and "isInstance" are falsy and "ctx.string" is not falsy, then rely on the "ctx.string" set by "dox" + if (ctx.isInstance || (!ctx.isStatic && !ctx.isInstance && (!ctx.string || ctx.constructorWasUndefined))) { + // to transform things like "[Symbol.toStringTag]" to ".prototype[Symbol.toStringTag]" instead of ".prototype.[Symbol.toStringTag]" + if (ctx.name.startsWith('[')) { + ctx.string = `${ctx.constructor}.prototype${ctx.name}`; - // add "()" to the end of the string if function - if ((ctx.isFunction || ctx.type === 'method') && !ctx.string.endsWith('()')) { - ctx.string = ctx.string + '()'; + } else { + ctx.string = `${ctx.constructor}.prototype.${ctx.name}`; } + } else if (ctx.isStatic) { + ctx.string = `${ctx.constructor}.${ctx.name}`; + } - ctx.anchorId = ctx.string; + // add "()" to the end of the string if function + if ((ctx.isFunction || ctx.type === 'method') && !ctx.string.endsWith('()')) { + ctx.string = ctx.string + '()'; + } - ctx.description = prop.description.full. - replace(/
/ig, ' '). - replace(/>/ig, '>'); - ctx.description = md.parse(ctx.description); + ctx.anchorId = ctx.string; - data.props.push(ctx); - } + ctx.description = prop.description.full. + replace(/
/ig, ' '). + replace(/>/ig, '>'); + ctx.description = md.parse(ctx.description); - data.props.sort(function(a, b) { - if (a.string < b.string) { - return -1; - } else { - return 1; - } - }); + data.props.push(ctx); + } - if (props.file.startsWith('lib/options')) { - data.hideFromNav = true; + data.props.sort(function(a, b) { + if (a.string < b.string) { + return -1; + } else { + return 1; } + }); - data.file = props.file; - data.editLink = 'https://github.com/Automattic/mongoose/blob/master/' + + if (props.file.startsWith('lib/options')) { + data.hideFromNav = true; + } + + data.file = props.file; + data.editLink = 'https://github.com/Automattic/mongoose/blob/master/' + props.file; - out.push(data); - } + out.set(data.file, data); } /** @@ -375,8 +465,10 @@ function extractTextUrlFromTag(tag, ctx, warnOnMissingUrl = false) { // "No Href" -> "No Href" // "https://someurl.com" -> "" (fallback added) // "Some#Method #something" -> "Some#Method" + // "Test ./somewhere" -> "Test" + // "Test2 ./somewhere#andsomewhere" -> "Test2" // The remainder is simply taken by a call to "slice" (also the text is trimmed later) - const textMatches = /^(.*? (?=#|\/|(?:https?:)|$))/i.exec(tag.string); + const textMatches = /^(.*? (?=#|\/|(?:https?:)|\.\/|$))/i.exec(tag.string); let text = undefined; let url = undefined; @@ -399,3 +491,6 @@ function extractTextUrlFromTag(tag, ctx, warnOnMissingUrl = false) { url: url || undefined // change to be "undefined" if text is empty or non-valid }; } + +module.exports.parseFile = parseFile; +module.exports.parseAllFiles = parseAllFiles; diff --git a/docs/source/index.js b/docs/source/index.js index ac3e324e5ea..de0ca191cef 100644 --- a/docs/source/index.js +++ b/docs/source/index.js @@ -10,9 +10,12 @@ try { jobs = require('../data/jobs.json'); } catch (err) {} +const api = require('./api'); + /** * @typedef {Object} DocsOptions * @property {String} title Title of the page + * @property {Boolean} [api] Indicate that the page is for API * @property {Boolean} [acquit] Enable test parsing and insertion * @property {Boolean} [markdown] Enable markdown processing * @property {Boolean} [guide] Indicate the page is a guide @@ -29,6 +32,10 @@ const docs = { ...require('./typescript') }; +for (const apidoc of api.docs.values()) { + docs[`docs/api/${apidoc.fileName}.html`] = { ...apidoc, api: true }; +} + docs['index.pug'] = require('./home'); docs['docs/api.md'] = { docs: [], @@ -87,9 +94,13 @@ docs['docs/jobs.pug'] = { docs['docs/change-streams.md'] = { title: 'MongoDB Change Streams in NodeJS with Mongoose', markdown: true }; docs['docs/lodash.md'] = { title: 'Using Mongoose with Lodash', markdown: true }; docs['docs/incompatible_packages.md'] = { title: 'Known Incompatible npm Packages', markdown: true }; +docs['docs/check-version.md'] = { title: 'How to Check Your Mongoose Version', markdown: true }; +docs['docs/version-support.md'] = { title: 'Version Support', markdown: true }; for (const props of Object.values(docs)) { props.jobs = jobs; } -module.exports = docs; +module.exports.fileMap = docs; +/** Re-export for nav without extra filtering */ +module.exports.apiDocs = api.docs; diff --git a/docs/source/splitApiDocs.js b/docs/source/splitApiDocs.js deleted file mode 100644 index 2a6da98dc13..00000000000 --- a/docs/source/splitApiDocs.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -const api = require('./api'); -const fs = require('fs'); -const pug = require('pug'); -const pkg = require('../../package.json'); - -let jobs = []; -try { - jobs = require('../data/jobs.json'); -} catch (err) {} - -api.docs.forEach(file => { - const options = Object.assign({}, file, { - package: pkg, - docs: api.docs, - outputUrl: `/docs/api/${file.fileName}.html`, - jobs, - title: file.name - }); - - const html = pug.renderFile('./docs/api_split.pug', options); - console.log('Write', file.name); - // path is relative to CWD not __dirname - fs.writeFileSync(`./docs/api/${file.fileName}.html`, html); -}); diff --git a/docs/source/utils.js b/docs/source/utils.js index 4eaa3225763..0668867ad6c 100644 --- a/docs/source/utils.js +++ b/docs/source/utils.js @@ -2,7 +2,7 @@ const fs = require('fs'); /** - * @typedef {import("./docsIndex").DocsOptions} DocsOptions + * @typedef {import("./index").DocsOptions} DocsOptions */ /** diff --git a/docs/subdocs.md b/docs/subdocs.md index a0adbe2214d..2c64ba96d98 100644 --- a/docs/subdocs.md +++ b/docs/subdocs.md @@ -65,7 +65,7 @@ parent.children[0].name = 'Matthew'; // `parent.children[0].save()` is a no-op, it triggers middleware but // does **not** actually save the subdocument. You need to save the parent // doc. -parent.save(callback); +await parent.save(); ``` Subdocuments have `save` and `validate` [middleware](middleware.html) @@ -82,9 +82,11 @@ childSchema.pre('save', function(next) { }); const parent = new Parent({ children: [{ name: 'invalid' }] }); -parent.save(function(err) { - console.log(err.message); // #sadpanda -}); +try { + await parent.save(); +} catch (err) { + err.message; // '#sadpanda' +} ``` Subdocuments' `pre('save')` and `pre('validate')` middleware execute @@ -244,10 +246,8 @@ const subdoc = parent.children[0]; console.log(subdoc); // { _id: '501d86090d371bab2c0341c5', name: 'Liesl' } subdoc.isNew; // true -parent.save(function(err) { - if (err) return handleError(err); - console.log('Success!'); -}); +await parent.save(); +console.log('Success!'); ``` You can also create a subdocument without adding it to an array by using the [`create()` method](api/mongoosedocumentarray.html#mongoosedocumentarray_MongooseDocumentArray-create) of Document Arrays. @@ -258,21 +258,18 @@ const newdoc = parent.children.create({ name: 'Aaron' }); ## Removing Subdocs -Each subdocument has its own -[remove](api/subdocument.html#subdocument_Subdocument-remove) method. For -an array subdocument, this is equivalent to calling `.pull()` on the -subdocument. For a single nested subdocument, `remove()` is equivalent -to setting the subdocument to `null`. +Each subdocument has its own [deleteOne](api/subdocument.html#Subdocument.prototype.deleteOne()) method. +For an array subdocument, this is equivalent to calling `.pull()` on the subdocument. +For a single nested subdocument, `deleteOne()` is equivalent to setting the subdocument to [`null`](https://masteringjs.io/tutorials/fundamentals/null). ```javascript // Equivalent to `parent.children.pull(_id)` -parent.children.id(_id).remove(); +parent.children.id(_id).deleteOne(); // Equivalent to `parent.child = null` -parent.child.remove(); -parent.save(function(err) { - if (err) return handleError(err); - console.log('the subdocs were removed'); -}); +parent.child.deleteOne(); + +await parent.save(); +console.log('the subdocs were removed'); ```

Parents of Subdocs

@@ -318,8 +315,7 @@ doc.level1.level2.ownerDocument() === doc; // true

Alternate declaration syntax for arrays

-If you create a schema with an array of objects, Mongoose will automatically -convert the object to a schema for you: +If you create a schema with an array of objects, Mongoose will automatically convert the object to a schema for you: ```javascript const parentSchema = new Schema({ diff --git a/docs/tutorials/findoneandupdate.md b/docs/tutorials/findoneandupdate.md index de3dece2d6e..28a89a92a57 100644 --- a/docs/tutorials/findoneandupdate.md +++ b/docs/tutorials/findoneandupdate.md @@ -1,6 +1,7 @@ # How to Use `findOneAndUpdate()` in Mongoose -The [`findOneAndUpdate()` function in Mongoose](../api/query.html#query_Query-findOneAndUpdate) has a wide variety of use cases. [You should use `save()` to update documents where possible](https://masteringjs.io/tutorials/mongoose/update), but there are some cases where you need to use [`findOneAndUpdate()`](https://masteringjs.io/tutorials/mongoose/findoneandupdate). In this tutorial, you'll see how to use `findOneAndUpdate()`, and learn when you need to use it. +The [`findOneAndUpdate()` function in Mongoose](../api/query.html#query_Query-findOneAndUpdate) has a wide variety of use cases. [You should use `save()` to update documents where possible](https://masteringjs.io/tutorials/mongoose/update), for better [validation](../validation.html) and [middleware](../middleware.html) support. +However, there are some cases where you need to use [`findOneAndUpdate()`](https://masteringjs.io/tutorials/mongoose/findoneandupdate). In this tutorial, you'll see how to use `findOneAndUpdate()`, and learn when you need to use it. * [Getting Started](#getting-started) * [Atomic Updates](#atomic-updates) diff --git a/docs/version-support.md b/docs/version-support.md new file mode 100644 index 00000000000..037e5b1847a --- /dev/null +++ b/docs/version-support.md @@ -0,0 +1,27 @@ +# Version Support + +Mongoose 7.x (released February 27, 2023) is the current Mongoose major version. +We ship all new bug fixes and features to 7.x. + +## Mongoose 6 + +Mongoose 6.x (released August 24, 2021) is currently in legacy support. +We will continue to ship bug fixes to Mongoose 6 until August 24, 2023. +After August 24, 2023, we will only ship security fixes, and backport requested fixes to Mongoose 6. +Please open a [bug report on GitHub](https://github.com/Automattic/mongoose/issues/new?assignees=&labels=&template=bug.yml) to request backporting a fix to Mongoose 6. + +We are **not** actively backporting any new features from Mongoose 7 into Mongoose 6. +Until August 24, 2023, we will backport requested features into Mongoose 6; please open a [feature request on GitHub](https://github.com/Automattic/mongoose/issues/new?assignees=&labels=enhancement%2Cnew+feature&template=feature.yml) to request backporting a feature into Mongoose 6. +After August 24, 2023, we will not backport any new features into Mongoose 6. + +We do not currently have a formal end of life (EOL) date for Mongoose 6. +However, we will not end support for Mongoose 6 until at least January 1, 2024. + +## Mongoose 5 + +Mongoose 5.x (released January 17, 2018) is currently only receiving security fixes and requested bug fixes. +Please open a [bug report on GitHub](https://github.com/Automattic/mongoose/issues/new?assignees=&labels=&template=bug.yml) to request backporting a fix to Mongoose 5. +We will **not** backport any new features from Mongoose 6 or Mongoose 7 into Mongoose 5. + +Mongoose 5.x end of life (EOL) is March 1, 2024. +Mongoose 5.x will no longer receive any updates, security or otherwise, after that date. \ No newline at end of file diff --git a/index.pug b/index.pug index 7976e086b0a..077280505e3 100644 --- a/index.pug +++ b/index.pug @@ -7,22 +7,10 @@ html(lang='en') link(href="//fonts.googleapis.com/css?family=Anonymous+Pro:400,700|Droid+Sans+Mono|Open+Sans:400,700|Linden+Hill|Quattrocento:400,700|News+Cycle:400,700|Antic+Slab|Cabin+Condensed:400,700", rel="stylesheet", type="text/css") link(href="docs/css/default.css", rel="stylesheet") link(href="docs/css/style.css", rel="stylesheet") - link(href="/docs/css/github.css", rel="stylesheet") - - link(rel='apple-touch-icon', sizes='57x57', href='docs/images/favicon/apple-icon-57x57.png') - link(rel='apple-touch-icon', sizes='60x60', href='docs/images/favicon/apple-icon-60x60.png') - link(rel='apple-touch-icon', sizes='72x72', href='docs/images/favicon/apple-icon-72x72.png') - link(rel='apple-touch-icon', sizes='76x76', href='docs/images/favicon/apple-icon-76x76.png') - link(rel='apple-touch-icon', sizes='114x114', href='docs/images/favicon/apple-icon-114x114.png') - link(rel='apple-touch-icon', sizes='120x120', href='docs/images/favicon/apple-icon-120x120.png') - link(rel='apple-touch-icon', sizes='144x144', href='docs/images/favicon/apple-icon-144x144.png') - link(rel='apple-touch-icon', sizes='152x152', href='docs/images/favicon/apple-icon-152x152.png') - link(rel='apple-touch-icon', sizes='180x180', href='docs/images/favicon/apple-icon-180x180.png') - link(rel='icon', type='image/png', sizes='192x192', href='docs/images/favicon/android-icon-192x192.png') - link(rel='icon', type='image/png', sizes='32x32', href='docs/images/favicon/favicon-32x32.png') - link(rel='icon', type='image/png', sizes='96x96', href='docs/images/favicon/favicon-96x96.png') - link(rel='icon', type='image/png', sizes='16x16', href='docs/images/favicon/favicon-16x16.png') - link(rel='manifest', href='docs/images/favicon/manifest.json') + link(href="docs/css/github.css", rel="stylesheet") + + include ./docs/includes/favicon + meta(name='msapplication-TileColor', content='#ffffff') meta(name='msapplication-TileImage', content='docs/images/favicon/ms-icon-144x144.png') meta(name='theme-color', content='#ffffff') diff --git a/lib/aggregate.js b/lib/aggregate.js index cbeef62b496..e2dace83389 100644 --- a/lib/aggregate.js +++ b/lib/aggregate.js @@ -20,7 +20,7 @@ const validRedactStringValues = new Set(['$$DESCEND', '$$PRUNE', '$$KEEP']); /** * Aggregate constructor used for building aggregation pipelines. Do not - * instantiate this class directly, use [Model.aggregate()](/docs/api/model.html#model_Model-aggregate) instead. + * instantiate this class directly, use [Model.aggregate()](https://mongoosejs.com/docs/api/model.html#Model.aggregate()) instead. * * #### Example: * @@ -64,20 +64,20 @@ function Aggregate(pipeline, model) { * Contains options passed down to the [aggregate command](https://www.mongodb.com/docs/manual/reference/command/aggregate/). * Supported options are: * - * - [`allowDiskUse`](#aggregate_Aggregate-allowDiskUse) + * - [`allowDiskUse`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.allowDiskUse()) * - `bypassDocumentValidation` - * - [`collation`](#aggregate_Aggregate-collation) + * - [`collation`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.collation()) * - `comment` - * - [`cursor`](#aggregate_Aggregate-cursor) - * - [`explain`](#aggregate_Aggregate-explain) + * - [`cursor`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.cursor()) + * - [`explain`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.explain()) * - `fieldsAsRaw` - * - [`hint`](#aggregate_Aggregate-hint) + * - [`hint`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.hint()) * - `let` * - `maxTimeMS` * - `raw` - * - [`readConcern`](#aggregate_Aggregate-readConcern) + * - [`readConcern`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.readConcern()) * - `readPreference` - * - [`session`](#aggregate_Aggregate-session) + * - [`session`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.session()) * - `writeConcern` * * @property options @@ -187,7 +187,7 @@ Aggregate.prototype.addFields = function(arg) { /** * Appends a new $project operator to this aggregate pipeline. * - * Mongoose query [selection syntax](#query_Query-select) is also supported. + * Mongoose query [selection syntax](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()) is also supported. * * #### Example: * @@ -847,7 +847,7 @@ Aggregate.prototype.hint = function(value) { }; /** - * Sets the session for this aggregation. Useful for [transactions](/docs/transactions.html). + * Sets the session for this aggregation. Useful for [transactions](https://mongoosejs.com/docs/transactions.html). * * #### Example: * @@ -879,8 +879,8 @@ Aggregate.prototype.session = function(session) { * @param {Object} options keys to merge into current options * @param {Number} [options.maxTimeMS] number limits the time this aggregation will run, see [MongoDB docs on `maxTimeMS`](https://www.mongodb.com/docs/manual/reference/operator/meta/maxTimeMS/) * @param {Boolean} [options.allowDiskUse] boolean if true, the MongoDB server will use the hard drive to store data during this aggregation - * @param {Object} [options.collation] object see [`Aggregate.prototype.collation()`](#aggregate_Aggregate-collation) - * @param {ClientSession} [options.session] ClientSession see [`Aggregate.prototype.session()`](#aggregate_Aggregate-session) + * @param {Object} [options.collation] object see [`Aggregate.prototype.collation()`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.collation()) + * @param {ClientSession} [options.session] ClientSession see [`Aggregate.prototype.session()`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.session()) * @see mongodb https://www.mongodb.com/docs/manual/reference/command/aggregate/ * @return {Aggregate} this * @api public @@ -1096,7 +1096,7 @@ Aggregate.prototype.then = function(resolve, reject) { /** * Executes the query returning a `Promise` which will be * resolved with either the doc(s) or rejected with the error. - * Like [`.then()`](#query_Query-then), but only takes a rejection handler. + * Like [`.then()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.then), but only takes a rejection handler. * Compatible with `await`. * * @param {Function} [reject] diff --git a/lib/browser.js b/lib/browser.js index c6f3b80cdfd..aef666b85a0 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -9,7 +9,7 @@ const DocumentProvider = require('./document_provider.js'); DocumentProvider.setBrowser(true); /** - * The [MongooseError](#error_MongooseError) constructor. + * The [MongooseError](https://mongoosejs.com/docs/api/error.html#Error()) constructor. * * @method Error * @api public @@ -18,7 +18,7 @@ DocumentProvider.setBrowser(true); exports.Error = require('./error/index'); /** - * The Mongoose [Schema](#schema_Schema) constructor + * The Mongoose [Schema](https://mongoosejs.com/docs/api/schema.html#Schema()) constructor * * #### Example: * @@ -42,14 +42,14 @@ exports.Schema = require('./schema'); * * #### Types: * - * - [Array](/docs/schematypes.html#arrays) - * - [Buffer](/docs/schematypes.html#buffers) - * - [Embedded](/docs/schematypes.html#schemas) - * - [DocumentArray](/docs/api/documentarraypath.html) - * - [Decimal128](/docs/api/mongoose.html#mongoose_Mongoose-Decimal128) - * - [ObjectId](/docs/schematypes.html#objectids) - * - [Map](/docs/schematypes.html#maps) - * - [Subdocument](/docs/schematypes.html#schemas) + * - [Array](https://mongoosejs.com/docs/schematypes.html#arrays) + * - [Buffer](https://mongoosejs.com/docs/schematypes.html#buffers) + * - [Embedded](https://mongoosejs.com/docs/schematypes.html#schemas) + * - [DocumentArray](https://mongoosejs.com/docs/api/documentarraypath.html) + * - [Decimal128](https://mongoosejs.com/docs/api/decimal128.html#Decimal128()) + * - [ObjectId](https://mongoosejs.com/docs/schematypes.html#objectids) + * - [Map](https://mongoosejs.com/docs/schematypes.html#maps) + * - [Subdocument](https://mongoosejs.com/docs/schematypes.html#schemas) * * Using this exposed access to the `ObjectId` type, we can construct ids on demand. * @@ -62,7 +62,7 @@ exports.Schema = require('./schema'); exports.Types = require('./types'); /** - * The Mongoose [VirtualType](#virtualtype_VirtualType) constructor + * The Mongoose [VirtualType](https://mongoosejs.com/docs/api/virtualtype.html#VirtualType()) constructor * * @method VirtualType * @api public @@ -77,7 +77,7 @@ exports.VirtualType = require('./virtualtype'); * _Alias of mongoose.Schema.Types for backwards compatibility._ * * @property SchemaTypes - * @see Schema.SchemaTypes #schema_Schema-Types + * @see Schema.SchemaTypes https://mongoosejs.com/docs/api/schema.html#Schema.Types * @api public */ diff --git a/lib/connection.js b/lib/connection.js index a2769614de5..017c47b2d4f 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -152,7 +152,7 @@ Connection.prototype.get = function(key) { * * Supported options include: * - * - `maxTimeMS`: Set [`maxTimeMS`](/docs/api/query.html#query_Query-maxTimeMS) for all queries on this connection. + * - `maxTimeMS`: Set [`maxTimeMS`](https://mongoosejs.com/docs/api/query.html#Query.prototype.maxTimeMS()) for all queries on this connection. * - 'debug': If `true`, prints the operations mongoose sends to MongoDB to the console. If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arugments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`. * * #### Example: @@ -207,7 +207,7 @@ Connection.prototype.name; /** * A [POJO](https://masteringjs.io/tutorials/fundamentals/pojo) containing * a map from model names to models. Contains all models that have been - * added to this connection using [`Connection#model()`](/docs/api/connection.html#connection_Connection-model). + * added to this connection using [`Connection#model()`](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.model()). * * #### Example: * @@ -227,7 +227,7 @@ Connection.prototype.models; /** * A number identifier for this connection. Used for debugging when - * you have [multiple connections](/docs/connections.html#multiple_connections). + * you have [multiple connections](https://mongoosejs.com/docs/connections.html#multiple_connections). * * #### Example: * @@ -939,7 +939,7 @@ function _setClient(conn, client, options, dbName) { } /** - * Destory the connection (not just a alias of [`.close`](#connection_Connection-close)) + * Destory the connection (not just a alias of [`.close`](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.close())) * * @param {Boolean} [force] * @returns {Promise} @@ -1132,7 +1132,7 @@ Connection.prototype.collection = function(name, options) { * @param {Function} fn plugin callback * @param {Object} [opts] optional options * @return {Connection} this - * @see plugins /docs/plugins.html + * @see plugins https://mongoosejs.com/docs/plugins.html * @api public */ @@ -1150,7 +1150,7 @@ Connection.prototype.plugin = function(fn, opts) { * const Ticket = db.model('Ticket', new Schema(..)); * const Venue = db.model('Venue'); * - * _When no `collection` argument is passed, Mongoose produces a collection name by passing the model `name` to the [utils.toCollectionName](#utils_exports-toCollectionName) method. This method pluralizes the name. If you don't like this behavior, either pass a collection name or set your schemas collection name option._ + * _When no `collection` argument is passed, Mongoose produces a collection name by passing the model `name` to the `utils.toCollectionName` method. This method pluralizes the name. If you don't like this behavior, either pass a collection name or set your schemas collection name option._ * * #### Example: * @@ -1170,7 +1170,7 @@ Connection.prototype.plugin = function(fn, opts) { * @param {String} [collection] name of mongodb collection (optional) if not given it will be induced from model name * @param {Object} [options] * @param {Boolean} [options.overwriteModels=false] If true, overwrite existing models with the same name to avoid `OverwriteModelError` - * @see Mongoose#model #index_Mongoose-model + * @see Mongoose#model https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.model() * @return {Model} The compiled model * @api public */ @@ -1324,7 +1324,7 @@ Connection.prototype.deleteModel = function(name) { /** * Watches the entire underlying database for changes. Similar to - * [`Model.watch()`](/docs/api/model.html#model_Model-watch). + * [`Model.watch()`](https://mongoosejs.com/docs/api/model.html#Model.watch()). * * This function does **not** trigger any middleware. In particular, it * does **not** trigger aggregate middleware. diff --git a/lib/cursor/AggregationCursor.js b/lib/cursor/AggregationCursor.js index c2e9cd41a15..9b3e4795722 100644 --- a/lib/cursor/AggregationCursor.js +++ b/lib/cursor/AggregationCursor.js @@ -22,7 +22,7 @@ const util = require('util'); * but **not** the model's post aggregate hooks. * * Unless you're an advanced user, do **not** instantiate this class directly. - * Use [`Aggregate#cursor()`](/docs/api/aggregate.html#aggregate_Aggregate-cursor) instead. + * Use [`Aggregate#cursor()`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.cursor()) instead. * * @param {Aggregate} agg * @inherits Readable https://nodejs.org/api/stream.html#class-streamreadable diff --git a/lib/cursor/QueryCursor.js b/lib/cursor/QueryCursor.js index 0cd1abfaf30..9c04e7a0ecb 100644 --- a/lib/cursor/QueryCursor.js +++ b/lib/cursor/QueryCursor.js @@ -21,7 +21,7 @@ const util = require('util'); * from MongoDB, and the model's post `find` hooks after loading each document. * * Unless you're an advanced user, do **not** instantiate this class directly. - * Use [`Query#cursor()`](/docs/api/query.html#query_Query-cursor) instead. + * Use [`Query#cursor()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.cursor()) instead. * * @param {Query} query * @param {Object} options query options passed to `.find()` diff --git a/lib/document.js b/lib/document.js index aec1402ca56..aacd688b452 100644 --- a/lib/document.js +++ b/lib/document.js @@ -15,7 +15,6 @@ const Schema = require('./schema'); const StrictModeError = require('./error/strict'); const ValidationError = require('./error/validation'); const ValidatorError = require('./error/validator'); -const VirtualType = require('./virtualtype'); const $__hasIncludedChildren = require('./helpers/projection/hasIncludedChildren'); const applyDefaults = require('./helpers/document/applyDefaults'); const cleanModifiedSubpaths = require('./helpers/document/cleanModifiedSubpaths'); @@ -212,7 +211,7 @@ Document.prototype.$isMongooseDocumentPrototype = true; * await user.save(); // Sends an `insertOne` to MongoDB * * On the other hand, if you load an existing document from the database - * using `findOne()` or another [query operation](/docs/queries.html), + * using `findOne()` or another [query operation](https://mongoosejs.com/docs/queries.html), * `$isNew` will be false. * * #### Example: @@ -376,7 +375,7 @@ Object.defineProperty(Document.prototype, '$locals', { * @api public * @property isNew * @memberOf Document - * @see $isNew #document_Document-$isNew + * @see $isNew https://mongoosejs.com/docs/api/document.html#Document.prototype.$isNew * @instance */ @@ -409,12 +408,12 @@ Object.defineProperty(Document.prototype, '$where', { * * #### Note: * - * This getter exists on all documents by default. The getter can be disabled by setting the `id` [option](/docs/guide.html#id) of its `Schema` to false at construction time. + * This getter exists on all documents by default. The getter can be disabled by setting the `id` [option](https://mongoosejs.com/docs/guide.html#id) of its `Schema` to false at construction time. * * new Schema({ name: String }, { id: false }); * * @api public - * @see Schema options /docs/guide.html#options + * @see Schema options https://mongoosejs.com/docs/guide.html#options * @property id * @memberOf Document * @instance @@ -618,8 +617,8 @@ Document.prototype.toBSON = function() { * Called internally after a document is returned from mongodb. Normally, * you do **not** need to call this function on your own. * - * This function triggers `init` [middleware](/docs/middleware.html). - * Note that `init` hooks are [synchronous](/docs/middleware.html#synchronous). + * This function triggers `init` [middleware](https://mongoosejs.com/docs/middleware.html). + * Note that `init` hooks are [synchronous](https://mongoosejs.com/docs/middleware.html#synchronous). * * @param {Object} doc document returned by mongo * @param {Object} [opts] @@ -645,7 +644,7 @@ Document.prototype.init = function(doc, opts, fn) { }; /** - * Alias for [`.init`](#document_Document-init) + * Alias for [`.init`](https://mongoosejs.com/docs/api/document.html#Document.prototype.init()) * * @api public */ @@ -722,6 +721,9 @@ Document.prototype.$__init = function(doc, opts) { function init(self, obj, doc, opts, prefix) { prefix = prefix || ''; + if (obj.$__ != null) { + obj = obj._doc; + } const keys = Object.keys(obj); const len = keys.length; let schemaType; @@ -810,14 +812,14 @@ function init(self, obj, doc, opts, prefix) { * * #### Valid options: * - * - same as in [Model.updateOne](#model_Model-updateOne) + * - same as in [Model.updateOne](https://mongoosejs.com/docs/api/model.html#Model.updateOne) * - * @see Model.updateOne #model_Model-updateOne + * @see Model.updateOne https://mongoosejs.com/docs/api/model.html#Model.updateOne * @param {Object} doc - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) - * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api/query.html#query_Query-lean) and the [Mongoose lean tutorial](/docs/tutorials/lean.html). + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) + * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and the [Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html). * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) - * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. * @param {Function} [callback] * @return {Query} * @api public @@ -853,9 +855,9 @@ Document.prototype.updateOne = function updateOne(doc, options, callback) { * * #### Valid options: * - * - same as in [Model.replaceOne](https://mongoosejs.com/docs/api/model.html#model_Model-replaceOne) + * - same as in [Model.replaceOne](https://mongoosejs.com/docs/api/model.html#Model.replaceOne()) * - * @see Model.replaceOne #model_Model-replaceOne + * @see Model.replaceOne https://mongoosejs.com/docs/api/model.html#Model.replaceOne() * @param {Object} doc * @param {Object} [options] * @param {Function} [callback] @@ -1006,7 +1008,7 @@ Document.prototype.overwrite = function overwrite(obj) { * @param {Any} val the value to set * @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for "on-the-fly" attributes * @param {Object} [options] optionally specify options that modify the behavior of the set - * @param {Boolean} [options.merge=false] if true, setting a [nested path](/docs/subdocs.html#subdocuments-versus-nested-paths) will merge existing values rather than overwrite the whole object. So `doc.set('nested', { a: 1, b: 2 })` becomes `doc.set('nested.a', 1); doc.set('nested.b', 2);` + * @param {Boolean} [options.merge=false] if true, setting a [nested path](https://mongoosejs.com/docs/subdocs.html#subdocuments-versus-nested-paths) will merge existing values rather than overwrite the whole object. So `doc.set('nested', { a: 1, b: 2 })` becomes `doc.set('nested.a', 1); doc.set('nested.b', 2);` * @return {Document} this * @method $set * @memberOf Document @@ -1491,7 +1493,7 @@ function _isManuallyPopulatedArray(val, ref) { /** * Sets the value of a path, or many paths. - * Alias for [`.$set`](#document_Document-$set). + * Alias for [`.$set`](https://mongoosejs.com/docs/api/document.html#Document.prototype.$set()). * * #### Example: * @@ -1806,28 +1808,47 @@ Document.prototype.$__setValue = function(path, val) { Document.prototype.get = function(path, type, options) { let adhoc; - options = options || {}; + if (options == null) { + options = {}; + } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } + const noDottedPath = options.noDottedPath; - let schema = this.$__path(path); + // Fast path if we know we're just accessing top-level path on the document: + // just get the schema path, avoid `$__path()` because that does string manipulation + let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); + + if (schema != null) { + return schema.applyGetters(void 0, this); + } } - if (schema instanceof MixedSchema) { + + if (noDottedPath) { + let obj = this._doc[path]; + if (adhoc) { + obj = adhoc.cast(obj); + } + if (schema != null && options.getters !== false) { + return schema.applyGetters(obj, this); + } + return obj; + } + + if (schema != null && schema.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } - const pieces = path.indexOf('.') === -1 ? [path] : path.split('.'); - let obj = this._doc; - if (schema instanceof VirtualType) { - return schema.applyGetters(void 0, this); - } + const hasDot = path.indexOf('.') !== -1; + let obj = this._doc; + const pieces = hasDot ? path.split('.') : [path]; // Might need to change path for top-level alias if (typeof this.$__schema.aliases[pieces[0]] === 'string') { pieces[0] = this.$__schema.aliases[pieces[0]]; @@ -2003,7 +2024,7 @@ Document.prototype.directModifiedPaths = function() { /** * Returns true if the given path is nullish or only contains empty objects. * Useful for determining whether this subdoc will get stripped out by the - * [minimize option](/docs/guide.html#minimize). + * [minimize option](https://mongoosejs.com/docs/guide.html#minimize). * * #### Example: * @@ -2184,7 +2205,7 @@ Document.prototype.isModified = function(paths, modifiedPaths) { }; /** - * Alias of [`.isModified`](#document_Document-isModified) + * Alias of [`.isModified`](https://mongoosejs.com/docs/api/document.html#Document.prototype.isModified()) * * @method $isModified * @memberOf Document @@ -2463,7 +2484,7 @@ Document.prototype.isDirectSelected = function isDirectSelected(path) { * * #### Note: * - * This method is called `pre` save and if a validation rule is violated, [save](#model_Model-save) is aborted and the error is returned to your `callback`. + * This method is called `pre` save and if a validation rule is violated, [save](https://mongoosejs.com/docs/api/model.html#Model.prototype.save()) is aborted and the error is returned to your `callback`. * * #### Example: * @@ -2527,7 +2548,7 @@ Document.prototype.validate = async function validate(pathsToValidate, options) }; /** - * Alias of [`.validate`](#document_Document-validate) + * Alias of [`.validate`](https://mongoosejs.com/docs/api/document.html#Document.prototype.validate()) * * @method $validate * @memberOf Document @@ -3173,8 +3194,8 @@ function _checkImmutableSubpaths(subdoc, schematype, priorVal) { } /** - * Saves this document by inserting a new document into the database if [document.isNew](#document_Document-isNew) is `true`, - * or sends an [updateOne](#document_Document-updateOne) operation **only** with the modifications to the database, it does not replace the whole document in the latter case. + * Saves this document by inserting a new document into the database if [document.isNew](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew()) is `true`, + * or sends an [updateOne](https://mongoosejs.com/docs/api/document.html#Document.prototype.updateOne()) operation **only** with the modifications to the database, it does not replace the whole document in the latter case. * * #### Example: * @@ -3190,20 +3211,20 @@ function _checkImmutableSubpaths(subdoc, schematype, priorVal) { * newProduct === product; // true * * @param {Object} [options] options optional options - * @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](#document_Document-$session). - * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](https://mongoosejs.com//docs/guide.html#safe). Use the `w` option instead. + * @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](https://mongoosejs.com/docs/api/document.html#Document.prototype.$session()). + * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](https://mongoosejs.com/docs/guide.html#safe). Use the `w` option instead. * @param {Boolean} [options.validateBeforeSave] set to false to save without validating. * @param {Boolean} [options.validateModifiedOnly=false] If `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths. - * @param {Number|String} [options.w] set the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#w-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern) - * @param {Boolean} [options.j] set to true for MongoDB to wait until this `save()` has been [journaled before resolving the returned promise](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern) - * @param {Number} [options.wtimeout] sets a [timeout for the write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern). + * @param {Number|String} [options.w] set the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#w-option). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern) + * @param {Boolean} [options.j] set to true for MongoDB to wait until this `save()` has been [journaled before resolving the returned promise](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern) + * @param {Number} [options.wtimeout] sets a [timeout for the write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern). * @param {Boolean} [options.checkKeys=true] the MongoDB driver prevents you from saving keys that start with '$' or contain '.' by default. Set this option to `false` to skip that check. See [restrictions on field names](https://www.mongodb.com/docs/manual/reference/limits/#Restrictions-on-Field-Names) - * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this `save()`. + * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this `save()`. * @param {Function} [fn] optional callback * @method save * @memberOf Document * @instance - * @throws {DocumentNotFoundError} if this [save updates an existing document](#document_Document-isNew) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating). + * @throws {DocumentNotFoundError} if this [save updates an existing document](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew()) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating). * @return {Promise|undefined} Returns undefined if used with callback or a Promise otherwise. * @api public * @see middleware https://mongoosejs.com/docs/middleware.html @@ -3731,7 +3752,7 @@ Document.prototype.$toObject = function(options, json) { * * doc.toObject({ getters: true }) * - * To apply these options to every document of your schema by default, set your [schemas](#schema_Schema) `toObject` option to the same argument. + * To apply these options to every document of your schema by default, set your [schemas](https://mongoosejs.com/docs/api/schema.html#Schema()) `toObject` option to the same argument. * * schema.set('toObject', { virtuals: true }) * @@ -3805,7 +3826,7 @@ Document.prototype.$toObject = function(options, json) { * doc.toObject({ hide: 'secret _id', transform: true }); // { name: 'Wreck-it Ralph' } * * If you pass a transform in `toObject()` options, Mongoose will apply the transform - * to [subdocuments](/docs/subdocs.html) in addition to the top-level document. + * to [subdocuments](https://mongoosejs.com/docs/subdocs.html) in addition to the top-level document. * Similarly, `transform: false` skips transforms for all subdocuments. * Note that this behavior is different for transforms defined in the schema: * if you define a transform in `schema.options.toObject.transform`, that transform @@ -3827,7 +3848,7 @@ Document.prototype.$toObject = function(options, json) { * * Transforms, like all of these options, are also available for `toJSON`. See [this guide to `JSON.stringify()`](https://thecodebarbarian.com/the-80-20-guide-to-json-stringify-in-javascript.html) to learn why `toJSON()` and `toObject()` are separate functions. * - * See [schema options](/docs/guide.html#toObject) for some more details. + * See [schema options](https://mongoosejs.com/docs/guide.html#toObject) for some more details. * * _During save, no custom options are applied to the document before being sent to the database._ * @@ -4109,20 +4130,20 @@ function omitDeselectedFields(self, json) { /** * The return value of this method is used in calls to [`JSON.stringify(doc)`](https://thecodebarbarian.com/the-80-20-guide-to-json-stringify-in-javascript#the-tojson-function). * - * This method accepts the same options as [Document#toObject](#document_Document-toObject). To apply the options to every document of your schema by default, set your [schemas](#schema_Schema) `toJSON` option to the same argument. + * This method accepts the same options as [Document#toObject](https://mongoosejs.com/docs/api/document.html#Document.prototype.toObject()). To apply the options to every document of your schema by default, set your [schemas](https://mongoosejs.com/docs/api/schema.html#Schema()) `toJSON` option to the same argument. * * schema.set('toJSON', { virtuals: true }); * * There is one difference between `toJSON()` and `toObject()` options. - * When you call `toJSON()`, the [`flattenMaps` option](./document.html#document_Document-toObject) defaults to `true`, because `JSON.stringify()` doesn't convert maps to objects by default. + * When you call `toJSON()`, the [`flattenMaps` option](https://mongoosejs.com/docs/api/document.html#Document.prototype.toObject()) defaults to `true`, because `JSON.stringify()` doesn't convert maps to objects by default. * When you call `toObject()`, the `flattenMaps` option is `false` by default. * - * See [schema options](/docs/guide.html#toJSON) for more information on setting `toJSON` option defaults. + * See [schema options](https://mongoosejs.com/docs/guide.html#toJSON) for more information on setting `toJSON` option defaults. * * @param {Object} options * @param {Boolean} [options.flattenMaps=true] if true, convert Maps to [POJOs](https://masteringjs.io/tutorials/fundamentals/pojo). Useful if you want to `JSON.stringify()` the result. * @return {Object} - * @see Document#toObject #document_Document-toObject + * @see Document#toObject https://mongoosejs.com/docs/api/document.html#Document.prototype.toObject() * @see JSON.stringify() in JavaScript https://thecodebarbarian.com/the-80-20-guide-to-json-stringify-in-javascript.html * @api public * @memberOf Document @@ -4158,7 +4179,7 @@ Document.prototype.parent = function() { }; /** - * Alias for [`parent()`](#document_Document-parent). If this document is a subdocument or populated + * Alias for [`parent()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.parent()). If this document is a subdocument or populated * document, returns the document's parent. Returns `undefined` otherwise. * * @return {Document} @@ -4280,17 +4301,17 @@ Document.prototype.equals = function(doc) { * @param {Object} [match] Conditions for the population query * @param {Object} [options] Options for the population query (sort, etc) * @param {String} [options.path=null] The path to populate. - * @param {string|PopulateOptions} [options.populate=null] Recursively populate paths in the populated documents. See [deep populate docs](/docs/populate.html#deep-populate). + * @param {string|PopulateOptions} [options.populate=null] Recursively populate paths in the populated documents. See [deep populate docs](https://mongoosejs.com/docs/populate.html#deep-populate). * @param {boolean} [options.retainNullValues=false] by default, Mongoose removes null and undefined values from populated arrays. Use this option to make `populate()` retain `null` and `undefined` array entries. - * @param {boolean} [options.getters=false] if true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](/docs/schematypes.html#schematype-options). + * @param {boolean} [options.getters=false] if true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](https://mongoosejs.com/docs/schematypes.html#schematype-options). * @param {boolean} [options.clone=false] When you do `BlogPost.find().populate('author')`, blog posts with the same author will share 1 copy of an `author` doc. Enable this option to make Mongoose clone populated docs before assigning them. * @param {Object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://www.mongodb.com/docs/manual/tutorial/query-documents/), or a function that returns a filter object. * @param {Function} [options.transform=null] Function that Mongoose will call on every populated document that allows you to transform the populated document. * @param {Object} [options.options=null] Additional options like `limit` and `lean`. * @param {Function} [callback] Callback - * @see population /docs/populate.html - * @see Query#select #query_Query-select - * @see Model.populate #model_Model-populate + * @see population https://mongoosejs.com/docs/populate.html + * @see Query#select https://mongoosejs.com/docs/api/query.html#Query.prototype.select() + * @see Model.populate https://mongoosejs.com/docs/api/model.html#Model.populate() * @memberOf Document * @instance * @return {Promise|null} Returns a Promise if no `callback` is given. @@ -4434,7 +4455,7 @@ Document.prototype.populated = function(path, val, options) { }; /** - * Alias of [`.populated`](#document_Document-populated). + * Alias of [`.populated`](https://mongoosejs.com/docs/api/document.html#Document.prototype.populated()). * * @method $populated * @memberOf Document @@ -4499,7 +4520,7 @@ Document.prototype.$assertPopulated = function $assertPopulated(path, values) { * * @param {String|String[]} [path] Specific Path to depopulate. If unset, will depopulate all paths on the Document. Or multiple space-delimited paths. * @return {Document} this - * @see Document.populate #document_Document-populate + * @see Document.populate https://mongoosejs.com/docs/api/document.html#Document.prototype.populate() * @api public * @memberOf Document * @instance diff --git a/lib/error/index.js b/lib/error/index.js index a2c5fbebe69..6e31a83ee82 100644 --- a/lib/error/index.js +++ b/lib/error/index.js @@ -26,17 +26,17 @@ const MongooseError = require('./mongooseError'); * - `MongooseError`: general Mongoose error * - `CastError`: Mongoose could not convert a value to the type defined in the schema path. May be in a `ValidationError` class' `errors` property. * - `DivergentArrayError`: You attempted to `save()` an array that was modified after you loaded it with a `$elemMatch` or similar projection - * - `MissingSchemaError`: You tried to access a model with [`mongoose.model()`](mongoose.html#mongoose_Mongoose-model) that was not defined - * - `DocumentNotFoundError`: The document you tried to [`save()`](document.html#document_Document-save) was not found + * - `MissingSchemaError`: You tried to access a model with [`mongoose.model()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.model()) that was not defined + * - `DocumentNotFoundError`: The document you tried to [`save()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.save()) was not found * - `ValidatorError`: error from an individual schema path's validator - * - `ValidationError`: error returned from [`validate()`](document.html#document_Document-validate) or [`validateSync()`](document.html#document_Document-validateSync). Contains zero or more `ValidatorError` instances in `.errors` property. + * - `ValidationError`: error returned from [`validate()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.validate()) or [`validateSync()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.validateSync()). Contains zero or more `ValidatorError` instances in `.errors` property. * - `MissingSchemaError`: You called `mongoose.Document()` without a schema - * - `ObjectExpectedError`: Thrown when you set a nested path to a non-object value with [strict mode set](/docs/guide.html#strict). + * - `ObjectExpectedError`: Thrown when you set a nested path to a non-object value with [strict mode set](https://mongoosejs.com/docs/guide.html#strict). * - `ObjectParameterError`: Thrown when you pass a non-object value to a function which expects an object as a paramter - * - `OverwriteModelError`: Thrown when you call [`mongoose.model()`](mongoose.html#mongoose_Mongoose-model) to re-define a model that was already defined. - * - `ParallelSaveError`: Thrown when you call [`save()`](model.html#model_Model-save) on a document when the same document instance is already saving. - * - `StrictModeError`: Thrown when you set a path that isn't the schema and [strict mode](/docs/guide.html#strict) is set to `throw`. - * - `VersionError`: Thrown when the [document is out of sync](/docs/guide.html#versionKey) + * - `OverwriteModelError`: Thrown when you call [`mongoose.model()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.model()) to re-define a model that was already defined. + * - `ParallelSaveError`: Thrown when you call [`save()`](https://mongoosejs.com/docs/api/model.html#Model.prototype.save()) on a document when the same document instance is already saving. + * - `StrictModeError`: Thrown when you set a path that isn't the schema and [strict mode](https://mongoosejs.com/docs/guide.html#strict) is set to `throw`. + * - `VersionError`: Thrown when the [document is out of sync](https://mongoosejs.com/docs/guide.html#versionKey) * * @api public * @property {String} name @@ -53,7 +53,7 @@ module.exports = exports = MongooseError; /** * The default built-in validator error messages. * - * @see Error.messages #error_messages_MongooseError-messages + * @see Error.messages https://mongoosejs.com/docs/api/error.html#Error.messages * @api public * @memberOf Error * @static @@ -90,7 +90,7 @@ MongooseError.DocumentNotFoundError = require('./notFound'); MongooseError.CastError = require('./cast'); /** - * An instance of this error class will be returned when [validation](/docs/validation.html) failed. + * An instance of this error class will be returned when [validation](https://mongoosejs.com/docs/validation.html) failed. * The `errors` property contains an object whose keys are the paths that failed and whose values are * instances of CastError or ValidationError. * @@ -139,7 +139,7 @@ MongooseError.ValidatorError = require('./validator'); /** * An instance of this error class will be returned when you call `save()` after * the document in the database was changed in a potentially unsafe way. See - * the [`versionKey` option](/docs/guide.html#versionKey) for more information. + * the [`versionKey` option](https://mongoosejs.com/docs/guide.html#versionKey) for more information. * * @api public * @memberOf Error @@ -150,7 +150,7 @@ MongooseError.VersionError = require('./version'); /** * An instance of this error class will be returned when you call `save()` multiple - * times on the same document in parallel. See the [FAQ](/docs/faq.html) for more + * times on the same document in parallel. See the [FAQ](https://mongoosejs.com/docs/faq.html) for more * information. * * @api public @@ -162,7 +162,7 @@ MongooseError.ParallelSaveError = require('./parallelSave'); /** * Thrown when a model with the given name was already registered on the connection. - * See [the FAQ about `OverwriteModelError`](/docs/faq.html#overwrite-model-error). + * See [the FAQ about `OverwriteModelError`](https://mongoosejs.com/docs/faq.html#overwrite-model-error). * * @api public * @memberOf Error diff --git a/lib/helpers/document/compile.js b/lib/helpers/document/compile.js index fd790295cf0..51721e7a490 100644 --- a/lib/helpers/document/compile.js +++ b/lib/helpers/document/compile.js @@ -25,6 +25,10 @@ const _isEmptyOptions = Object.freeze({ transform: false }); +const noDottedPathGetOptions = Object.freeze({ + noDottedPath: true +}); + /** * Compiles schemas. * @param {Object} tree @@ -65,6 +69,7 @@ function defineKey({ prop, subprops, prototype, prefix, options }) { Document = Document || require('../../document'); const path = (prefix ? prefix + '.' : '') + prop; prefix = prefix || ''; + const useGetOptions = prefix ? Object.freeze({}) : noDottedPathGetOptions; if (subprops) { Object.defineProperty(prototype, prop, { @@ -189,7 +194,12 @@ function defineKey({ prop, subprops, prototype, prefix, options }) { enumerable: true, configurable: true, get: function() { - return this[getSymbol].call(this.$__[scopeSymbol] || this, path); + return this[getSymbol].call( + this.$__[scopeSymbol] || this, + path, + null, + useGetOptions + ); }, set: function(v) { this.$set.call(this.$__[scopeSymbol] || this, path, v); diff --git a/lib/index.js b/lib/index.js index 681b5a456cf..c838b3ea2b5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -54,7 +54,7 @@ const objectIdHexRegexp = /^[0-9A-Fa-f]{24}$/; * const m = new mongoose.Mongoose(); * * @api public - * @param {Object} options see [`Mongoose#set()` docs](/docs/api/mongoose.html#mongoose_Mongoose-set) + * @param {Object} options see [`Mongoose#set()` docs](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.set()) */ function Mongoose(options) { this.connections = []; @@ -192,25 +192,25 @@ Mongoose.prototype.setDriver = function setDriver(driver) { * - `allowDiskUse`: Set to `true` to set `allowDiskUse` to true to all aggregation operations by default. * - `applyPluginsToChildSchemas`: `true` by default. Set to false to skip applying global plugins to child schemas * - `applyPluginsToDiscriminators`: `false` by default. Set to true to apply global plugins to discriminator schemas. This typically isn't necessary because plugins are applied to the base schema and discriminators copy all middleware, methods, statics, and properties from the base schema. - * - `autoCreate`: Set to `true` to make Mongoose call [`Model.createCollection()`](/docs/api/model.html#model_Model-createCollection) automatically when you create a model with `mongoose.model()` or `conn.model()`. This is useful for testing transactions, change streams, and other features that require the collection to exist. + * - `autoCreate`: Set to `true` to make Mongoose call [`Model.createCollection()`](https://mongoosejs.com/docs/api/model.html#Model.createCollection()) automatically when you create a model with `mongoose.model()` or `conn.model()`. This is useful for testing transactions, change streams, and other features that require the collection to exist. * - `autoIndex`: `true` by default. Set to false to disable automatic index creation for all models associated with this Mongoose instance. * - `bufferCommands`: enable/disable mongoose's buffering mechanism for all connections and models * - `bufferTimeoutMS`: If bufferCommands is on, this option sets the maximum amount of time Mongoose buffering will wait before throwing an error. If not specified, Mongoose will use 10000 (10 seconds). * - `cloneSchemas`: `false` by default. Set to `true` to `clone()` all schemas before compiling into a model. * - `debug`: If `true`, prints the operations mongoose sends to MongoDB to the console. If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arguments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`. * - `id`: If `true`, adds a `id` virtual to all schemas unless overwritten on a per-schema basis. - * - `timestamps.createdAt.immutable`: `true` by default. If `false`, it will change the `createdAt` field to be [`immutable: false`](https://mongoosejs.com/docs/api/schematype.html#schematype_SchemaType-immutable) which means you can update the `createdAt` + * - `timestamps.createdAt.immutable`: `true` by default. If `false`, it will change the `createdAt` field to be [`immutable: false`](https://mongoosejs.com/docs/api/schematype.html#SchemaType.prototype.immutable) which means you can update the `createdAt` * - `maxTimeMS`: If set, attaches [maxTimeMS](https://www.mongodb.com/docs/manual/reference/operator/meta/maxTimeMS/) to every query * - `objectIdGetter`: `true` by default. Mongoose adds a getter to MongoDB ObjectId's called `_id` that returns `this` for convenience with populate. Set this to false to remove the getter. * - `overwriteModels`: Set to `true` to default to overwriting models with the same name when calling `mongoose.model()`, as opposed to throwing an `OverwriteModelError`. - * - `returnOriginal`: If `false`, changes the default `returnOriginal` option to `findOneAndUpdate()`, `findByIdAndUpdate`, and `findOneAndReplace()` to false. This is equivalent to setting the `new` option to `true` for `findOneAndX()` calls by default. Read our [`findOneAndUpdate()` tutorial](/docs/tutorials/findoneandupdate.html) for more information. - * - `runValidators`: `false` by default. Set to true to enable [update validators](/docs/validation.html#update-validators) for all validators by default. - * - `sanitizeFilter`: `false` by default. Set to true to enable the [sanitization of the query filters](/docs/api/mongoose.html#mongoose_Mongoose-sanitizeFilter) against query selector injection attacks by wrapping any nested objects that have a property whose name starts with `$` in a `$eq`. + * - `returnOriginal`: If `false`, changes the default `returnOriginal` option to `findOneAndUpdate()`, `findByIdAndUpdate`, and `findOneAndReplace()` to false. This is equivalent to setting the `new` option to `true` for `findOneAndX()` calls by default. Read our [`findOneAndUpdate()` tutorial](https://mongoosejs.com/docs/tutorials/findoneandupdate.html) for more information. + * - `runValidators`: `false` by default. Set to true to enable [update validators](https://mongoosejs.com/docs/validation.html#update-validators) for all validators by default. + * - `sanitizeFilter`: `false` by default. Set to true to enable the [sanitization of the query filters](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.sanitizeFilter()) against query selector injection attacks by wrapping any nested objects that have a property whose name starts with `$` in a `$eq`. * - `selectPopulatedPaths`: `true` by default. Set to false to opt out of Mongoose adding all fields that you `populate()` to your `select()`. The schema-level option `selectPopulatedPaths` overwrites this one. * - `strict`: `true` by default, may be `false`, `true`, or `'throw'`. Sets the default strict mode for schemas. - * - `strictQuery`: `false` by default. May be `false`, `true`, or `'throw'`. Sets the default [strictQuery](/docs/guide.html#strictQuery) mode for schemas. - * - `toJSON`: `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](/docs/api/document.html#document_Document-toJSON), for determining how Mongoose documents get serialized by `JSON.stringify()` - * - `toObject`: `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](/docs/api/document.html#document_Document-toObject) + * - `strictQuery`: `false` by default. May be `false`, `true`, or `'throw'`. Sets the default [strictQuery](https://mongoosejs.com/docs/guide.html#strictQuery) mode for schemas. + * - `toJSON`: `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.toJSON()), for determining how Mongoose documents get serialized by `JSON.stringify()` + * - `toObject`: `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.toObject()) * * @param {String|Object} key The name of the option or a object of multiple key-value pairs * @param {String|Function|Boolean} value The value of the option, unused if "key" is a object @@ -390,7 +390,7 @@ Mongoose.prototype.createConnection = function(uri, options) { * @param {Number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0`, `4`, or `6`. `4` means use IPv4 only, `6` means use IPv6 only, `0` means try both. * @param {Boolean} [options.autoCreate=false] Set to `true` to make Mongoose automatically call `createCollection()` on every model created on this connection. * @param {Function} [callback] - * @see Mongoose#createConnection /docs/api/mongoose.html#mongoose_Mongoose-createConnection + * @see Mongoose#createConnection https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.createConnection() * @api public * @return {Promise} resolves to `this` if connection succeeded */ @@ -434,7 +434,7 @@ Mongoose.prototype.disconnect = async function disconnect() { * * Calling `mongoose.startSession()` is equivalent to calling `mongoose.connection.startSession()`. * Sessions are scoped to a connection, so calling `mongoose.startSession()` - * starts a session on the [default mongoose connection](/docs/api/mongoose.html#mongoose_Mongoose-connection). + * starts a session on the [default mongoose connection](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.connection). * * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/MongoClient.html#startSession) * @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency @@ -708,7 +708,7 @@ Mongoose.prototype._applyPlugins = function(schema, options) { * @param {Function} fn plugin callback * @param {Object} [opts] optional options * @return {Mongoose} this - * @see plugins /docs/plugins.html + * @see plugins https://mongoosejs.com/docs/plugins.html * @api public */ @@ -720,7 +720,7 @@ Mongoose.prototype.plugin = function(fn, opts) { }; /** - * The Mongoose module's default connection. Equivalent to `mongoose.connections[0]`, see [`connections`](#mongoose_Mongoose-connections). + * The Mongoose module's default connection. Equivalent to `mongoose.connections[0]`, see [`connections`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.connections). * * #### Example: * @@ -728,9 +728,9 @@ Mongoose.prototype.plugin = function(fn, opts) { * mongoose.connect(...); * mongoose.connection.on('error', cb); * - * This is the connection used by default for every model created using [mongoose.model](#index_Mongoose-model). + * This is the connection used by default for every model created using [mongoose.model](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.model()). * - * To create a new connection, use [`createConnection()`](#mongoose_Mongoose-createConnection). + * To create a new connection, use [`createConnection()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.createConnection()). * * @memberOf Mongoose * @instance @@ -752,7 +752,7 @@ Mongoose.prototype.__defineSetter__('connection', function(v) { /** * An array containing all [connections](connection.html) associated with this * Mongoose instance. By default, there is 1 connection. Calling - * [`createConnection()`](#mongoose_Mongoose-createConnection) adds a connection + * [`createConnection()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.createConnection()) adds a connection * to this array. * * #### Example: @@ -774,7 +774,7 @@ Mongoose.prototype.connections; /** * An integer containing the value of the next connection id. Calling - * [`createConnection()`](#mongoose_Mongoose-createConnection) increments + * [`createConnection()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.createConnection()) increments * this value. * * #### Example: @@ -821,7 +821,7 @@ Object.defineProperty(Mongoose.prototype, 'Collection', { }); /** - * The Mongoose [Connection](#connection_Connection) constructor + * The Mongoose [Connection](https://mongoosejs.com/docs/api/connection.html#Connection()) constructor * * @memberOf Mongoose * @instance @@ -872,7 +872,7 @@ Mongoose.prototype.version = pkg.version; Mongoose.prototype.Mongoose = Mongoose; /** - * The Mongoose [Schema](#schema_Schema) constructor + * The Mongoose [Schema](https://mongoosejs.com/docs/api/schema.html#Schema()) constructor * * #### Example: * @@ -887,7 +887,7 @@ Mongoose.prototype.Mongoose = Mongoose; Mongoose.prototype.Schema = Schema; /** - * The Mongoose [SchemaType](#schematype_SchemaType) constructor + * The Mongoose [SchemaType](https://mongoosejs.com/docs/api/schematype.html#SchemaType()) constructor * * @method SchemaType * @api public @@ -903,14 +903,14 @@ Mongoose.prototype.SchemaType = SchemaType; * _Alias of mongoose.Schema.Types for backwards compatibility._ * * @property SchemaTypes - * @see Schema.SchemaTypes /docs/schematypes.html + * @see Schema.SchemaTypes https://mongoosejs.com/docs/schematypes.html * @api public */ Mongoose.prototype.SchemaTypes = Schema.Types; /** - * The Mongoose [VirtualType](#virtualtype_VirtualType) constructor + * The Mongoose [VirtualType](https://mongoosejs.com/docs/api/virtualtype.html#VirtualType()) constructor * * @method VirtualType * @api public @@ -928,14 +928,14 @@ Mongoose.prototype.VirtualType = VirtualType; * * #### Types: * - * - [Array](/docs/schematypes.html#arrays) - * - [Buffer](/docs/schematypes.html#buffers) - * - [Embedded](/docs/schematypes.html#schemas) - * - [DocumentArray](/docs/api/documentarraypath.html) - * - [Decimal128](/docs/api/mongoose.html#mongoose_Mongoose-Decimal128) - * - [ObjectId](/docs/schematypes.html#objectids) - * - [Map](/docs/schematypes.html#maps) - * - [Subdocument](/docs/schematypes.html#schemas) + * - [Array](https://mongoosejs.com/docs/schematypes.html#arrays) + * - [Buffer](https://mongoosejs.com/docs/schematypes.html#buffers) + * - [Embedded](https://mongoosejs.com/docs/schematypes.html#schemas) + * - [DocumentArray](https://mongoosejs.com/docs/api/documentarraypath.html) + * - [Decimal128](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.Decimal128) + * - [ObjectId](https://mongoosejs.com/docs/schematypes.html#objectids) + * - [Map](https://mongoosejs.com/docs/schematypes.html#maps) + * - [Subdocument](https://mongoosejs.com/docs/schematypes.html#schemas) * * Using this exposed access to the `ObjectId` type, we can construct ids on demand. * @@ -949,7 +949,7 @@ Mongoose.prototype.VirtualType = VirtualType; Mongoose.prototype.Types = Types; /** - * The Mongoose [Query](#query_Query) constructor. + * The Mongoose [Query](https://mongoosejs.com/docs/api/query.html#Query()) constructor. * * @method Query * @api public @@ -958,7 +958,7 @@ Mongoose.prototype.Types = Types; Mongoose.prototype.Query = Query; /** - * The Mongoose [Model](#model_Model) constructor. + * The Mongoose [Model](https://mongoosejs.com/docs/api/model.html#Model()) constructor. * * @method Model * @api public @@ -967,7 +967,7 @@ Mongoose.prototype.Query = Query; Mongoose.prototype.Model = Model; /** - * The Mongoose [Document](/docs/api/document.html#Document) constructor. + * The Mongoose [Document](https://mongoosejs.com/docs/api/document.html#Document()) constructor. * * @method Document * @api public @@ -986,7 +986,7 @@ Mongoose.prototype.Document = Document; Mongoose.prototype.DocumentProvider = require('./document_provider'); /** - * The Mongoose ObjectId [SchemaType](/docs/schematypes.html). Used for + * The Mongoose ObjectId [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for * declaring paths in your schema that should be * [MongoDB ObjectIds](https://www.mongodb.com/docs/manual/reference/method/ObjectId/). * Do not use this to create a new ObjectId instance, use `mongoose.Types.ObjectId` @@ -1071,7 +1071,7 @@ Mongoose.prototype.syncIndexes = function(options) { }; /** - * The Mongoose Decimal128 [SchemaType](/docs/schematypes.html). Used for + * The Mongoose Decimal128 [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for * declaring paths in your schema that should be * [128-bit decimal floating points](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-decimal.html). * Do not use this to create a new Decimal128 instance, use `mongoose.Types.Decimal128` @@ -1088,7 +1088,7 @@ Mongoose.prototype.syncIndexes = function(options) { Mongoose.prototype.Decimal128 = SchemaTypes.Decimal128; /** - * The Mongoose Mixed [SchemaType](/docs/schematypes.html). Used for + * The Mongoose Mixed [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for * declaring paths in your schema that Mongoose's change tracking, casting, * and validation should ignore. * @@ -1103,7 +1103,7 @@ Mongoose.prototype.Decimal128 = SchemaTypes.Decimal128; Mongoose.prototype.Mixed = SchemaTypes.Mixed; /** - * The Mongoose Date [SchemaType](/docs/schematypes.html). + * The Mongoose Date [SchemaType](https://mongoosejs.com/docs/schematypes.html). * * #### Example: * @@ -1117,7 +1117,7 @@ Mongoose.prototype.Mixed = SchemaTypes.Mixed; Mongoose.prototype.Date = SchemaTypes.Date; /** - * The Mongoose Number [SchemaType](/docs/schematypes.html). Used for + * The Mongoose Number [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for * declaring paths in your schema that Mongoose should cast to numbers. * * #### Example: @@ -1133,7 +1133,7 @@ Mongoose.prototype.Date = SchemaTypes.Date; Mongoose.prototype.Number = SchemaTypes.Number; /** - * The [MongooseError](#error_MongooseError) constructor. + * The [MongooseError](https://mongoosejs.com/docs/api/error.html#Error()) constructor. * * @method Error * @api public @@ -1143,7 +1143,7 @@ Mongoose.prototype.Error = require('./error/index'); /** * Mongoose uses this function to get the current time when setting - * [timestamps](/docs/guide.html#timestamps). You may stub out this function + * [timestamps](https://mongoosejs.com/docs/guide.html#timestamps). You may stub out this function * using a tool like [Sinon](https://www.npmjs.com/package/sinon) for testing. * * @method now diff --git a/lib/model.js b/lib/model.js index 4843012b349..4aa4e79cc2f 100644 --- a/lib/model.js +++ b/lib/model.js @@ -81,12 +81,12 @@ const saveToObjectOptions = Object.assign({}, internalToObjectOptions, { /** * A Model is a class that's your primary tool for interacting with MongoDB. - * An instance of a Model is called a [Document](/docs/api/document.html#Document). + * An instance of a Model is called a [Document](https://mongoosejs.com/docs/api/document.html#Document). * * In Mongoose, the term "Model" refers to subclasses of the `mongoose.Model` * class. You should not use the `mongoose.Model` class directly. The - * [`mongoose.model()`](/docs/api/mongoose.html#mongoose_Mongoose-model) and - * [`connection.model()`](/docs/api/connection.html#connection_Connection-model) functions + * [`mongoose.model()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.model()) and + * [`connection.model()`](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.model()) functions * create subclasses of `mongoose.Model` as shown below. * * #### Example: @@ -102,7 +102,7 @@ const saveToObjectOptions = Object.assign({}, internalToObjectOptions, { * const userFromDb = await UserModel.findOne({ name: 'Foo' }); * * @param {Object} doc values for initial set - * @param {Object} [fields] optional object containing the fields that were selected in the query which returned this document. You do **not** need to set this parameter to ensure Mongoose handles your [query projection](/docs/api/query.html#query_Query-select). + * @param {Object} [fields] optional object containing the fields that were selected in the query which returned this document. You do **not** need to set this parameter to ensure Mongoose handles your [query projection](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()). * @param {Boolean} [skipId=false] optional boolean. If true, mongoose doesn't add an `_id` field to the document. * @inherits Document https://mongoosejs.com/docs/api/document.html * @event `error`: If listening to this event, 'error' is emitted when a document was saved and an `error` occurred. If not listening, the event bubbles to the connection used to create this Model. @@ -324,7 +324,6 @@ Model.prototype.$__handleSave = function(options, callback) { // Make sure we don't treat it as a new object on error, // since it already exists this.$__.inserting = false; - const delta = this.$__delta(); if (delta) { if (delta instanceof MongooseError) { @@ -361,7 +360,7 @@ Model.prototype.$__handleSave = function(options, callback) { where[key] = val; } } - this.constructor.exists(where, optionsWithCustomValues) + this.constructor.collection.findOne(where, optionsWithCustomValues) .then(documentExists => { const matchedCount = !documentExists ? 0 : 1; callback(null, { $where: where, matchedCount }); @@ -460,8 +459,8 @@ function generateVersionError(doc, modifiedPaths) { } /** - * Saves this document by inserting a new document into the database if [document.isNew](/docs/api/document.html#document_Document-isNew) is `true`, - * or sends an [updateOne](/docs/api/document.html#document_Document-updateOne) operation with just the modified paths if `isNew` is `false`. + * Saves this document by inserting a new document into the database if [document.isNew](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew) is `true`, + * or sends an [updateOne](https://mongoosejs.com/docs/api/document.html#Document.prototype.updateOne()) operation with just the modified paths if `isNew` is `false`. * * #### Example: * @@ -477,16 +476,16 @@ function generateVersionError(doc, modifiedPaths) { * newProduct === product; // true * * @param {Object} [options] options optional options - * @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](/docs/api/document.html#document_Document-$session). - * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](https://mongoosejs.com//docs/guide.html#safe). Use the `w` option instead. + * @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](https://mongoosejs.com/docs/api/document.html#Document.prototype.session()). + * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](https://mongoosejs.com/docs/guide.html#safe). Use the `w` option instead. * @param {Boolean} [options.validateBeforeSave] set to false to save without validating. * @param {Boolean} [options.validateModifiedOnly=false] if `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths. - * @param {Number|String} [options.w] set the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#w-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern) - * @param {Boolean} [options.j] set to true for MongoDB to wait until this `save()` has been [journaled before resolving the returned promise](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern) - * @param {Number} [options.wtimeout] sets a [timeout for the write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern). - * @param {Boolean} [options.checkKeys=true] the MongoDB driver prevents you from saving keys that start with '$' or contain '.' by default. Set this option to `false` to skip that check. See [restrictions on field names](https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names) - * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](./guide.html#timestamps) are enabled, skip timestamps for this `save()`. - * @throws {DocumentNotFoundError} if this [save updates an existing document](api/document.html#document_Document-isNew) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating). + * @param {Number|String} [options.w] set the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#w-option). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern) + * @param {Boolean} [options.j] set to true for MongoDB to wait until this `save()` has been [journaled before resolving the returned promise](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern) + * @param {Number} [options.wtimeout] sets a [timeout for the write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern). + * @param {Boolean} [options.checkKeys=true] the MongoDB driver prevents you from saving keys that start with '$' or contain '.' by default. Set this option to `false` to skip that check. See [restrictions on field names](https://docs.mongodb.com/manual/reference/limits/#mongodb-limit-Restrictions-on-Field-Names) + * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this `save()`. + * @throws {DocumentNotFoundError} if this [save updates an existing document](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating). * @return {Promise} * @api public * @see middleware https://mongoosejs.com/docs/middleware.html @@ -1103,7 +1102,7 @@ Model.prototype.$model = function $model(name) { * - `findOne()` * * @param {Object} filter - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @return {Query} */ @@ -1250,8 +1249,8 @@ for (const i in EventEmitter.prototype) { * unless [`autoIndex`](https://mongoosejs.com/docs/guide.html#autoIndex) is turned off. * * Mongoose calls this function automatically when a model is created using - * [`mongoose.model()`](/docs/api/mongoose.html#mongoose_Mongoose-model) or - * [`connection.model()`](/docs/api/connection.html#connection_Connection-model), so you + * [`mongoose.model()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.model()) or + * [`connection.model()`](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.model()), so you * don't need to call `init()` to trigger index builds. * * However, you _may_ need to call `init()` to get back a promise that will resolve when your indexes are finished. @@ -1312,7 +1311,7 @@ Model.init = function init() { if (!autoCreate) { return; } - return await this.createCollection({}); + return await this.createCollection(); }; this.$init = _createCollection().then(() => _ensureIndexes()); @@ -1395,6 +1394,14 @@ Model.createCollection = async function createCollection(options) { } } + const clusteredIndex = this && + this.schema && + this.schema.options && + this.schema.options.clusteredIndex; + if (clusteredIndex != null) { + options = Object.assign({ clusteredIndex: { ...clusteredIndex, unique: true } }, options); + } + try { await this.db.createCollection(this.$__collection.collectionName, options); } catch (err) { @@ -1581,7 +1588,7 @@ async function _dropIndexes(toDrop, collection) { /** * Lists the indexes currently defined in MongoDB. This may or may not be * the same as the indexes defined in your schema depending on whether you - * use the [`autoIndex` option](/docs/guide.html#autoIndex) and if you + * use the [`autoIndex` option](https://mongoosejs.com/docs/guide.html#autoIndex) and if you * build indexes manually. * * @return {Promise} @@ -1950,10 +1957,10 @@ Model.translateAliases = function translateAliases(fields) { * #### Note: * * This function triggers `deleteOne` query hooks. Read the - * [middleware docs](/docs/middleware.html#naming) to learn more. + * [middleware docs](https://mongoosejs.com/docs/middleware.html#naming) to learn more. * * @param {Object} conditions - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @return {Query} * @api public */ @@ -1984,10 +1991,10 @@ Model.deleteOne = function deleteOne(conditions, options) { * #### Note: * * This function triggers `deleteMany` query hooks. Read the - * [middleware docs](/docs/middleware.html#naming) to learn more. + * [middleware docs](https://mongoosejs.com/docs/middleware.html#naming) to learn more. * * @param {Object} conditions - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @return {Query} * @api public */ @@ -2009,7 +2016,7 @@ Model.deleteMany = function deleteMany(conditions, options) { * Finds documents. * * Mongoose casts the `filter` to match the model's schema before the command is sent. - * See our [query casting tutorial](/docs/tutorials/query_casting.html) for + * See our [query casting tutorial](https://mongoosejs.com/docs/tutorials/query_casting.html) for * more information on how Mongoose casts `filter`. * * #### Example: @@ -2027,11 +2034,11 @@ Model.deleteMany = function deleteMany(conditions, options) { * await MyModel.find({ name: /john/i }, null, { skip: 10 }).exec(); * * @param {Object|ObjectId} filter - * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#query_Query-select) - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) + * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @return {Query} - * @see field selection #query_Query-select - * @see query casting /docs/tutorials/query_casting.html + * @see field selection https://mongoosejs.com/docs/api/query.html#Query.prototype.select() + * @see query casting https://mongoosejs.com/docs/tutorials/query_casting.html * @api public */ @@ -2073,11 +2080,11 @@ Model.find = function find(conditions, projection, options) { * await Adventure.findById(id, 'name length').exec(); * * @param {Any} id value of `_id` to query by - * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](#query_Query-select) - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) + * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @return {Query} - * @see field selection #query_Query-select - * @see lean queries /docs/tutorials/lean.html + * @see field selection https://mongoosejs.com/docs/api/query.html#Query.prototype.select() + * @see lean queries https://mongoosejs.com/docs/tutorials/lean.html * @see findById in Mongoose https://masteringjs.io/tutorials/mongoose/find-by-id * @api public */ @@ -2115,11 +2122,11 @@ Model.findById = function findById(id, projection, options) { * await Adventure.findOne({ country: 'Croatia' }, 'name length').exec(); * * @param {Object} [conditions] - * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](#query_Query-select) - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) + * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @return {Query} - * @see field selection #query_Query-select - * @see lean queries /docs/tutorials/lean.html + * @see field selection https://mongoosejs.com/docs/api/query.html#Query.prototype.select() + * @see lean queries https://mongoosejs.com/docs/tutorials/lean.html * @api public */ @@ -2169,7 +2176,7 @@ Model.estimatedDocumentCount = function estimatedDocumentCount(options) { * }); * * If you want to count all documents in a large collection, - * use the [`estimatedDocumentCount()` function](#model_Model-estimatedDocumentCount) + * use the [`estimatedDocumentCount()` function](https://mongoosejs.com/docs/api/model.html#Model.estimatedDocumentCount()) * instead. If you call `countDocuments({})`, MongoDB will always execute * a full collection scan and **not** use any indexes. * @@ -2205,8 +2212,8 @@ Model.countDocuments = function countDocuments(conditions, options) { * Counts number of documents that match `filter` in a database collection. * * This method is deprecated. If you want to count the number of documents in - * a collection, e.g. `count({})`, use the [`estimatedDocumentCount()` function](#model_Model-estimatedDocumentCount) - * instead. Otherwise, use the [`countDocuments()`](#model_Model-countDocuments) function instead. + * a collection, e.g. `count({})`, use the [`estimatedDocumentCount()` function](https://mongoosejs.com/docs/api/model.html#Model.estimatedDocumentCount()) + * instead. Otherwise, use the [`countDocuments()`](https://mongoosejs.com/docs/api/model.html#Model.countDocuments()) function instead. * * #### Example: * @@ -2298,7 +2305,7 @@ Model.where = function where(path, val) { * @method $where * @memberOf Model * @return {Query} - * @see Query.$where #query_Query-%24where + * @see Query.$where https://mongoosejs.com/docs/api/query.html#Query.prototype.$where * @api public */ @@ -2346,24 +2353,24 @@ Model.$where = function $where() { * * @param {Object} [conditions] * @param {Object} [update] - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied. - * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api/query.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html). - * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and [the Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html). + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) - * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. - * @param {Boolean} [options.overwrite=false] By default, if you don't include any [update operators](https://www.mongodb.com/docs/manual/reference/operator/update/) in `update`, Mongoose will wrap `update` in `$set` for you. This prevents you from accidentally overwriting the document. This option tells Mongoose to skip adding `$set`. An alternative to this would be using [Model.findOneAndReplace(conditions, update, options, callback)](#model_Model-findOneAndReplace). + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. + * @param {Boolean} [options.overwrite=false] By default, if you don't include any [update operators](https://www.mongodb.com/docs/manual/reference/operator/update/) in `update`, Mongoose will wrap `update` in `$set` for you. This prevents you from accidentally overwriting the document. This option tells Mongoose to skip adding `$set`. An alternative to this would be using [Model.findOneAndReplace(conditions, update, options, callback)](https://mongoosejs.com/docs/api/model.html#Model.findOneAndReplace()). * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document - * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select) + * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()) * @param {Boolean} [options.new=false] if true, return the modified document rather than the original * @param {Object|String} [options.fields] Field selection. Equivalent to `.select(fields).findOneAndUpdate()` * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0 * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update. - * @param {Boolean} [options.runValidators] if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema + * @param {Boolean} [options.runValidators] if true, runs [update validators](https://mongoosejs.com/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema * @param {Boolean} [options.setDefaultsOnInsert=true] If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) * @return {Query} - * @see Tutorial /docs/tutorials/findoneandupdate.html + * @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/ * @api public */ @@ -2466,22 +2473,22 @@ function _decorateUpdateWithVersionKey(update, options, versionKey) { * * @param {Object|Number|String} id value of `_id` to query by * @param {Object} [update] - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied. - * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api/query.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html). - * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and [the Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html). + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) - * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. - * @param {Boolean} [options.overwrite=false] By default, if you don't include any [update operators](https://www.mongodb.com/docs/manual/reference/operator/update/) in `update`, Mongoose will wrap `update` in `$set` for you. This prevents you from accidentally overwriting the document. This option tells Mongoose to skip adding `$set`. An alternative to this would be using [Model.findOneAndReplace({ _id: id }, update, options)](#model_Model-findOneAndReplace). + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. + * @param {Boolean} [options.overwrite=false] By default, if you don't include any [update operators](https://www.mongodb.com/docs/manual/reference/operator/update/) in `update`, Mongoose will wrap `update` in `$set` for you. This prevents you from accidentally overwriting the document. This option tells Mongoose to skip adding `$set`. An alternative to this would be using [Model.findOneAndReplace({ _id: id }, update, options)](https://mongoosejs.com/docs/api/model.html#Model.findOneAndReplace()). * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update. - * @param {Boolean} [options.runValidators] if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema + * @param {Boolean} [options.runValidators] if true, runs [update validators](https://mongoosejs.com/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema * @param {Boolean} [options.setDefaultsOnInsert=true] If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document * @param {Boolean} [options.new=false] if true, return the modified document rather than the original * @param {Object|String} [options.select] sets the document fields to return. * @return {Query} - * @see Model.findOneAndUpdate #model_Model-findOneAndUpdate + * @see Model.findOneAndUpdate https://mongoosejs.com/docs/api/model.html#Model.findOneAndUpdate() * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/ * @api public */ @@ -2532,10 +2539,10 @@ Model.findByIdAndUpdate = function(id, update, options) { * await doc.save(); * * @param {Object} conditions - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) - * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select) - * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()) + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update. * @param {Object|String} [options.select] sets the document fields to return. @@ -2573,10 +2580,10 @@ Model.findOneAndDelete = function(conditions, options) { * - `findOneAndDelete()` * * @param {Object|Number|String} id value of `_id` to query by - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @return {Query} - * @see Model.findOneAndRemove #model_Model-findOneAndRemove + * @see Model.findOneAndRemove https://mongoosejs.com/docs/api/model.html#Model.findOneAndRemove() * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/ */ @@ -2607,13 +2614,13 @@ Model.findByIdAndDelete = function(id, options) { * * @param {Object} filter Replace the first document that matches this filter * @param {Object} [replacement] Replace with this document - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied. - * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api/query.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html). - * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and [the Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html). + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) - * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. - * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select) + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. + * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()) * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update. * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) * @param {Object|String} [options.select] sets the document fields to return. @@ -2667,10 +2674,10 @@ Model.findOneAndReplace = function(filter, replacement, options) { * await doc.save(); * * @param {Object} conditions - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) - * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) - * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select) + * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()) * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update. * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) * @param {Object|String} [options.select] sets the document fields to return. @@ -2715,15 +2722,15 @@ Model.findOneAndRemove = function(conditions, options) { * A.findByIdAndRemove() // returns Query * * @param {Object|Number|String} id value of `_id` to query by - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) - * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). - * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select) + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). + * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()) * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update. * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) * @param {Object|String} [options.select] sets the document fields to return. * @return {Query} - * @see Model.findOneAndRemove #model_Model-findOneAndRemove + * @see Model.findOneAndRemove https://mongoosejs.com/docs/api/model.html#Model.findOneAndRemove() * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/ */ @@ -2760,7 +2767,7 @@ Model.findByIdAndRemove = function(id, options) { * await Character.create([{ name: 'Jean-Luc Picard' }], { session }); * * @param {Array|Object} docs Documents to insert, as a spread or array - * @param {Object} [options] Options passed down to `save()`. To specify `options`, `docs` **must** be an array, not a spread. See [Model.save](#model_Model-save) for available options. + * @param {Object} [options] Options passed down to `save()`. To specify `options`, `docs` **must** be an array, not a spread. See [Model.save](https://mongoosejs.com/docs/api/model.html#Model.prototype.save()) for available options. * @return {Promise} * @api public */ @@ -2801,7 +2808,7 @@ Model.create = async function create(doc, options) { // to use a spread to specify options, see gh-7535 utils.warn('WARNING: to pass a `session` to `Model.create()` in ' + 'Mongoose, you **must** pass an array as the first argument. See: ' + - 'https://mongoosejs.com/docs/api/model.html#model_Model-create'); + 'https://mongoosejs.com/docs/api/model.html#Model.create()'); } } @@ -3233,7 +3240,7 @@ function _setIsNew(doc, val) { * * This function does **not** trigger any middleware, neither `save()`, nor `update()`. * If you need to trigger - * `save()` middleware for every document use [`create()`](#model_Model-create) instead. + * `save()` middleware for every document use [`create()`](https://mongoosejs.com/docs/api/model.html#Model.create()) instead. * * #### Example: * @@ -3295,13 +3302,13 @@ function _setIsNew(doc, val) { * @param {Boolean} [ops.replaceOne.upsert=false] If true, insert a doc if no documents match `filter` * @param {Object} [options] * @param {Boolean} [options.ordered=true] If true, execute writes in order and stop at the first error. If false, execute writes in parallel and continue until all writes have either succeeded or errored. - * @param {ClientSession} [options.session=null] The session associated with this bulk write. See [transactions docs](/docs/transactions.html). - * @param {String|number} [options.w=1] The [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/). See [`Query#w()`](/docs/api/query.html#query_Query-w) for more information. + * @param {ClientSession} [options.session=null] The session associated with this bulk write. See [transactions docs](https://mongoosejs.com/docs/transactions.html). + * @param {String|number} [options.w=1] The [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/). See [`Query#w()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.w()) for more information. * @param {number} [options.wtimeout=null] The [write concern timeout](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout). * @param {Boolean} [options.j=true] If false, disable [journal acknowledgement](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option) * @param {Boolean} [options.skipValidation=false] Set to true to skip Mongoose schema validation on bulk write operations. Mongoose currently runs validation on `insertOne` and `replaceOne` operations by default. * @param {Boolean} [options.bypassDocumentValidation=false] If true, disable [MongoDB server-side schema validation](https://www.mongodb.com/docs/manual/core/schema-validation/) for all writes in this bulk. - * @param {Boolean} [options.strict=null] Overwrites the [`strict` option](/docs/guide.html#strict) on schema. If false, allows filtering and writing fields not defined in the schema for all writes in this bulk. + * @param {Boolean} [options.strict=null] Overwrites the [`strict` option](https://mongoosejs.com/docs/guide.html#strict) on schema. If false, allows filtering and writing fields not defined in the schema for all writes in this bulk. * @return {Promise} resolves to a [`BulkWriteOpResult`](https://mongodb.github.io/node-mongodb-native/4.9/classes/BulkWriteResult.html) if the operation succeeds * @api public */ @@ -3314,25 +3321,78 @@ Model.bulkWrite = async function bulkWrite(ops, options) { throw new MongooseError('Model.bulkWrite() no longer accepts a callback'); } options = options || {}; + const ordered = options.ordered == null ? true : options.ordered; const validations = ops.map(op => castBulkWrite(this, op, options)); return new Promise((resolve, reject) => { - each(validations, (fn, cb) => fn(cb), error => { - if (error) { - return reject(error); - } + if (ordered) { + each(validations, (fn, cb) => fn(cb), error => { + if (error) { + return reject(error); + } - if (ops.length === 0) { - return resolve(getDefaultBulkwriteResult()); - } + if (ops.length === 0) { + return resolve(getDefaultBulkwriteResult()); + } - try { - this.$__collection.bulkWrite(ops, options).then(resolve, reject); - } catch (err) { - return reject(err); - } - }); + try { + this.$__collection.bulkWrite(ops, options, (error, res) => { + if (error) { + return reject(error); + } + + resolve(res); + }); + } catch (err) { + return reject(err); + } + }); + + return; + } + + let remaining = validations.length; + let validOps = []; + let validationErrors = []; + for (let i = 0; i < validations.length; ++i) { + validations[i]((err) => { + if (err == null) { + validOps.push(i); + } else { + validationErrors.push({ index: i, error: err }); + } + if (--remaining <= 0) { + completeUnorderedValidation.call(this); + } + }); + } + + validationErrors = validationErrors. + sort((v1, v2) => v1.index - v2.index). + map(v => v.error); + + function completeUnorderedValidation() { + validOps = validOps.sort().map(index => ops[index]); + + this.$__collection.bulkWrite(validOps, options, (error, res) => { + if (error) { + if (validationErrors.length > 0) { + error.mongoose = error.mongoose || {}; + error.mongoose.validationErrors = validationErrors; + } + + return reject(error); + } + + if (validationErrors.length > 0) { + res.mongoose = res.mongoose || {}; + res.mongoose.validationErrors = validationErrors; + } + + resolve(res); + }); + } }); }; @@ -3345,8 +3405,8 @@ Model.bulkWrite = async function bulkWrite(ops, options) { * @param {Array} documents * @param {Object} [options] options passed to the underlying `bulkWrite()` * @param {Boolean} [options.timestamps] defaults to `null`, when set to false, mongoose will not add/update timestamps to the documents. - * @param {ClientSession} [options.session=null] The session associated with this bulk write. See [transactions docs](/docs/transactions.html). - * @param {String|number} [options.w=1] The [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/). See [`Query#w()`](/docs/api/query.html#query_Query-w) for more information. + * @param {ClientSession} [options.session=null] The session associated with this bulk write. See [transactions docs](https://mongoosejs.com/docs/transactions.html). + * @param {String|number} [options.w=1] The [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/). See [`Query#w()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.w()) for more information. * @param {number} [options.wtimeout=null] The [write concern timeout](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout). * @param {Boolean} [options.j=true] If false, disable [journal acknowledgement](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option) * @@ -3674,11 +3734,11 @@ Model.hydrate = function(obj, projection, options) { * * @param {Object} filter * @param {Object|Array} update - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document - * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern) - * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. + * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern) + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. * @return {Query} * @see Query docs https://mongoosejs.com/docs/queries.html * @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output @@ -3712,11 +3772,11 @@ Model.updateMany = function updateMany(conditions, doc, options) { * * @param {Object} filter * @param {Object|Array} update - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document - * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern) - * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. + * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern) + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. * @return {Query} * @see Query docs https://mongoosejs.com/docs/queries.html * @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output @@ -3748,11 +3808,11 @@ Model.updateOne = function updateOne(conditions, doc, options) { * * @param {Object} filter * @param {Object} doc - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document - * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern) - * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. + * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern) + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. * @return {Query} * @see Query docs https://mongoosejs.com/docs/queries.html * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html @@ -3831,11 +3891,11 @@ function _update(model, op, conditions, doc, options) { * * #### More About Aggregations: * - * - [Mongoose `Aggregate`](/docs/api/aggregate.html) + * - [Mongoose `Aggregate`](https://mongoosejs.com/docs/api/aggregate.html) * - [An Introduction to Mongoose Aggregate](https://masteringjs.io/tutorials/mongoose/aggregate) * - [MongoDB Aggregation docs](https://www.mongodb.com/docs/manual/applications/aggregation/) * - * @see Aggregate #aggregate_Aggregate + * @see Aggregate https://mongoosejs.com/docs/api/aggregate.html#Aggregate() * @see MongoDB https://www.mongodb.com/docs/manual/applications/aggregation/ * @param {Array} [pipeline] aggregation pipeline as an array of objects * @param {Object} [options] aggregation options @@ -4026,9 +4086,9 @@ Model.validate = async function validate(obj, pathsToValidate, context) { * @param {Document|Array} docs Either a single document or array of documents to populate. * @param {Object|String} options Either the paths to populate or an object specifying all parameters * @param {string} [options.path=null] The path to populate. - * @param {string|PopulateOptions} [options.populate=null] Recursively populate paths in the populated documents. See [deep populate docs](/docs/populate.html#deep-populate). + * @param {string|PopulateOptions} [options.populate=null] Recursively populate paths in the populated documents. See [deep populate docs](https://mongoosejs.com/docs/populate.html#deep-populate). * @param {boolean} [options.retainNullValues=false] By default, Mongoose removes null and undefined values from populated arrays. Use this option to make `populate()` retain `null` and `undefined` array entries. - * @param {boolean} [options.getters=false] If true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](/docs/schematypes.html#schematype-options). + * @param {boolean} [options.getters=false] If true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](https://mongoosejs.com/docs/schematypes.html#schematype-options). * @param {boolean} [options.clone=false] When you do `BlogPost.find().populate('author')`, blog posts with the same author will share 1 copy of an `author` doc. Enable this option to make Mongoose clone populated docs before assigning them. * @param {Object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://www.mongodb.com/docs/manual/tutorial/query-documents/), or a function that returns a filter object. * @param {Boolean} [options.skipInvalidIds=false] By default, Mongoose throws a cast error if `localField` and `foreignField` schemas don't line up. If you enable this option, Mongoose will instead filter out any `localField` properties that cannot be casted to `foreignField`'s schema type. diff --git a/lib/options/SchemaNumberOptions.js b/lib/options/SchemaNumberOptions.js index bd91da01b19..126e4244bac 100644 --- a/lib/options/SchemaNumberOptions.js +++ b/lib/options/SchemaNumberOptions.js @@ -69,7 +69,7 @@ Object.defineProperty(SchemaNumberOptions.prototype, 'max', opts); Object.defineProperty(SchemaNumberOptions.prototype, 'enum', opts); /** - * Sets default [populate options](/docs/populate.html#query-conditions). + * Sets default [populate options](https://mongoosejs.com/docs/populate.html#query-conditions). * * #### Example: * diff --git a/lib/options/SchemaObjectIdOptions.js b/lib/options/SchemaObjectIdOptions.js index 37048e92c4d..81c7bce7fbf 100644 --- a/lib/options/SchemaObjectIdOptions.js +++ b/lib/options/SchemaObjectIdOptions.js @@ -32,7 +32,7 @@ const opts = require('./propertyOptions'); Object.defineProperty(SchemaObjectIdOptions.prototype, 'auto', opts); /** - * Sets default [populate options](/docs/populate.html#query-conditions). + * Sets default [populate options](https://mongoosejs.com/docs/populate.html#query-conditions). * * #### Example: * diff --git a/lib/options/SchemaStringOptions.js b/lib/options/SchemaStringOptions.js index 49836ef13d0..69fe174070b 100644 --- a/lib/options/SchemaStringOptions.js +++ b/lib/options/SchemaStringOptions.js @@ -120,7 +120,7 @@ Object.defineProperty(SchemaStringOptions.prototype, 'maxLength', opts); Object.defineProperty(SchemaStringOptions.prototype, 'maxlength', opts); /** - * Sets default [populate options](/docs/populate.html#query-conditions). + * Sets default [populate options](https://mongoosejs.com/docs/populate.html#query-conditions). * * @api public * @property populate diff --git a/lib/options/SchemaTypeOptions.js b/lib/options/SchemaTypeOptions.js index f2376431034..cfe3de3cd24 100644 --- a/lib/options/SchemaTypeOptions.js +++ b/lib/options/SchemaTypeOptions.js @@ -161,7 +161,7 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'index', opts); /** * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose * will build a unique index on this path when the - * model is compiled. [The `unique` option is **not** a validator](/docs/validation.html#the-unique-option-is-not-a-validator). + * model is compiled. [The `unique` option is **not** a validator](https://mongoosejs.com/docs/validation.html#the-unique-option-is-not-a-validator). * * @api public * @property unique diff --git a/lib/options/VirtualOptions.js b/lib/options/VirtualOptions.js index 3db53b99d27..c17c5a405ff 100644 --- a/lib/options/VirtualOptions.js +++ b/lib/options/VirtualOptions.js @@ -146,7 +146,7 @@ Object.defineProperty(VirtualOptions.prototype, 'skip', opts); Object.defineProperty(VirtualOptions.prototype, 'limit', opts); /** - * The `limit` option for `populate()` has [some unfortunate edge cases](/docs/populate.html#query-conditions) + * The `limit` option for `populate()` has [some unfortunate edge cases](https://mongoosejs.com/docs/populate.html#query-conditions) * when working with multiple documents, like `.find().populate()`. The * `perDocumentLimit` option makes `populate()` execute a separate query * for each document returned from `find()` to ensure each document diff --git a/lib/query.js b/lib/query.js index 80c13a52c33..3a84505b374 100644 --- a/lib/query.js +++ b/lib/query.js @@ -67,7 +67,7 @@ const queryOptionMethods = new Set([ /** * Query constructor used for building queries. You do not need * to instantiate a `Query` directly. Instead use Model functions like - * [`Model.find()`](/docs/api/model.html#model_Model-find). + * [`Model.find()`](https://mongoosejs.com/docs/api/model.html#Model.find()). * * #### Example: * @@ -812,7 +812,7 @@ Query.prototype.mod = function() { * * #### Note: * - * As of Mongoose 3.7, `$geoWithin` is always used for queries. To change this behavior, see [Query.use$geoWithin](#query_Query-use%2524geoWithin). + * As of Mongoose 3.7, `$geoWithin` is always used for queries. To change this behavior, see [Query.use$geoWithin](https://mongoosejs.com/docs/api/query.html#Query.prototype.use$geoWithin). * * #### Note: * @@ -999,7 +999,7 @@ Query.prototype.projection = function(arg) { /** * Specifies which document fields to include or exclude (also known as the query "projection") * - * When using string syntax, prefixing a path with `-` will flag that path as excluded. When a path does not have the `-` prefix, it is included. Lastly, if a path is prefixed with `+`, it forces inclusion of the path, which is useful for paths excluded at the [schema level](/docs/api/schematype.html#schematype_SchemaType-select). + * When using string syntax, prefixing a path with `-` will flag that path as excluded. When a path does not have the `-` prefix, it is included. Lastly, if a path is prefixed with `+`, it forces inclusion of the path, which is useful for paths excluded at the [schema level](https://mongoosejs.com/docs/api/schematype.html#SchemaType.prototype.select()). * * A projection _must_ be either inclusive or exclusive. In other words, you must * either list the fields to include (which excludes all others), or list the fields @@ -1040,7 +1040,7 @@ Query.prototype.projection = function(arg) { * @instance * @param {Object|String|String[]} arg * @return {Query} this - * @see SchemaType /docs/api/schematype.html + * @see SchemaType https://mongoosejs.com/docs/api/schematype.html * @api public */ @@ -1227,7 +1227,7 @@ Query.prototype.toString = function toString() { /** * Sets the [MongoDB session](https://www.mongodb.com/docs/manual/reference/server-sessions/) * associated with this query. Sessions are how you mark a query as part of a - * [transaction](/docs/transactions.html). + * [transaction](https://mongoosejs.com/docs/transactions.html). * * Calling `session(null)` removes the session from this query. * @@ -1240,8 +1240,8 @@ Query.prototype.toString = function toString() { * @memberOf Query * @instance * @param {ClientSession} [session] from `await conn.startSession()` - * @see Connection.prototype.startSession() /docs/api/connection.html#connection_Connection-startSession - * @see mongoose.startSession() /docs/api/mongoose.html#mongoose_Mongoose-startSession + * @see Connection.prototype.startSession() https://mongoosejs.com/docs/api/connection.html#Connection.prototype.startSession() + * @see mongoose.startSession() https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.startSession() * @return {Query} this * @api public */ @@ -1259,7 +1259,7 @@ Query.prototype.session = function session(v) { * * - `w`: Sets the specified number of `mongod` servers, or tag set of `mongod` servers, that must acknowledge this write before this write is considered successful. * - `j`: Boolean, set to `true` to request acknowledgement that this operation has been persisted to MongoDB's on-disk journal. - * - `wtimeout`: If [`w > 1`](#query_Query-w), the maximum amount of time to wait for this write to propagate through the replica set before this operation fails. The default is `0`, which means no timeout. + * - `wtimeout`: If [`w > 1`](https://mongoosejs.com/docs/api/query.html#Query.prototype.w()), the maximum amount of time to wait for this write to propagate through the replica set before this operation fails. The default is `0`, which means no timeout. * * This option is only valid for operations that write to the database: * @@ -1271,7 +1271,7 @@ Query.prototype.session = function session(v) { * - `updateOne()` * - `updateMany()` * - * Defaults to the schema's [`writeConcern` option](/docs/guide.html#writeConcern) + * Defaults to the schema's [`writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern) * * #### Example: * @@ -1312,7 +1312,7 @@ Query.prototype.writeConcern = function writeConcern(val) { * - `updateOne()` * - `updateMany()` * - * Defaults to the schema's [`writeConcern.w` option](/docs/guide.html#writeConcern) + * Defaults to the schema's [`writeConcern.w` option](https://mongoosejs.com/docs/guide.html#writeConcern) * * #### Example: * @@ -1356,7 +1356,7 @@ Query.prototype.w = function w(val) { * - `updateOne()` * - `updateMany()` * - * Defaults to the schema's [`writeConcern.j` option](/docs/guide.html#writeConcern) + * Defaults to the schema's [`writeConcern.j` option](https://mongoosejs.com/docs/guide.html#writeConcern) * * #### Example: * @@ -1384,7 +1384,7 @@ Query.prototype.j = function j(val) { }; /** - * If [`w > 1`](#query_Query-w), the maximum amount of time to + * If [`w > 1`](https://mongoosejs.com/docs/api/query.html#Query.prototype.w()), the maximum amount of time to * wait for this write to propagate through the replica set before this * operation fails. The default is `0`, which means no timeout. * @@ -1398,7 +1398,7 @@ Query.prototype.j = function j(val) { * - `updateOne()` * - `updateMany()` * - * Defaults to the schema's [`writeConcern.wtimeout` option](/docs/guide.html#writeConcern) + * Defaults to the schema's [`writeConcern.wtimeout` option](https://mongoosejs.com/docs/guide.html#writeConcern) * * #### Example: * @@ -1526,9 +1526,9 @@ Query.prototype.getOptions = function() { * * The following options are only for `find()`, `findOne()`, `findById()`, `findOneAndUpdate()`, and `findByIdAndUpdate()`: * - * - [lean](#query_Query-lean) - * - [populate](/docs/populate.html) - * - [projection](#query_Query-projection) + * - [lean](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) + * - [populate](https://mongoosejs.com/docs/populate.html) + * - [projection](https://mongoosejs.com/docs/api/query.html#Query.prototype.projection()) * - sanitizeProjection * * The following options are only for all operations **except** `updateOne()`, `updateMany()`, `deleteOne()`, and `deleteMany()`: @@ -1541,7 +1541,7 @@ Query.prototype.getOptions = function() { * * The following options are for all operations: * - * - [strict](/docs/guide.html#strict) + * - [strict](https://mongoosejs.com/docs/guide.html#strict) * - [collation](https://www.mongodb.com/docs/manual/reference/collation/) * - [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) * - [explain](https://www.mongodb.com/docs/manual/reference/method/cursor.explain/) @@ -1967,7 +1967,7 @@ Query.prototype._optionsForExec = function(model) { * Sets the lean option. * * Documents returned from queries with the `lean` option enabled are plain - * javascript objects, not [Mongoose Documents](/docs/api/document.html). They have no + * javascript objects, not [Mongoose Documents](https://mongoosejs.com/docs/api/document.html). They have no * `save` method, getters/setters, virtuals, or other Mongoose features. * * #### Example: @@ -1979,9 +1979,9 @@ Query.prototype._optionsForExec = function(model) { * const docs = await Model.find().lean(); * docs[0] instanceof mongoose.Document; // false * - * [Lean is great for high-performance, read-only cases](/docs/tutorials/lean.html), + * [Lean is great for high-performance, read-only cases](https://mongoosejs.com/docs/tutorials/lean.html), * especially when combined - * with [cursors](/docs/queries.html#streaming). + * with [cursors](https://mongoosejs.com/docs/queries.html#streaming). * * If you need virtuals, getters/setters, or defaults with `lean()`, you need * to use a plugin. See: @@ -2129,11 +2129,11 @@ Query.prototype._unsetCastError = function _unsetCastError() { * Getter/setter around the current mongoose-specific options for this query * Below are the current Mongoose-specific options. * - * - `populate`: an array representing what paths will be populated. Should have one entry for each call to [`Query.prototype.populate()`](#query_Query-populate) - * - `lean`: if truthy, Mongoose will not [hydrate](/docs/api/model.html#model_Model-hydrate) any documents that are returned from this query. See [`Query.prototype.lean()`](#query_Query-lean) for more information. - * - `strict`: controls how Mongoose handles keys that aren't in the schema for updates. This option is `true` by default, which means Mongoose will silently strip any paths in the update that aren't in the schema. See the [`strict` mode docs](/docs/guide.html#strict) for more information. - * - `strictQuery`: controls how Mongoose handles keys that aren't in the schema for the query `filter`. This option is `false` by default, which means Mongoose will allow `Model.find({ foo: 'bar' })` even if `foo` is not in the schema. See the [`strictQuery` docs](/docs/guide.html#strictQuery) for more information. - * - `nearSphere`: use `$nearSphere` instead of `near()`. See the [`Query.prototype.nearSphere()` docs](#query_Query-nearSphere) + * - `populate`: an array representing what paths will be populated. Should have one entry for each call to [`Query.prototype.populate()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.populate()) + * - `lean`: if truthy, Mongoose will not [hydrate](https://mongoosejs.com/docs/api/model.html#Model.hydrate()) any documents that are returned from this query. See [`Query.prototype.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) for more information. + * - `strict`: controls how Mongoose handles keys that aren't in the schema for updates. This option is `true` by default, which means Mongoose will silently strip any paths in the update that aren't in the schema. See the [`strict` mode docs](https://mongoosejs.com/docs/guide.html#strict) for more information. + * - `strictQuery`: controls how Mongoose handles keys that aren't in the schema for the query `filter`. This option is `false` by default, which means Mongoose will allow `Model.find({ foo: 'bar' })` even if `foo` is not in the schema. See the [`strictQuery` docs](https://mongoosejs.com/docs/guide.html#strictQuery) for more information. + * - `nearSphere`: use `$nearSphere` instead of `near()`. See the [`Query.prototype.nearSphere()` docs](https://mongoosejs.com/docs/api/query.html#Query.prototype.nearSphere()) * * Mongoose maintains a separate object for internal options because * Mongoose sends `Query.prototype.options` to the MongoDB server, and the @@ -2268,7 +2268,7 @@ Query.prototype._find = async function _find() { * Find all documents that match `selector`. The result will be an array of documents. * * If there are too many documents in the result to fit in memory, use - * [`Query.prototype.cursor()`](#query_Query-cursor) + * [`Query.prototype.cursor()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.cursor()) * * #### Example: * @@ -2509,10 +2509,10 @@ Query.prototype._findOne = async function _findOne() { * * @param {Object} [filter] mongodb selector * @param {Object} [projection] optional fields to return - * @param {Object} [options] see [`setOptions()`](#query_Query-setOptions) + * @param {Object} [options] see [`setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @return {Query} this * @see findOne https://www.mongodb.com/docs/manual/reference/method/db.collection.findOne/ - * @see Query.select #query_Query-select + * @see Query.select https://mongoosejs.com/docs/api/query.html#Query.prototype.select() * @api public */ @@ -2626,8 +2626,8 @@ Query.prototype._estimatedDocumentCount = async function _estimatedDocumentCount * Specifies this query as a `count` query. * * This method is deprecated. If you want to count the number of documents in - * a collection, e.g. `count({})`, use the [`estimatedDocumentCount()` function](#query_Query-estimatedDocumentCount) - * instead. Otherwise, use the [`countDocuments()`](#query_Query-countDocuments) function instead. + * a collection, e.g. `count({})`, use the [`estimatedDocumentCount()` function](https://mongoosejs.com/docs/api/query.html#Query.prototype.estimatedDocumentCount()) + * instead. Otherwise, use the [`countDocuments()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.countDocuments()) function instead. * * This function triggers the following middleware. * @@ -2899,7 +2899,7 @@ Query.prototype.sort = function(arg) { * res.deletedCount; * * @param {Object|Query} [filter] mongodb selector - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](#query_Query-setOptions) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @return {Query} this * @see DeleteResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/DeleteResult.html * @see deleteOne https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#deleteOne @@ -2975,7 +2975,7 @@ Query.prototype._deleteOne = async function _deleteOne() { * res.deletedCount; * * @param {Object|Query} [filter] mongodb selector - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](#query_Query-setOptions) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @return {Query} this * @see DeleteResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/DeleteResult.html * @see deleteMany https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#deleteMany @@ -3106,7 +3106,7 @@ function prepareDiscriminatorCriteria(query) { * - `fields`: {Object|String} - Field selection. Equivalent to `.select(fields).findOneAndUpdate()` * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0 - * - `runValidators`: if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema. + * - `runValidators`: if true, runs [update validators](https://mongoosejs.com/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema. * - `setDefaultsOnInsert`: `true` by default. If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created. * - `rawResult`: if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) * @@ -3125,15 +3125,15 @@ function prepareDiscriminatorCriteria(query) { * @param {Object} [options] * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) - * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). * @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors. * @param {Boolean} [options.new=false] By default, `findOneAndUpdate()` returns the document as it was **before** `update` was applied. If you set `new: true`, `findOneAndUpdate()` will instead give you the object after `update` was applied. - * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html). - * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and [the Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html). + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) - * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. * @param {Boolean} [options.returnOriginal=null] An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`. - * @see Tutorial /docs/tutorials/findoneandupdate.html + * @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html * @see findAndModify command https://www.mongodb.com/docs/manual/reference/command/findAndModify/ * @see ModifyResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html * @see findOneAndUpdate https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#findOneAndUpdate @@ -3208,6 +3208,8 @@ Query.prototype.findOneAndUpdate = function(criteria, doc, options) { Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() { this._castConditions(); + _castArrayFilters(this); + if (this.error()) { throw this.error(); } @@ -3307,7 +3309,7 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() { * @param {Object} [conditions] * @param {Object} [options] * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) - * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @return {Query} this * @see findAndModify command https://www.mongodb.com/docs/manual/reference/command/findAndModify/ @@ -3367,7 +3369,7 @@ Query.prototype.findOneAndRemove = function(conditions, options) { * @param {Object} [conditions] * @param {Object} [options] * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) - * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @return {Query} this * @see findAndModify command https://www.mongodb.com/docs/manual/reference/command/findAndModify/ @@ -3472,13 +3474,13 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() { * @param {Object} [replacement] * @param {Object} [options] * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) - * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @param {Boolean} [options.new=false] By default, `findOneAndUpdate()` returns the document as it was **before** `update` was applied. If you set `new: true`, `findOneAndUpdate()` will instead give you the object after `update` was applied. - * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html). - * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and [the Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html). + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) - * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. * @param {Boolean} [options.returnOriginal=null] An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`. * @return {Query} this * @api public @@ -3856,7 +3858,7 @@ Query.prototype.validate = async function validate(castedDoc, options, isOverwri /** * Execute an updateMany query * - * @see Model.update #model_Model-update + * @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update() * @method _updateMany * @memberOf Query * @instance @@ -3869,7 +3871,7 @@ Query.prototype._updateMany = async function _updateMany() { /** * Execute an updateOne query * - * @see Model.update #model_Model-update + * @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update() * @method _updateOne * @memberOf Query * @instance @@ -3882,7 +3884,7 @@ Query.prototype._updateOne = async function _updateOne() { /** * Execute a replaceOne query * - * @see Model.replaceOne #model_Model-replaceOne + * @see Model.replaceOne https://mongoosejs.com/docs/api/model.html#Model.replaceOne() * @method _replaceOne * @memberOf Query * @instance @@ -3915,11 +3917,11 @@ Query.prototype._replaceOne = async function _replaceOne() { * @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors. * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document - * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern) - * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. + * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern) + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. * @param {Function} [callback] params are (error, writeOpResult) * @return {Query} this - * @see Model.update #model_Model-update + * @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update() * @see Query docs https://mongoosejs.com/docs/queries.html * @see update https://www.mongodb.com/docs/manual/reference/method/db.collection.update/ * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html @@ -3983,11 +3985,11 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) { * @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors. * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document - * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern) - * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. + * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern) + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. * @param {Function} [callback] params are (error, writeOpResult) * @return {Query} this - * @see Model.update #model_Model-update + * @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update() * @see Query docs https://mongoosejs.com/docs/queries.html * @see update https://www.mongodb.com/docs/manual/reference/method/db.collection.update/ * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html @@ -4049,11 +4051,11 @@ Query.prototype.updateOne = function(conditions, doc, options, callback) { * @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors. * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document - * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern) - * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. + * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern) + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. * @param {Function} [callback] params are (error, writeOpResult) * @return {Query} this - * @see Model.update #model_Model-update + * @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update() * @see Query docs https://mongoosejs.com/docs/queries.html * @see update https://www.mongodb.com/docs/manual/reference/method/db.collection.update/ * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html @@ -4294,6 +4296,13 @@ Query.prototype.exec = async function exec(op) { return; } + if (this.options && this.options.sort) { + const keys = Object.keys(this.options.sort); + if (keys.includes('')) { + throw new Error('Invalid field "" passed to sort()'); + } + } + let thunk = '_' + this.op; if (this.op === 'distinct') { thunk = '__distinct'; @@ -4491,7 +4500,7 @@ Query.prototype[Symbol.toStringTag] = function toString() { }; /** - * Add pre [middleware](/docs/middleware.html) to this query instance. Doesn't affect + * Add pre [middleware](https://mongoosejs.com/docs/middleware.html) to this query instance. Doesn't affect * other queries. * * #### Example: @@ -4517,7 +4526,7 @@ Query.prototype.pre = function(fn) { }; /** - * Add post [middleware](/docs/middleware.html) to this query instance. Doesn't affect + * Add post [middleware](https://mongoosejs.com/docs/middleware.html) to this query instance. Doesn't affect * other queries. * * #### Example: @@ -4636,14 +4645,14 @@ Query.prototype._castUpdate = function _castUpdate(obj, overwrite) { * @param {Object} [options] Options for the population query (sort, etc) * @param {String} [options.path=null] The path to populate. * @param {boolean} [options.retainNullValues=false] by default, Mongoose removes null and undefined values from populated arrays. Use this option to make `populate()` retain `null` and `undefined` array entries. - * @param {boolean} [options.getters=false] if true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](/docs/schematypes.html#schematype-options). + * @param {boolean} [options.getters=false] if true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](https://mongoosejs.com/docs/schematypes.html#schematype-options). * @param {boolean} [options.clone=false] When you do `BlogPost.find().populate('author')`, blog posts with the same author will share 1 copy of an `author` doc. Enable this option to make Mongoose clone populated docs before assigning them. * @param {Object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://www.mongodb.com/docs/manual/tutorial/query-documents/), or a function that returns a filter object. * @param {Function} [options.transform=null] Function that Mongoose will call on every populated document that allows you to transform the populated document. * @param {Object} [options.options=null] Additional options like `limit` and `lean`. - * @see population /docs/populate.html - * @see Query#select #query_Query-select - * @see Model.populate #model_Model-populate + * @see population https://mongoosejs.com/docs/populate.html + * @see Query#select https://mongoosejs.com/docs/api/query.html#Query.prototype.select() + * @see Model.populate https://mongoosejs.com/docs/api/model.html#Model.populate() * @return {Query} this * @api public */ @@ -4911,7 +4920,7 @@ Query.prototype._applyPaths = function applyPaths() { * * @return {QueryCursor} * @param {Object} [options] - * @see QueryCursor /docs/api/querycursor.html + * @see QueryCursor https://mongoosejs.com/docs/api/querycursor.html * @api public */ @@ -5162,7 +5171,7 @@ Query.prototype.near = function() { * query.where('loc').near({ center: [10, 10], spherical: true }); * * @deprecated - * @see near() #query_Query-near + * @see near() https://mongoosejs.com/docs/api/query.html#Query.prototype.near() * @see $near https://www.mongodb.com/docs/manual/reference/operator/near/ * @see $nearSphere https://www.mongodb.com/docs/manual/reference/operator/nearSphere/ * @see $maxDistance https://www.mongodb.com/docs/manual/reference/operator/maxDistance/ @@ -5217,7 +5226,7 @@ if (Symbol.asyncIterator != null) { * @memberOf Query * @instance * @param {String|Array} [path] - * @param {Array|Object} [coordinatePairs...] + * @param {...Array|Object} [coordinatePairs] * @return {Query} this * @see $polygon https://www.mongodb.com/docs/manual/reference/operator/polygon/ * @see MongoDB Geospatial Indexing https://www.mongodb.com/docs/manual/core/geospatial-indexes/ @@ -5239,7 +5248,7 @@ if (Symbol.asyncIterator != null) { * @memberOf Query * @instance * @see $box https://www.mongodb.com/docs/manual/reference/operator/box/ - * @see within() Query#within #query_Query-within + * @see within() Query#within https://mongoosejs.com/docs/api/query.html#Query.prototype.within() * @see MongoDB Geospatial Indexing https://www.mongodb.com/docs/manual/core/geospatial-indexes/ * @param {Object|Array} val1 Lower Left Coordinates OR a object of lower-left(ll) and upper-right(ur) Coordinates * @param {Array} [val2] Upper Right Coordinates @@ -5296,9 +5305,9 @@ Query.prototype.box = function(ll, ur) { */ /** - * _DEPRECATED_ Alias for [circle](#query_Query-circle) + * _DEPRECATED_ Alias for [circle](https://mongoosejs.com/docs/api/query.html#Query.prototype.circle()) * - * **Deprecated.** Use [circle](#query_Query-circle) instead. + * **Deprecated.** Use [circle](https://mongoosejs.com/docs/api/query.html#Query.prototype.circle()) instead. * * @deprecated * @method center @@ -5312,7 +5321,7 @@ Query.prototype.center = Query.base.circle; /** * _DEPRECATED_ Specifies a `$centerSphere` condition * - * **Deprecated.** Use [circle](#query_Query-circle) instead. + * **Deprecated.** Use [circle](https://mongoosejs.com/docs/api/query.html#Query.prototype.circle()) instead. * * #### Example: * diff --git a/lib/schema.js b/lib/schema.js index c501792c529..7b5ef0ece89 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -51,33 +51,33 @@ let id = 0; * * #### Options: * - * - [autoIndex](/docs/guide.html#autoIndex): bool - defaults to null (which means use the connection's autoIndex option) - * - [autoCreate](/docs/guide.html#autoCreate): bool - defaults to null (which means use the connection's autoCreate option) - * - [bufferCommands](/docs/guide.html#bufferCommands): bool - defaults to true - * - [bufferTimeoutMS](/docs/guide.html#bufferTimeoutMS): number - defaults to 10000 (10 seconds). If `bufferCommands` is enabled, the amount of time Mongoose will wait for connectivity to be restablished before erroring out. - * - [capped](/docs/guide.html#capped): bool | number | object - defaults to false - * - [collection](/docs/guide.html#collection): string - no default - * - [discriminatorKey](/docs/guide.html#discriminatorKey): string - defaults to `__t` - * - [id](/docs/guide.html#id): bool - defaults to true - * - [_id](/docs/guide.html#_id): bool - defaults to true - * - [minimize](/docs/guide.html#minimize): bool - controls [document#toObject](#document_Document-toObject) behavior when called manually - defaults to true - * - [read](/docs/guide.html#read): string - * - [writeConcern](/docs/guide.html#writeConcern): object - defaults to null, use to override [the MongoDB server's default write concern settings](https://www.mongodb.com/docs/manual/reference/write-concern/) - * - [shardKey](/docs/guide.html#shardKey): object - defaults to `null` - * - [strict](/docs/guide.html#strict): bool - defaults to true - * - [strictQuery](/docs/guide.html#strictQuery): bool - defaults to false - * - [toJSON](/docs/guide.html#toJSON) - object - no default - * - [toObject](/docs/guide.html#toObject) - object - no default - * - [typeKey](/docs/guide.html#typeKey) - string - defaults to 'type' - * - [validateBeforeSave](/docs/guide.html#validateBeforeSave) - bool - defaults to `true` - * - [versionKey](/docs/guide.html#versionKey): string or object - defaults to "__v" - * - [optimisticConcurrency](/docs/guide.html#optimisticConcurrency): bool - defaults to false. Set to true to enable [optimistic concurrency](https://thecodebarbarian.com/whats-new-in-mongoose-5-10-optimistic-concurrency.html). - * - [collation](/docs/guide.html#collation): object - defaults to null (which means use no collation) - * - [timeseries](/docs/guide.html#timeseries): object - defaults to null (which means this schema's collection won't be a timeseries collection) - * - [selectPopulatedPaths](/docs/guide.html#selectPopulatedPaths): boolean - defaults to `true` - * - [skipVersioning](/docs/guide.html#skipVersioning): object - paths to exclude from versioning - * - [timestamps](/docs/guide.html#timestamps): object or boolean - defaults to `false`. If true, Mongoose adds `createdAt` and `updatedAt` properties to your schema and manages those properties for you. - * - [pluginTags](/docs/guide.html#pluginTags): array of strings - defaults to `undefined`. If set and plugin called with `tags` option, will only apply that plugin to schemas with a matching tag. + * - [autoIndex](https://mongoosejs.com/docs/guide.html#autoIndex): bool - defaults to null (which means use the connection's autoIndex option) + * - [autoCreate](https://mongoosejs.com/docs/guide.html#autoCreate): bool - defaults to null (which means use the connection's autoCreate option) + * - [bufferCommands](https://mongoosejs.com/docs/guide.html#bufferCommands): bool - defaults to true + * - [bufferTimeoutMS](https://mongoosejs.com/docs/guide.html#bufferTimeoutMS): number - defaults to 10000 (10 seconds). If `bufferCommands` is enabled, the amount of time Mongoose will wait for connectivity to be restablished before erroring out. + * - [capped](https://mongoosejs.com/docs/guide.html#capped): bool | number | object - defaults to false + * - [collection](https://mongoosejs.com/docs/guide.html#collection): string - no default + * - [discriminatorKey](https://mongoosejs.com/docs/guide.html#discriminatorKey): string - defaults to `__t` + * - [id](https://mongoosejs.com/docs/guide.html#id): bool - defaults to true + * - [_id](https://mongoosejs.com/docs/guide.html#_id): bool - defaults to true + * - [minimize](https://mongoosejs.com/docs/guide.html#minimize): bool - controls [document#toObject](https://mongoosejs.com/docs/api/document.html#Document.prototype.toObject()) behavior when called manually - defaults to true + * - [read](https://mongoosejs.com/docs/guide.html#read): string + * - [writeConcern](https://mongoosejs.com/docs/guide.html#writeConcern): object - defaults to null, use to override [the MongoDB server's default write concern settings](https://www.mongodb.com/docs/manual/reference/write-concern/) + * - [shardKey](https://mongoosejs.com/docs/guide.html#shardKey): object - defaults to `null` + * - [strict](https://mongoosejs.com/docs/guide.html#strict): bool - defaults to true + * - [strictQuery](https://mongoosejs.com/docs/guide.html#strictQuery): bool - defaults to false + * - [toJSON](https://mongoosejs.com/docs/guide.html#toJSON) - object - no default + * - [toObject](https://mongoosejs.com/docs/guide.html#toObject) - object - no default + * - [typeKey](https://mongoosejs.com/docs/guide.html#typeKey) - string - defaults to 'type' + * - [validateBeforeSave](https://mongoosejs.com/docs/guide.html#validateBeforeSave) - bool - defaults to `true` + * - [versionKey](https://mongoosejs.com/docs/guide.html#versionKey): string or object - defaults to "__v" + * - [optimisticConcurrency](https://mongoosejs.com/docs/guide.html#optimisticConcurrency): bool - defaults to false. Set to true to enable [optimistic concurrency](https://thecodebarbarian.com/whats-new-in-mongoose-5-10-optimistic-concurrency.html). + * - [collation](https://mongoosejs.com/docs/guide.html#collation): object - defaults to null (which means use no collation) + * - [timeseries](https://mongoosejs.com/docs/guide.html#timeseries): object - defaults to null (which means this schema's collection won't be a timeseries collection) + * - [selectPopulatedPaths](https://mongoosejs.com/docs/guide.html#selectPopulatedPaths): boolean - defaults to `true` + * - [skipVersioning](https://mongoosejs.com/docs/guide.html#skipVersioning): object - paths to exclude from versioning + * - [timestamps](https://mongoosejs.com/docs/guide.html#timestamps): object or boolean - defaults to `false`. If true, Mongoose adds `createdAt` and `updatedAt` properties to your schema and manages those properties for you. + * - [pluginTags](https://mongoosejs.com/docs/guide.html#pluginTags): array of strings - defaults to `undefined`. If set and plugin called with `tags` option, will only apply that plugin to schemas with a matching tag. * * #### Options for Nested Schemas: * @@ -962,9 +962,9 @@ reserved.collection = 1; */ Schema.prototype.path = function(path, obj) { - // Convert to '.$' to check subpaths re: gh-6405 - const cleanPath = _pathToPositionalSyntax(path); if (obj === undefined) { + // Convert to '.$' to check subpaths re: gh-6405 + const cleanPath = _pathToPositionalSyntax(path); let schematype = _getPath(this, path, cleanPath); if (schematype != null) { return schematype; @@ -1862,7 +1862,7 @@ Schema.prototype.post = function(name) { * @param {Function} plugin The Plugin's callback * @param {Object} [opts] Options to pass to the plugin * @param {Boolean} [opts.deduplicate=false] If true, ignore duplicate plugins (same `fn` argument using `===`) - * @see plugins /docs/plugins.html + * @see plugins https://mongoosejs.com/docs/plugins.html * @api public */ @@ -1913,7 +1913,7 @@ Schema.prototype.plugin = function(fn, opts) { * fizz.purr(); * fizz.scratch(); * - * NOTE: `Schema.method()` adds instance methods to the `Schema.methods` object. You can also add instance methods directly to the `Schema.methods` object as seen in the [guide](/docs/guide.html#methods) + * NOTE: `Schema.method()` adds instance methods to the `Schema.methods` object. You can also add instance methods directly to the `Schema.methods` object as seen in the [guide](https://mongoosejs.com/docs/guide.html#methods) * * @param {String|Object} name The Method Name for a single function, or a Object of "string-function" pairs. * @param {Function} [fn] The Function in a single-function definition. @@ -1963,7 +1963,7 @@ Schema.prototype.method = function(name, fn, options) { * @param {String|Object} name The Method Name for a single function, or a Object of "string-function" pairs. * @param {Function} [fn] The Function in a single-function definition. * @api public - * @see Statics /docs/guide.html#statics + * @see Statics https://mongoosejs.com/docs/guide.html#statics */ Schema.prototype.static = function(name, fn) { @@ -2015,7 +2015,7 @@ Schema.prototype.index = function(fields, options) { * @param {String} key The name of the option to set the value to * @param {Object} [value] The value to set the option to, if not passed, the option will be reset to default * @param {Array} [tags] tags to add to read preference if key === 'read' - * @see Schema #schema_Schema + * @see Schema https://mongoosejs.com/docs/api/schema.html#Schema() * @api public */ @@ -2115,7 +2115,7 @@ Object.defineProperty(Schema, 'indexTypes', { * // [ { registeredAt: 1 }, { background: true } ] ] * userSchema.indexes(); * - * [Plugins](/docs/plugins.html) can use the return value of this function to modify a schema's indexes. + * [Plugins](https://mongoosejs.com/docs/plugins.html) can use the return value of this function to modify a schema's indexes. * For example, the below plugin makes every index unique by default. * * function myPlugin(schema) { @@ -2139,12 +2139,12 @@ Schema.prototype.indexes = function() { * * @param {String} name The name of the Virtual * @param {Object} [options] - * @param {String|Model} [options.ref] model name or model instance. Marks this as a [populate virtual](/docs/populate.html#populate-virtuals). - * @param {String|Function} [options.localField] Required for populate virtuals. See [populate virtual docs](/docs/populate.html#populate-virtuals) for more information. - * @param {String|Function} [options.foreignField] Required for populate virtuals. See [populate virtual docs](/docs/populate.html#populate-virtuals) for more information. + * @param {String|Model} [options.ref] model name or model instance. Marks this as a [populate virtual](https://mongoosejs.com/docs/populate.html#populate-virtuals). + * @param {String|Function} [options.localField] Required for populate virtuals. See [populate virtual docs](https://mongoosejs.com/docs/populate.html#populate-virtuals) for more information. + * @param {String|Function} [options.foreignField] Required for populate virtuals. See [populate virtual docs](https://mongoosejs.com/docs/populate.html#populate-virtuals) for more information. * @param {Boolean|Function} [options.justOne=false] Only works with populate virtuals. If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), will be a single doc or `null`. Otherwise, the populate virtual will be an array. * @param {Boolean} [options.count=false] Only works with populate virtuals. If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), this populate virtual will contain the number of documents rather than the documents themselves when you `populate()`. - * @param {Function|null} [options.get=null] Adds a [getter](/docs/tutorials/getters-setters.html) to this virtual to transform the populated doc. + * @param {Function|null} [options.get=null] Adds a [getter](https://mongoosejs.com/docs/tutorials/getters-setters.html) to this virtual to transform the populated doc. * @return {VirtualType} */ @@ -2357,6 +2357,11 @@ Schema.prototype.removeVirtual = function(path) { for (const virtual of path) { delete this.paths[virtual]; delete this.virtuals[virtual]; + if (virtual.indexOf('.') !== -1) { + mpath.unset(virtual, this.tree); + } else { + delete this.tree[virtual]; + } } } return this; @@ -2365,9 +2370,9 @@ Schema.prototype.removeVirtual = function(path) { /** * Loads an ES6 class into a schema. Maps [setters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) + [getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get), [static methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static), * and [instance methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Class_body_and_method_definitions) - * to schema [virtuals](/docs/guide.html#virtuals), - * [statics](/docs/guide.html#statics), and - * [methods](/docs/guide.html#methods). + * to schema [virtuals](https://mongoosejs.com/docs/guide.html#virtuals), + * [statics](https://mongoosejs.com/docs/guide.html#statics), and + * [methods](https://mongoosejs.com/docs/guide.html#methods). * * #### Example: * @@ -2652,14 +2657,14 @@ module.exports = exports = Schema; * * #### Types: * - * - [String](/docs/schematypes.html#strings) - * - [Number](/docs/schematypes.html#numbers) - * - [Boolean](/docs/schematypes.html#booleans) | Bool - * - [Array](/docs/schematypes.html#arrays) - * - [Buffer](/docs/schematypes.html#buffers) - * - [Date](/docs/schematypes.html#dates) - * - [ObjectId](/docs/schematypes.html#objectids) | Oid - * - [Mixed](/docs/schematypes.html#mixed) + * - [String](https://mongoosejs.com/docs/schematypes.html#strings) + * - [Number](https://mongoosejs.com/docs/schematypes.html#numbers) + * - [Boolean](https://mongoosejs.com/docs/schematypes.html#booleans) | Bool + * - [Array](https://mongoosejs.com/docs/schematypes.html#arrays) + * - [Buffer](https://mongoosejs.com/docs/schematypes.html#buffers) + * - [Date](https://mongoosejs.com/docs/schematypes.html#dates) + * - [ObjectId](https://mongoosejs.com/docs/schematypes.html#objectids) | Oid + * - [Mixed](https://mongoosejs.com/docs/schematypes.html#mixed) * * Using this exposed access to the `Mixed` SchemaType, we can use them in our schema. * diff --git a/lib/schema/SubdocumentPath.js b/lib/schema/SubdocumentPath.js index eb504181026..e66a1529539 100644 --- a/lib/schema/SubdocumentPath.js +++ b/lib/schema/SubdocumentPath.js @@ -296,7 +296,7 @@ SubdocumentPath.prototype.doValidateSync = function(value, scope, options) { * @param {String} [options.value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter. * @param {Boolean} [options.clone=true] By default, `discriminator()` clones the given `schema`. Set to `false` to skip cloning. * @return {Function} the constructor Mongoose will use for creating instances of this discriminator model - * @see discriminators /docs/discriminators.html + * @see discriminators https://mongoosejs.com/docs/discriminators.html * @api public */ diff --git a/lib/schema/array.js b/lib/schema/array.js index b44f610aec9..a33b4e497cb 100644 --- a/lib/schema/array.js +++ b/lib/schema/array.js @@ -452,7 +452,7 @@ SchemaArray.prototype.$toObject = SchemaArray.prototype.toObject; * ignore */ -SchemaArray.prototype.discriminator = function(name, schema) { +SchemaArray.prototype.discriminator = function(...args) { let arr = this; while (arr.$isMongooseArray && !arr.$isMongooseDocumentArray) { arr = arr.casterConstructor; @@ -461,7 +461,7 @@ SchemaArray.prototype.discriminator = function(name, schema) { 'a document array, ' + this.path + ' is a plain array'); } } - return arr.discriminator(name, schema); + return arr.discriminator(...args); }; /*! diff --git a/lib/schema/boolean.js b/lib/schema/boolean.js index 197d53d396a..b6c4f891c41 100644 --- a/lib/schema/boolean.js +++ b/lib/schema/boolean.js @@ -156,6 +156,8 @@ SchemaBoolean.prototype.checkRequired = function(value) { * new M({ b: 'affirmative' }).b; // true * * @property convertToTrue + * @static + * @memberOf SchemaBoolean * @type {Set} * @api public */ @@ -176,6 +178,8 @@ Object.defineProperty(SchemaBoolean, 'convertToTrue', { * new M({ b: 'nay' }).b; // false * * @property convertToFalse + * @static + * @memberOf SchemaBoolean * @type {Set} * @api public */ diff --git a/lib/schema/date.js b/lib/schema/date.js index f91343954e8..746f39df8b4 100644 --- a/lib/schema/date.js +++ b/lib/schema/date.js @@ -234,7 +234,7 @@ SchemaDate.prototype.checkRequired = function(value, doc) { * @param {Date} value minimum date * @param {String} [message] optional custom error message * @return {SchemaType} this - * @see Customized Error Messages #error_messages_MongooseError-messages + * @see Customized Error Messages https://mongoosejs.com/docs/api/error.html#Error.messages * @api public */ @@ -296,7 +296,7 @@ SchemaDate.prototype.min = function(value, message) { * @param {Date} maximum date * @param {String} [message] optional custom error message * @return {SchemaType} this - * @see Customized Error Messages #error_messages_MongooseError-messages + * @see Customized Error Messages https://mongoosejs.com/docs/api/error.html#Error.messages * @api public */ diff --git a/lib/schema/documentarray.js b/lib/schema/documentarray.js index 8e68240270c..e8a2ba15fb2 100644 --- a/lib/schema/documentarray.js +++ b/lib/schema/documentarray.js @@ -184,7 +184,7 @@ function _createConstructor(schema, options, baseClass) { * @param {Object|string} [options] If string, same as `options.value`. * @param {String} [options.value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter. * @param {Boolean} [options.clone=true] By default, `discriminator()` clones the given `schema`. Set to `false` to skip cloning. - * @see discriminators /docs/discriminators.html + * @see discriminators https://mongoosejs.com/docs/discriminators.html * @return {Function} the constructor Mongoose will use for creating instances of this discriminator model * @api public */ diff --git a/lib/schema/number.js b/lib/schema/number.js index 2396c420630..48d3ff7ffda 100644 --- a/lib/schema/number.js +++ b/lib/schema/number.js @@ -204,7 +204,7 @@ SchemaNumber.prototype.checkRequired = function checkRequired(value, doc) { * @param {Number} value minimum number * @param {String} [message] optional custom error message * @return {SchemaType} this - * @see Customized Error Messages #error_messages_MongooseError-messages + * @see Customized Error Messages https://mongoosejs.com/docs/api/error.html#Error.messages * @api public */ @@ -258,7 +258,7 @@ SchemaNumber.prototype.min = function(value, message) { * @param {Number} maximum number * @param {String} [message] optional custom error message * @return {SchemaType} this - * @see Customized Error Messages #error_messages_MongooseError-messages + * @see Customized Error Messages https://mongoosejs.com/docs/api/error.html#Error.messages * @api public */ @@ -302,7 +302,7 @@ SchemaNumber.prototype.max = function(value, message) { * @param {Array} values allowed values * @param {String} [message] optional custom error message * @return {SchemaType} this - * @see Customized Error Messages #error_messages_MongooseError-messages + * @see Customized Error Messages https://mongoosejs.com/docs/api/error.html#Error.messages * @api public */ diff --git a/lib/schema/string.js b/lib/schema/string.js index daf9e97a14e..37993744263 100644 --- a/lib/schema/string.js +++ b/lib/schema/string.js @@ -201,7 +201,7 @@ SchemaString.checkRequired = SchemaType.checkRequired; * * @param {...String|Object} [args] enumeration values * @return {SchemaType} this - * @see Customized Error Messages #error_messages_MongooseError-messages + * @see Customized Error Messages https://mongoosejs.com/docs/api/error.html#Error.messages * @api public */ @@ -254,7 +254,7 @@ SchemaString.prototype.enum = function() { }; /** - * Adds a lowercase [setter](https://mongoosejs.com/docs/api/schematype.html#schematype_SchemaType-set). + * Adds a lowercase [setter](https://mongoosejs.com/docs/api/schematype.html#SchemaType.prototype.set()). * * #### Example: * @@ -293,7 +293,7 @@ SchemaString.prototype.lowercase = function(shouldApply) { }; /** - * Adds an uppercase [setter](https://mongoosejs.com/docs/api/schematype.html#schematype_SchemaType-set). + * Adds an uppercase [setter](https://mongoosejs.com/docs/api/schematype.html#SchemaType.prototype.set()). * * #### Example: * @@ -330,7 +330,7 @@ SchemaString.prototype.uppercase = function(shouldApply) { }; /** - * Adds a trim [setter](https://mongoosejs.com/docs/api/schematype.html#schematype_SchemaType-set). + * Adds a trim [setter](https://mongoosejs.com/docs/api/schematype.html#SchemaType.prototype.set()). * * The string value will be [trimmed](https://masteringjs.io/tutorials/fundamentals/trim-string) when set. * @@ -399,7 +399,7 @@ SchemaString.prototype.trim = function(shouldTrim) { * @param {Number} value minimum string length * @param {String} [message] optional custom error message * @return {SchemaType} this - * @see Customized Error Messages #error_messages_MongooseError-messages + * @see Customized Error Messages https://mongoosejs.com/docs/api/error.html#Error.messages * @api public */ @@ -455,7 +455,7 @@ SchemaString.prototype.minLength = SchemaString.prototype.minlength; * @param {Number} value maximum string length * @param {String} [message] optional custom error message * @return {SchemaType} this - * @see Customized Error Messages #error_messages_MongooseError-messages + * @see Customized Error Messages https://mongoosejs.com/docs/api/error.html#Error.messages * @api public */ @@ -518,7 +518,7 @@ SchemaString.prototype.maxLength = SchemaString.prototype.maxlength; * @param {RegExp} regExp regular expression to test against * @param {String} [message] optional custom error message * @return {SchemaType} this - * @see Customized Error Messages #error_messages_MongooseError-messages + * @see Customized Error Messages https://mongoosejs.com/docs/api/error.html#Error.messages * @api public */ diff --git a/lib/schema/uuid.js b/lib/schema/uuid.js index 956c8faa8a1..8c16b84ef13 100644 --- a/lib/schema/uuid.js +++ b/lib/schema/uuid.js @@ -8,7 +8,6 @@ const MongooseBuffer = require('../types/buffer'); const SchemaType = require('../schematype'); const CastError = SchemaType.CastError; const utils = require('../utils'); -const isBsonType = require('../helpers/isBsonType'); const handleBitwiseOperator = require('./operators/bitwise'); const UUID_FORMAT = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/i; @@ -86,7 +85,13 @@ function binaryToString(uuidBin) { function SchemaUUID(key, options) { SchemaType.call(this, key, options, 'UUID'); - this.getters.push(binaryToString); + this.getters.push(function(value) { + // For populated + if (value != null && value.$__ != null) { + return value; + } + return binaryToString(value); + }); } /** @@ -110,7 +115,7 @@ SchemaUUID.prototype.constructor = SchemaUUID; */ SchemaUUID._cast = function(value) { - if (value === null) { + if (value == null) { return value; } @@ -247,11 +252,8 @@ SchemaUUID.prototype.checkRequired = function checkRequired(value) { */ SchemaUUID.prototype.cast = function(value, doc, init) { - if (SchemaType._isRef(this, value, doc, init)) { - if (isBsonType(value, 'UUID')) { - return value; - } - + if (utils.isNonBuiltinObject(value) && + SchemaType._isRef(this, value, doc, init)) { return this._castRef(value, doc, init); } diff --git a/lib/schematype.js b/lib/schematype.js index 050341d7d06..4591e46785c 100644 --- a/lib/schematype.js +++ b/lib/schematype.js @@ -35,7 +35,7 @@ const setOptionsForDefaults = { _skipMarkModified: true }; * schema.path('name') instanceof SchemaType; // true * * @param {String} path - * @param {SchemaTypeOptions} [options] See [SchemaTypeOptions docs](/docs/api/schematypeoptions.html) + * @param {SchemaTypeOptions} [options] See [SchemaTypeOptions docs](https://mongoosejs.com/docs/api/schematypeoptions.html) * @param {String} [instance] * @api public */ @@ -532,7 +532,7 @@ SchemaType.prototype.sparse = function(bool) { /** * Defines this path as immutable. Mongoose prevents you from changing - * immutable paths unless the parent document has [`isNew: true`](/docs/api/document.html#document_Document-isNew). + * immutable paths unless the parent document has [`isNew: true`](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew()). * * #### Example: * @@ -550,7 +550,7 @@ SchemaType.prototype.sparse = function(bool) { * doc.name; // 'test', because `name` is immutable * * Mongoose also prevents changing immutable properties using `updateOne()` - * and `updateMany()` based on [strict mode](/docs/guide.html#strict). + * and `updateMany()` based on [strict mode](https://mongoosejs.com/docs/guide.html#strict). * * #### Example: * @@ -569,7 +569,7 @@ SchemaType.prototype.sparse = function(bool) { * * @param {Boolean} bool * @return {SchemaType} this - * @see isNew /docs/api/document.html#document_Document-isNew + * @see isNew https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew() * @api public */ @@ -792,7 +792,7 @@ SchemaType.prototype.get = function(fn) { * must return `Boolean`. Returning `false` or throwing an error means * validation failed. * - * The error message argument is optional. If not passed, the [default generic error message template](#error_messages_MongooseError-messages) will be used. + * The error message argument is optional. If not passed, the [default generic error message template](https://mongoosejs.com/docs/api/error.html#Error.messages) will be used. * * #### Example: * @@ -825,7 +825,7 @@ SchemaType.prototype.get = function(fn) { * From the examples above, you may have noticed that error messages support * basic templating. There are a few other template keywords besides `{PATH}` * and `{VALUE}` too. To find out more, details are available - * [here](#error_messages_MongooseError-messages). + * [here](./error.html#Error.prototype.name). * * If Mongoose's built-in error message templating isn't enough, Mongoose * supports setting the `message` property to a function. @@ -863,9 +863,9 @@ SchemaType.prototype.get = function(fn) { * * You might use asynchronous validators to retreive other documents from the database to validate against or to meet other I/O bound validation needs. * - * Validation occurs `pre('save')` or whenever you manually execute [document#validate](#document_Document-validate). + * Validation occurs `pre('save')` or whenever you manually execute [document#validate](https://mongoosejs.com/docs/api/document.html#Document.prototype.validate()). * - * If validation fails during `pre('save')` and no callback was passed to receive the error, an `error` event will be emitted on your Models associated db [connection](#connection_Connection), passing the validation error object along. + * If validation fails during `pre('save')` and no callback was passed to receive the error, an `error` event will be emitted on your Models associated db [connection](https://mongoosejs.com/docs/api/connection.html#Connection()), passing the validation error object along. * * const conn = mongoose.createConnection(..); * conn.on('error', handleError); @@ -926,7 +926,7 @@ SchemaType.prototype.validate = function(obj, message, type) { if (!utils.isPOJO(arg)) { const msg = 'Invalid validator. Received (' + typeof arg + ') ' + arg - + '. See https://mongoosejs.com/docs/api/schematype.html#schematype_SchemaType-validate'; + + '. See https://mongoosejs.com/docs/api/schematype.html#SchemaType.prototype.validate()'; throw new Error(msg); } @@ -993,13 +993,13 @@ SchemaType.prototype.validate = function(obj, message, type) { * @param {Function} [options.ErrorConstructor] custom error constructor. The constructor receives 1 parameter, an object containing the validator properties. * @param {String} [message] optional custom error message * @return {SchemaType} this - * @see Customized Error Messages #error_messages_MongooseError-messages - * @see SchemaArray#checkRequired #schema_array_SchemaArray-checkRequired - * @see SchemaBoolean#checkRequired #schema_boolean_SchemaBoolean-checkRequired - * @see SchemaBuffer#checkRequired #schema_buffer_SchemaBuffer-schemaName - * @see SchemaNumber#checkRequired #schema_number_SchemaNumber-min - * @see SchemaObjectId#checkRequired #schema_objectid_ObjectId-auto - * @see SchemaString#checkRequired #schema_string_SchemaString-checkRequired + * @see Customized Error Messages https://mongoosejs.com/docs/api/error.html#Error.messages + * @see SchemaArray#checkRequired https://mongoosejs.com/docs/api/schemaarray.html#SchemaArray.prototype.checkRequired() + * @see SchemaBoolean#checkRequired https://mongoosejs.com/docs/api/schemaboolean.html#SchemaBoolean.prototype.checkRequired() + * @see SchemaBuffer#checkRequired https://mongoosejs.com/docs/api/schemabuffer.html#SchemaBuffer.prototype.checkRequired() + * @see SchemaNumber#checkRequired https://mongoosejs.com/docs/api/schemanumber.html#SchemaNumber.prototype.checkRequired() + * @see SchemaObjectId#checkRequired https://mongoosejs.com/docs/api/schemaobjectid.html#ObjectId.prototype.checkRequired() + * @see SchemaString#checkRequired https://mongoosejs.com/docs/api/schemastring.html#SchemaString.prototype.checkRequired() * @api public */ @@ -1522,6 +1522,7 @@ SchemaType.prototype._castRef = function _castRef(value, doc, init) { const path = doc.$__fullPath(this.path, true); const owner = doc.ownerDocument(); const pop = owner.$populated(path, true); + let ret = value; if (!doc.$__.populated || !doc.$__.populated[path] || diff --git a/lib/types/array/methods/index.js b/lib/types/array/methods/index.js index ef262d58834..d7aae6567d9 100644 --- a/lib/types/array/methods/index.js +++ b/lib/types/array/methods/index.js @@ -105,7 +105,7 @@ const methods = { * #### Note: * * _Calling this multiple times on an array before saving sends the same command as calling it once._ - * _This update is implemented using the MongoDB [$pop](https://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop) method which enforces this restriction._ + * _This update is implemented using the MongoDB [$pop](https://www.mongodb.com/docs/manual/reference/operator/update/pop/) method which enforces this restriction._ * * doc.array = [1,2,3]; * @@ -130,7 +130,7 @@ const methods = { * @memberOf MongooseArray * @instance * @method $shift - * @see mongodb https://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop + * @see mongodb https://www.mongodb.com/docs/manual/reference/operator/update/pop/ */ $shift() { @@ -153,7 +153,7 @@ const methods = { * #### NOTE: * * _Calling this multiple times on an array before saving sends the same command as calling it once._ - * _This update is implemented using the MongoDB [$pop](https://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop) method which enforces this restriction._ + * _This update is implemented using the MongoDB [$pop](https://www.mongodb.com/docs/manual/reference/operator/update/pop/) method which enforces this restriction._ * * doc.array = [1,2,3]; * @@ -178,7 +178,7 @@ const methods = { * @method $pop * @memberOf MongooseArray * @instance - * @see mongodb https://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop + * @see mongodb https://www.mongodb.com/docs/manual/reference/operator/update/pop/ * @method $pop * @memberOf MongooseArray */ @@ -547,7 +547,7 @@ const methods = { * * _marks the entire array as modified which will pass the entire thing to $set potentially overwriting any changes that happen between when you retrieved the object and when you save it._ * - * @see MongooseArray#$pop #types_array_MongooseArray-%24pop + * @see MongooseArray#$pop https://mongoosejs.com/docs/api/array.html#MongooseArray.prototype.$pop() * @api public * @method pop * @memberOf MongooseArray @@ -563,7 +563,7 @@ const methods = { /** * Pulls items from the array atomically. Equality is determined by casting * the provided value to an embedded document and comparing using - * [the `Document.equals()` function.](/docs/api/document.html#document_Document-equals) + * [the `Document.equals()` function.](https://mongoosejs.com/docs/api/document.html#Document.prototype.equals()) * * #### Example: * @@ -585,7 +585,7 @@ const methods = { * The first pull call will result in a atomic operation on the database, if pull is called repeatedly without saving the document, a $set operation is used on the complete array instead, overwriting possible changes that happened on the database in the meantime. * * @param {...any} [args] - * @see mongodb https://www.mongodb.org/display/DOCS/Updating/#Updating-%24pull + * @see mongodb https://www.mongodb.com/docs/manual/reference/operator/update/pull/ * @api public * @method pull * @memberOf MongooseArray @@ -716,10 +716,10 @@ const methods = { }, /** - * Alias of [pull](#mongoosearray_MongooseArray-pull) + * Alias of [pull](https://mongoosejs.com/docs/api/array.html#MongooseArray.prototype.pull()) * - * @see MongooseArray#pull #types_array_MongooseArray-pull - * @see mongodb https://www.mongodb.org/display/DOCS/Updating/#Updating-%24pull + * @see MongooseArray#pull https://mongoosejs.com/docs/api/array.html#MongooseArray.prototype.pull() + * @see mongodb https://www.mongodb.com/docs/manual/reference/operator/update/pull/ * @api public * @memberOf MongooseArray * @instance @@ -806,7 +806,7 @@ const methods = { * @api public * @method sort * @memberOf MongooseArray - * @see https://masteringjs.io/tutorials/fundamentals/array-sort + * @see MasteringJS: Array sort https://masteringjs.io/tutorials/fundamentals/array-sort */ sort() { @@ -826,7 +826,7 @@ const methods = { * @api public * @method splice * @memberOf MongooseArray - * @see https://masteringjs.io/tutorials/fundamentals/array-splice + * @see MasteringJS: Array splice https://masteringjs.io/tutorials/fundamentals/array-splice */ splice() { diff --git a/lib/utils.js b/lib/utils.js index 0762520697e..1a013d610d9 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -4,6 +4,7 @@ * Module dependencies. */ +const UUID = require('bson').UUID; const ms = require('ms'); const mpath = require('mpath'); const ObjectId = require('./types/objectid'); @@ -395,6 +396,7 @@ exports.isNonBuiltinObject = function isNonBuiltinObject(val) { return typeof val === 'object' && !exports.isNativeObject(val) && !exports.isMongooseType(val) && + !(val instanceof UUID) && val != null; }; diff --git a/lib/virtualtype.js b/lib/virtualtype.js index 6efa01c096e..87c912fdd70 100644 --- a/lib/virtualtype.js +++ b/lib/virtualtype.js @@ -13,12 +13,12 @@ const utils = require('./utils'); * fullname instanceof mongoose.VirtualType // true * * @param {Object} options - * @param {String|Function} [options.ref] if `ref` is not nullish, this becomes a [populated virtual](/docs/populate.html#populate-virtuals) + * @param {String|Function} [options.ref] if `ref` is not nullish, this becomes a [populated virtual](https://mongoosejs.com/docs/populate.html#populate-virtuals) * @param {String|Function} [options.localField] the local field to populate on if this is a populated virtual. * @param {String|Function} [options.foreignField] the foreign field to populate on if this is a populated virtual. * @param {Boolean} [options.justOne=false] by default, a populated virtual is an array. If you set `justOne`, the populated virtual will be a single doc or `null`. * @param {Boolean} [options.getters=false] if you set this to `true`, Mongoose will call any custom getters you defined on this virtual - * @param {Boolean} [options.count=false] if you set this to `true`, `populate()` will set this virtual to the number of populated documents, as opposed to the documents themselves, using [`Query#countDocuments()`](/docs/api/query.html#query_Query-countDocuments) + * @param {Boolean} [options.count=false] if you set this to `true`, `populate()` will set this virtual to the number of populated documents, as opposed to the documents themselves, using [`Query#countDocuments()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.countDocuments()) * @param {Object|Function} [options.match=null] add an extra match condition to `populate()` * @param {Number} [options.limit=null] add a default `limit` to the `populate()` query * @param {Number} [options.skip=null] add a default `skip` to the `populate()` query diff --git a/package.json b/package.json index 9c74e0b98d1..ee75c8d0026 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "7.0.3", + "version": "7.0.5", "author": "Guillermo Rauch ", "keywords": [ "mongodb", @@ -28,10 +28,10 @@ "sift": "16.0.1" }, "devDependencies": { - "@babel/core": "7.21.0", - "@babel/preset-env": "7.20.2", - "@typescript-eslint/eslint-plugin": "5.54.0", - "@typescript-eslint/parser": "5.54.0", + "@babel/core": "7.21.4", + "@babel/preset-env": "7.21.4", + "@typescript-eslint/eslint-plugin": "5.57.0", + "@typescript-eslint/parser": "5.57.0", "acquit": "1.3.0", "acquit-ignore": "0.2.1", "acquit-require": "0.1.1", @@ -45,28 +45,29 @@ "crypto-browserify": "3.12.0", "dotenv": "16.0.3", "dox": "1.0.0", - "eslint": "8.35.0", + "eslint": "8.37.0", "eslint-plugin-markdown": "^3.0.0", "eslint-plugin-mocha-no-only": "1.1.1", "express": "^4.18.1", "highlight.js": "11.7.0", "lodash.isequal": "4.5.0", "lodash.isequalwith": "4.4.0", - "marked": "4.2.12", + "marked": "4.3.0", "mkdirp": "^2.1.3", "mocha": "10.2.0", "moment": "2.x", - "mongodb-memory-server": "8.11.5", + "mongodb-memory-server": "8.12.1", "ncp": "^2.0.0", "nyc": "15.1.0", "pug": "3.0.2", "q": "1.5.1", - "sinon": "15.0.1", + "sinon": "15.0.3", "stream-browserify": "3.0.0", "tsd": "0.25.0", - "typescript": "4.9.5", + "typescript": "5.0.3", "uuid": "9.0.0", - "webpack": "5.75.0" + "webpack": "5.77.0", + "fs-extra": "~11.1.1" }, "directories": { "lib": "./lib/mongoose" diff --git a/scripts/generateSearch.js b/scripts/generateSearch.js index ce85eb9be8b..8cf41f5646d 100644 --- a/scripts/generateSearch.js +++ b/scripts/generateSearch.js @@ -10,7 +10,7 @@ try { } } const cheerio = require('cheerio'); -const filemap = require('../docs/source'); +const docsFilemap = require('../docs/source'); const fs = require('fs'); const pug = require('pug'); const mongoose = require('../'); @@ -31,34 +31,30 @@ const contentSchema = new mongoose.Schema({ title: { type: String, required: true }, body: { type: String, required: true }, url: { type: String, required: true }, - version: { type: String, required: true, default: version } + version: { type: String, required: true, default: version }, + versionNumber: { type: Number, required: true, default: version.replace(/\.x$/, '') } }); contentSchema.index({ title: 'text', body: 'text' }); const Content = mongoose.model('Content', contentSchema, 'Content'); const contents = []; -const api = require('../docs/source/api'); - -// API docs are special, because they are not added to the file-map individually currently and use different properties -for (const _class of api.docs) { - for (const prop of _class.props) { - const content = new Content({ - title: `API: ${prop.name}`, - body: prop.description, - url: `api/${_class.fileName}.html#${prop.anchorId}` - }); - const err = content.validateSync(); - if (err != null) { - console.error(content); - throw err; +for (const [filename, file] of Object.entries(docsFilemap.fileMap)) { + if (file.api) { + for (const prop of file.props) { + const content = new Content({ + title: `API: ${prop.name}`, + body: prop.description, + url: `${filename}#${prop.anchorId}` + }); + const err = content.validateSync(); + if (err != null) { + console.error(content); + throw err; + } + contents.push(content); } - contents.push(content); - } -} - -for (const [filename, file] of Object.entries(filemap)) { - if (file.markdown) { + } else if (file.markdown) { let text = fs.readFileSync(filename, 'utf8'); text = markdown.parse(text); diff --git a/scripts/static.js b/scripts/static.js index 6f2e0a8e8c7..09178b533c1 100644 --- a/scripts/static.js +++ b/scripts/static.js @@ -10,14 +10,23 @@ const port = process.env.PORT : 8089; async function main() { - await website.pugifyAllFiles(); + await Promise.all([ + website.copyAllRequiredFiles(), + website.pugifyAllFiles() + ]); // start watching for file changes and re-compile them, so that they can be served directly website.startWatch(); app.use('/', express.static(website.cwd)); app.listen(port, () => { - console.log(`now listening on http://127.0.0.1:${port}`); + let urlPath = '/'; + + if (website.versionObj.versionedDeploy) { + urlPath = website.versionObj.versionedPath; + } + + console.log(`now listening on http://127.0.0.1:${port}${urlPath}`); }); } diff --git a/scripts/website.js b/scripts/website.js index 69c276810d0..0ef9598c708 100644 --- a/scripts/website.js +++ b/scripts/website.js @@ -8,6 +8,7 @@ const path = require('path'); const pug = require('pug'); const pkg = require('../package.json'); const transform = require('acquit-require'); +const childProcess = require("child_process"); // using "__dirname" and ".." to have a consistent CWD, this script should not be runnable, even when not being in the root of the project // also a consistent root path so that it is easy to change later when the script should be moved @@ -75,53 +76,177 @@ const tests = [ ...acquit.parse(fs.readFileSync(path.join(testPath, 'docs/schemas.test.js')).toString()) ]; -function getVersion() { - return require('../package.json').version; -} +/** + * Array of array of semver numbers, sorted with highest number first + * @example + * [[1,2,3], [0,1,2]] + * @type {number[][]} + */ +let filteredTags = []; + +/** + * Parse a given semver version string to a number array + * @param {string} str The string to parse + * @returns number array or undefined + */ +function parseVersion(str) { + const versionReg = /^v?(\d+)\.(\d+)\.(\d+)$/i; + + const match = versionReg.exec(str); + + if (!!match) { + const parsed = [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])] + + // fallback just in case some number did not parse + if (Number.isNaN(parsed[0]) || Number.isNaN(parsed[1]) || Number.isNaN(parsed[2])) { + console.log(`some version was matched but did not parse to int! "${str}"`); + return undefined; + } + return parsed; + } -function getLatestLegacyVersion(startsWith) { - const hist = fs.readFileSync(path.join(cwd, 'CHANGELOG.md'), 'utf8').replace(/\r/g, '\n').split('\n'); + return undefined; +} - for (const rawLine of hist) { - const line = (rawLine || '').trim(); - if (!line) { - continue; - } - const match = /^\s*([^\s]+)\s/.exec(line); - if (match && match[1] && match[1].startsWith(startsWith)) { - return match[1]; +function getVersions() { + // get all tags from git + const res = childProcess.execSync("git tag").toString(); + + filteredTags = res.split('\n') + // map all gotten tags if they match the regular expression + .map(parseVersion) + // filter out all null / undefined / falsy values + .filter(v => !!v) + // sort tags with latest (highest) first + .sort((a, b) => { + if (a[0] === b[0]) { + if (a[1] === b[1]) { + return b[2] - a[2]; + } + return b[1] - a[1]; } + return b[0] - a[0]; + }); + + if (filteredTags.length === 0) { + console.error("no tags found!"); + filteredTags.push([0,0,0]); } +} + +/** + * Stringify a semver number array + * @param {number[]} arr The array to stringify + * @param {boolean} dotX If "true", return "5.X" instead of "5.5.5" + * @returns + */ +function stringifySemverNumber(arr, dotX) { + if (dotX) { + return `${arr[0]}.x`; + } + return `${arr[0]}.${arr[1]}.${arr[2]}`; +} - throw new Error('no match found'); +/** + * Get the latest version available + * @returns {Version} + */ +function getLatestVersion() { + return { listed: stringifySemverNumber(filteredTags[0]), path: '' }; } -// use last release -pkg.version = getVersion(); -pkg.latest6x = getLatestLegacyVersion('6.'); -pkg.latest5x = getLatestLegacyVersion('5.'); +/** + * Get the latest version for the provided major version + * @param {number} version major version to search for + * @returns {Version} + */ +function getLatestVersionOf(version) { + let foundVersion = filteredTags.find(v => v[0] === version); + + // fallback to "0" in case a version cannot be found + if (!foundVersion) { + console.error(`Could not find a version for major "${version}"`); + foundVersion = [0, 0, 0]; + } + + return {listed: stringifySemverNumber(foundVersion), path: stringifySemverNumber(foundVersion, true)}; +} + +/** + * Try to get the current version on the checked-out branch + * @returns {Version} + */ +function getCurrentVersion() { + let versionToUse = pkg.version; + + // i dont think this will ever happen, but just in case + if (!pkg.version) { + console.log("no version from package?"); + versionToUse = getLatestVersion(); + } + + return {listed: versionToUse, path: stringifySemverNumber(parseVersion(versionToUse), true) }; +} + +// execute function to get all tags from git +getVersions(); + +/** + * @typedef {Object} Version + * @property {string} listed The string it is displayed as + * @property {string} path The path to use for the actual url + */ + +/** + * Object for all version information + * @property {Version} currentVersion The current version this branch is built for + * @property {string} latestVersion The latest version available across the repository + * @property {Version[]} pastVersions The latest versions of past major versions to include as selectable + * @property {boolean} versionedDeploy Indicates wheter to build for a version or as latest (no prefix) + * @property {string} versionedPath The path to use for versioned deploy (empty string when "versionedDeploy" is false) + */ +const versionObj = (() => { + const base = { + currentVersion: getCurrentVersion(), + latestVersion: getLatestVersion(), + pastVersions: [ + getLatestVersionOf(6), + getLatestVersionOf(5), + ] + }; + const versionedDeploy = process.env.DOCS_DEPLOY === "true" ? !(base.currentVersion.listed === base.latestVersion.listed) : false; + + const versionedPath = versionedDeploy ? `/docs/${base.currentVersion.path}` : ''; + + return { + ...base, + versionedDeploy, + versionedPath + }; +})(); // Create api dir if it doesn't already exist try { fs.mkdirSync(path.join(cwd, './docs/api')); } catch (err) {} // eslint-disable-line no-empty -require('../docs/source/splitApiDocs'); -const filemap = Object.assign({}, require('../docs/source')); -const files = Object.keys(filemap); +const docsFilemap = require('../docs/source/index'); +const files = Object.keys(docsFilemap.fileMap); +// api explicitly imported for specific file loading +const apiReq = require('../docs/source/api'); const wrapMarkdown = (md, baseLayout) => ` extends ${baseLayout} append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") + link(rel="stylesheet", href="#{versions.versionedPath}/docs/css/inlinecpc.css") + script(type="text/javascript" src="#{versions.versionedPath}/docs/js/native.js") style. p { line-height: 1.5em } block content - + :markdown ${md.split('\n').map(line => ' ' + line).join('\n')} @@ -139,7 +264,74 @@ const cpc = ` `; -async function pugify(filename, options) { +/** Alias to not execute "promisify" often */ +const pugRender = promisify(pug.render); + +/** Find all urls that are href's and start with "https://mongoosejs.com" */ +const mongooseComRegex = /(?:href=")(https:\/\/mongoosejs\.com\/?)/g; +/** Regex to detect a versioned path */ +const versionedDocs = /docs\/\d/; + +/** + * Map urls (https://mongoosejs.com/) to local paths + * @param {String} block The String block to look for urls + * @param {String} currentUrl The URL the block is for (non-versioned) + */ +function mapURLs(block, currentUrl) { + let match; + + let out = ''; + let lastIndex = 0; + + while ((match = mongooseComRegex.exec(block)) !== null) { + // console.log("match", match); + // cant just use "match.index" byitself, because of the extra "href=\"" condition, which is not factored in in "match.index" + let startIndex = match.index + match[0].length - match[1].length; + out += block.slice(lastIndex, startIndex); + lastIndex = startIndex + match[1].length; + + // somewhat primitive gathering of the url, but should be enough for now + let fullUrl = /^\/[^"]+/.exec(block.slice(lastIndex-1)); + + let noPrefix = false; + + if (fullUrl) { + // extra processing to only use "#otherId" instead of using full url for the same page + // at least firefox does not make a difference between a full path and just "#", but it makes debugging paths easier + if (fullUrl[0].startsWith(currentUrl)) { + let indexMatch = /#/.exec(fullUrl); + + if (indexMatch) { + lastIndex += indexMatch.index - 1; + noPrefix = true; + } + } + } + + if (!noPrefix) { + // map all to the versioned-path, unless a explicit version is given + if (!versionedDocs.test(block.slice(lastIndex, lastIndex+10))) { + out += versionObj.versionedPath + "/"; + } else { + out += "/"; + } + } + } + + out += block.slice(lastIndex); + + return out; +} + +/** + * Render a given file with the given options + * @param {String} filename The documentation file path to render + * @param {import("../docs/source/index").DocsOptions} options The options to use to render the file (api may be overwritten at reload) + * @param {Boolean} isReload Indicate this is a reload of the file + * @returns + */ +async function pugify(filename, options, isReload = false) { + /** Path for the output file */ let newfile = undefined; options = options || {}; options.package = pkg; @@ -148,7 +340,20 @@ async function pugify(filename, options) { filename.replace(cwd, ''); options.editLink = options.editLink || _editLink; - let contents = fs.readFileSync(path.resolve(cwd, filename)).toString(); + /** Set which path to read, also pug uses this to resolve relative includes from */ + let inputFile = filename; + + if (options.api) { + // only re-parse the api file when in a reload, because it is done once at file load + if (isReload) { + apiReq.parseFile(options.file); + // overwrite original options because of reload + options = {...options, ...apiReq.docs.get(options.file)}; + } + inputFile = path.resolve(cwd, 'docs/api_split.pug'); + } + + let contents = fs.readFileSync(path.resolve(cwd, inputFile)).toString(); if (options.acquit) { contents = transform(contents, tests); @@ -163,24 +368,41 @@ async function pugify(filename, options) { newfile = filename.replace('.md', '.html'); } - options.filename = filename; + options.filename = inputFile; options.filters = { markdown: function(block) { return markdown.parse(block); } }; + if (options.api) { + newfile = path.resolve(cwd, filename); + options.docs = Array.from(docsFilemap.apiDocs.values()); + } + newfile = newfile || filename.replace('.pug', '.html'); + + /** Unversioned final documentation path */ + const docsPath = newfile; + + if (versionObj.versionedDeploy) { + newfile = path.resolve(cwd, path.join('.', versionObj.versionedPath), path.relative(cwd, newfile)); + await fs.promises.mkdir(path.dirname(newfile), {recursive:true}); + } + options.outputUrl = newfile.replace(cwd, ''); options.jobs = jobs; + options.versions = versionObj; options.opencollectiveSponsors = opencollectiveSponsors; - const str = await promisify(pug.render)(contents, options).catch(console.error); + let str = await pugRender(contents, options).catch(console.error); if (typeof str !== "string") { return; } + + str = mapURLs(str, '/' + path.relative(cwd, docsPath)) await fs.promises.writeFile(newfile, str).catch((err) => { console.error('could not write', err.stack); @@ -191,11 +413,17 @@ async function pugify(filename, options) { // extra function to start watching for file-changes, without having to call this file directly with "watch" function startWatch() { - files.forEach((file) => { - const filepath = path.resolve(cwd, file); - fs.watchFile(filepath, { interval: 1000 }, (cur, prev) => { + Object.entries(docsFilemap.fileMap).forEach(([file, fileValue]) => { + let watchPath = path.resolve(cwd, file); + const notifyPath = path.resolve(cwd, file); + + if (fileValue.api) { + watchPath = path.resolve(cwd, fileValue.file); + } + + fs.watchFile(watchPath, { interval: 1000 }, (cur, prev) => { if (cur.mtime > prev.mtime) { - pugify(filepath, filemap[file]); + pugify(notifyPath, docsFilemap.fileMap[file], true); } }); }); @@ -203,15 +431,30 @@ function startWatch() { fs.watchFile(path.join(cwd, 'docs/layout.pug'), { interval: 1000 }, (cur, prev) => { if (cur.mtime > prev.mtime) { console.log('docs/layout.pug modified, reloading all files'); - pugifyAllFiles(true); + pugifyAllFiles(true, true); + } + }); + + fs.watchFile(path.join(cwd, 'docs/api_split.pug'), {interval: 1000}, (cur, prev) => { + if (cur.mtime > prev.mtime) { + console.log('docs/api_split.pug modified, reloading all api files'); + Promise.all(files.filter(v=> v.startsWith('docs/api')).map(async (file) => { + const filename = path.join(cwd, file); + await pugify(filename, docsFilemap.fileMap[file], true); + })); } }); } -async function pugifyAllFiles(noWatch) { +/** + * Render all files at once + * @param {Boolean} noWatch Set whether to start file watchers for reload + * @param {Boolean} isReload Indicate this is a reload of all files + */ +async function pugifyAllFiles(noWatch, isReload = false) { await Promise.all(files.map(async (file) => { const filename = path.join(cwd, file); - await pugify(filename, filemap[file]); + await pugify(filename, docsFilemap.fileMap[file], isReload); })); // enable watch after all files have been done once, and not in the loop to use less-code @@ -221,13 +464,39 @@ async function pugifyAllFiles(noWatch) { } } +/** Set which static paths to fully copy over to versioned docs */ +const pathsToCopy = [ + 'docs/js', + 'docs/css', + 'docs/images' +] + +/** Copy all static files when versionedDeploy is used */ +async function copyAllRequiredFiles() { + // dont copy files to themself + if (!versionObj.versionedDeploy) { + return; + } + + const fsextra = require('fs-extra'); + await Promise.all(pathsToCopy.map(async v => { + const resultPath = path.resolve(cwd, path.join('.', versionObj.versionedPath, v)); + await fsextra.copy(v, resultPath); + })) +} + exports.default = pugify; exports.pugify = pugify; exports.startWatch = startWatch; exports.pugifyAllFiles = pugifyAllFiles; +exports.copyAllRequiredFiles = copyAllRequiredFiles; +exports.versionObj = versionObj; exports.cwd = cwd; // only run the following code if this file is the main module / entry file if (isMain) { - pugifyAllFiles(); + console.log(`Processing ~${files.length} files`); + Promise.all([pugifyAllFiles(), copyAllRequiredFiles()]).then(() => { + console.log("Done Processing"); + }) } diff --git a/test/deno.js b/test/deno.js index 6515f93f86a..b41eeaa9df7 100644 --- a/test/deno.js +++ b/test/deno.js @@ -12,6 +12,8 @@ Object.defineProperty(process.stdout, 'getWindowSize', { import { parse } from "https://deno.land/std/flags/mod.ts" const args = parse(Deno.args); +Error.stackTraceLimit = 100; + const require = createRequire(import.meta.url); const Mocha = require('mocha'); diff --git a/test/docs/debug.test.js b/test/docs/debug.test.js index 9cdbd21b46a..db3e4b7523b 100644 --- a/test/docs/debug.test.js +++ b/test/docs/debug.test.js @@ -91,4 +91,22 @@ describe('debug: shell', function() { // Last log should not have been overwritten assert.equal(storedLog, lastLog); }); + + it('should avoid sending null session option with document ops (gh-13052)', async function() { + const args = []; + const m = new mongoose.Mongoose(); + m.set('debug', function() { + args.push([...arguments]); + }); + await m.connect(start.uri); + const schema = new Schema({ name: String }); + const Test = m.model('gh_13052', schema); + + await Test.create({ name: 'foo' }); + assert.equal(args.length, 1); + assert.equal(args[0][1], 'insertOne'); + assert.strictEqual(args[0][3], undefined); + + await m.disconnect(); + }); }); diff --git a/test/document.test.js b/test/document.test.js index 76029b27d31..d7bac350061 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -1968,25 +1968,6 @@ describe('document', function() { assert.equal(threw, true); }); - - it('passes save custom options to Model.exists(...) when no changes are present (gh-8739)', async function() { - const personSchema = new Schema({ name: String }); - - let optionInMiddleware; - - personSchema.pre('findOne', function(next) { - optionInMiddleware = this.getOptions().customOption; - - return next(); - }); - - const Person = db.model('Person', personSchema); - - const person = await Person.create({ name: 'Hafez' }); - await person.save({ customOption: 'test' }); - - assert.equal(optionInMiddleware, 'test'); - }); }); it('properly calls queue functions (gh-2856)', function() { diff --git a/test/model.discriminator.test.js b/test/model.discriminator.test.js index 173f426fcc6..6f7fd05d4a2 100644 --- a/test/model.discriminator.test.js +++ b/test/model.discriminator.test.js @@ -2112,4 +2112,72 @@ describe('model', function() { const Test = Base.discriminator('model-discriminator-custom1', childSchema); assert.deepEqual(Test.schema.options.toJSON, { virtuals: true, getters: true }); }); + it('uses "value" over "name" for multi-dimensonal arrays (gh-13201)', function() { + const buildingSchema = new mongoose.Schema( + { + width: { + type: Number, + default: 100 + }, + type: { + type: String, + enum: ['G', 'S'] + } + }, + { discriminatorKey: 'type', _id: false } + ); + + const garageSchema = buildingSchema.clone(); + garageSchema.add({ + slotsForCars: { + type: Number, + default: 10 + } + }); + + const summerSchema = buildingSchema.clone(); + summerSchema.add({ + distanceToLake: { + type: Number, + default: 100 + } + }); + + const areaSchema = new mongoose.Schema({ + buildings: { + type: [ + [ + { + type: buildingSchema + } + ] + ] + } + }); + + garageSchema.paths['type'].options.$skipDiscriminatorCheck = true; + summerSchema.paths['type'].options.$skipDiscriminatorCheck = true; + const path = areaSchema.path('buildings'); + + path.discriminator('Garage', garageSchema, 'G'); + path.discriminator('Summer', summerSchema, 'S'); + + const AreaModel = mongoose.model('Area', areaSchema); + + const area = new AreaModel({ + buildings: [ + [ + { type: 'S', distanceToLake: 100 }, + { type: 'G', slotsForCars: 20 } + ] + ] + }); + + assert.ok(area.buildings[0][0].distanceToLake); + assert.ok(area.buildings[0][1].slotsForCars); + + const innerBuildingsPath = AreaModel.schema.path('buildings.$'); + assert.ok(innerBuildingsPath.schemaOptions.type.discriminators.Garage); + assert.equal(innerBuildingsPath.schemaOptions.type.discriminators.Garage.discriminatorMapping.value, 'G'); + }); }); diff --git a/test/model.findOneAndUpdate.test.js b/test/model.findOneAndUpdate.test.js index 3069209069d..5c7b9684d31 100644 --- a/test/model.findOneAndUpdate.test.js +++ b/test/model.findOneAndUpdate.test.js @@ -2061,4 +2061,24 @@ describe('model: findOneAndUpdate:', function() { assert.ifError(err); }); + + it('casts array filters (gh-13219)', async function() { + const MyModel = db.model('Test', new Schema({ + _id: Number, + grades: [Number] + })); + + await MyModel.create([ + { _id: 1, grades: [95, 102, 90] } + ]); + + await MyModel.findOneAndUpdate( + {}, + { $set: { 'grades.$[element]': 100 } }, + { arrayFilters: [{ element: { $gt: '100' } }] } + ); + + const doc = await MyModel.findOne(); + assert.deepEqual(doc.toObject().grades, [95, 100, 90]); + }); }); diff --git a/test/model.indexes.test.js b/test/model.indexes.test.js index dba9318233a..ed25a0818db 100644 --- a/test/model.indexes.test.js +++ b/test/model.indexes.test.js @@ -256,19 +256,23 @@ describe('model', function() { it('error should emit on the model', async function() { const schema = new Schema({ name: { type: String } }); - const Test = db.model('Test', schema); - + const Test = db.model('Test', schema, 'Test'); + await Test.init(); await Test.create({ name: 'hi' }, { name: 'hi' }); - Test.schema.index({ name: 1 }, { unique: true }); - Test.schema.index({ other: 1 }); - - const err = await Test.ensureIndexes().then(() => null, err => err); + const Test2 = db.model( + 'Test2', + new Schema({ + name: { + type: String, + unique: true + } + }), + 'Test' + ); + const err = await Test2.init().then(() => null, err => err); assert.ok(/E11000 duplicate key error/.test(err.message), err); - - delete Test.$init; - await Test.init().catch(() => {}); }); it('when one index creation errors', async function() { @@ -593,6 +597,70 @@ describe('model', function() { await User.collection.drop(); }); + it('should not re-create a compound text index that involves non-text indexes, using syncIndexes (gh-13136)', function(done) { + const Test = new Schema({ + title: { + type: String + }, + description: { + type: String + }, + age: { + type: Number + } + }, { + autoIndex: false + }); + + Test.index({ + title: 'text', + description: 'text', + age: 1 + }); + + const TestModel = db.model('Test', Test); + TestModel.syncIndexes().then((results1) => { + assert.deepEqual(results1, []); + // second call to syncIndexes should return an empty array, representing 0 deleted indexes + TestModel.syncIndexes().then((results2) => { + assert.deepEqual(results2, []); + done(); + }); + }); + }); + + it('should not find a diff when calling diffIndexes after syncIndexes involving a text and non-text compound index (gh-13136)', async function() { + const Test = new Schema({ + title: { + type: String + }, + description: { + type: String + }, + age: { + type: Number + } + }, { + autoIndex: false + }); + + Test.index({ + title: 'text', + description: 'text', + age: 1 + }); + + const TestModel = db.model('Test', Test); + await TestModel.init(); + + const diff = await TestModel.diffIndexes(); + assert.deepEqual(diff, { toCreate: [{ age: 1, title: 'text', description: 'text' }], toDrop: [] }); + await TestModel.syncIndexes(); + + const diff2 = await TestModel.diffIndexes(); + assert.deepEqual(diff2, { toCreate: [], toDrop: [] }); + }); + it('cleanIndexes (gh-6676)', async function() { let M = db.model('Test', new Schema({ diff --git a/test/model.populate.test.js b/test/model.populate.test.js index b5d0795aa6c..b5257b35895 100644 --- a/test/model.populate.test.js +++ b/test/model.populate.test.js @@ -10230,6 +10230,61 @@ describe('model: populate:', function() { assert.equal(person.stories[0].title, 'Casino Royale'); }); + it('supports removing and then recreating populate virtual using schema clone (gh-13085)', async function() { + const personSch = new mongoose.Schema( + { + firstName: { type: mongoose.SchemaTypes.String, required: true }, + surname: { type: mongoose.SchemaTypes.String, trim: true }, + nat: { type: mongoose.SchemaTypes.String, required: true, uppercase: true, minLength: 2, maxLength: 2 } + }, + { strict: true, timestamps: true } + ); + personSch.virtual('nationality', { + localField: 'nat', + foreignField: 'key', + ref: 'Nat', + justOne: true + }); + let Person = db.model('Person', personSch.clone(), 'people'); + + const natSch = new mongoose.Schema( + { + key: { type: mongoose.SchemaTypes.String, uppercase: true, index: true, minLength: 2, maxLength: 2 }, + desc: { type: mongoose.SchemaTypes.String, trim: true } + }, + { strict: true } + ); + const Nat = db.model('Nat', natSch); + let n = new Nat({ key: 'ES', desc: 'Spain' }); + await n.save(); + n = new Nat({ key: 'IT', desc: 'Italy' }); + await n.save(); + n = new Nat({ key: 'FR', desc: 'French' }); + await n.save(); + + let p = new Person({ firstName: 'Pepe', surname: 'Pérez', nat: 'it' }); + await p.save(); + p = new Person({ firstName: 'Paco', surname: 'Matinez', nat: 'es' }); + await p.save(); + p = new Person({ firstName: 'John', surname: 'Doe', nat: 'us' }); + await p.save(); + + personSch.removeVirtual('nationality'); + personSch.virtual('nationality', { + localField: 'nat', + foreignField: 'key', + ref: 'Nat', + justOne: true + }); + Person = db.model('Person', personSch.clone(), 'people', { overwriteModels: true }); + + const peopleList = await Person.find(). + sort({ firstName: 1 }). + populate({ path: 'nationality', match: { desc: 'Spain' } }); + assert.deepStrictEqual(peopleList.map(p => p.nationality?.key), [undefined, 'ES', undefined]); + + }); + describe('strictPopulate', function() { it('reports full path when throwing `strictPopulate` error with deep populate (gh-10923)', async function() { diff --git a/test/model.test.js b/test/model.test.js index 7128891ea79..c6e0648c9a0 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -3322,15 +3322,6 @@ describe('Model', function() { }); describe('3.6 features', function() { - before(async function() { - const version = await start.mongodVersion(); - const mongo36 = version[0] > 3 || (version[0] === 3 && version[1] >= 6); - - if (!mongo36) { - this.skip(); - } - }); - it('arrayFilter (gh-5965)', async function() { const MyModel = db.model('Test', new Schema({ @@ -4135,6 +4126,69 @@ describe('Model', function() { } }]); }); + + it('sends valid ops if ordered = false (gh-13176)', async function() { + const testSchema = new mongoose.Schema({ + num: Number + }); + const Test = db.model('Test', testSchema); + + const res = await Test.bulkWrite([ + { + updateOne: { + filter: {}, + update: { $set: { num: 'not a number' } }, + upsert: true + } + }, + { + updateOne: { + filter: {}, + update: { $set: { num: 42 } } + } + } + ], { ordered: false }); + assert.ok(res.mongoose); + assert.equal(res.mongoose.validationErrors.length, 1); + assert.strictEqual(res.result.nUpserted, 0); + }); + + it('decorates write error with validation errors if unordered fails (gh-13176)', async function() { + const testSchema = new mongoose.Schema({ + num: Number + }); + const Test = db.model('Test', testSchema); + + await Test.deleteMany({}); + const { _id } = await Test.create({ num: 42 }); + + const err = await Test.bulkWrite([ + { + updateOne: { + filter: {}, + update: { $set: { num: 'not a number' } }, + upsert: true + } + }, + { + updateOne: { + filter: {}, + update: { $push: { num: 42 } } + } + }, + { + updateOne: { + filter: {}, + update: { $inc: { num: 57 } } + } + } + ], { ordered: false }).then(() => null, err => err); + assert.ok(err); + assert.equal(err.mongoose.validationErrors.length, 1); + + const { num } = await Test.findById(_id); + assert.equal(num, 99); + }); }); it('deleteOne with cast error (gh-5323)', async function() { @@ -5068,6 +5122,37 @@ describe('Model', function() { assert.ok(collOptions.timeseries); }); + it('createCollection() respects clusteredIndex', async function() { + const version = await start.mongodVersion(); + if (version[0] < 6) { + this.skip(); + return; + } + + const schema = Schema({ name: String, timestamp: Date, metadata: Object }, { + clusteredIndex: { + key: { _id: 1 }, + name: 'clustered test' + }, + autoCreate: false, + autoIndex: false + }); + + const Test = db.model('Test', schema, 'Test'); + await Test.init(); + + await Test.collection.drop().catch(() => {}); + await Test.createCollection(); + + const collections = await Test.db.db.listCollections().toArray(); + const coll = collections.find(coll => coll.name === 'Test'); + assert.ok(coll); + assert.deepEqual(coll.options.clusteredIndex.key, { _id: 1 }); + assert.equal(coll.options.clusteredIndex.name, 'clustered test'); + + await Test.collection.drop().catch(() => {}); + }); + it('mongodb actually removes expired documents (gh-11229)', async function() { this.timeout(1000 * 80); // 80 seconds, see later comments on why const version = await start.mongodVersion(); @@ -6831,6 +6916,25 @@ describe('Model', function() { assert.equal(TestModel.staticFn(), 'Returned from staticFn'); }); }); + + describe('Bypass middleware', function() { + it('should bypass middleware if save is called on a document with no changes gh-13250', async function() { + const testSchema = new Schema({ + name: String + }); + let bypass = true; + testSchema.pre('findOne', function(next) { + bypass = false; + next(); + }); + const Test = db.model('gh13250', testSchema); + const doc = await Test.create({ + name: 'Test Testerson' + }); + await doc.save(); + assert(bypass); + }); + }); }); diff --git a/test/query.test.js b/test/query.test.js index 270304f585f..4f04eee5afb 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -4001,4 +4001,14 @@ describe('Query', function() { await Test.findOneAndUpdate({}, { name: 'bar' }); assert.ok(!('projection' in lastOptions)); }); + it('should provide a clearer error message when sorting with empty string', async function() { + const testSchema = new Schema({ + name: { type: String } + }); + + const Error = db.model('error', testSchema); + await assert.rejects(async() => { + await Error.find().sort('-'); + }, { message: 'Invalid field "" passed to sort()' }); + }); }); diff --git a/test/schema.test.js b/test/schema.test.js index 12521dcdbfd..21fbd423f4f 100644 --- a/test/schema.test.js +++ b/test/schema.test.js @@ -2988,9 +2988,13 @@ describe('schema', function() { assert.ok(schema.virtuals.foo); schema.removeVirtual('foo'); assert.ok(!schema.virtuals.foo); + assert.ok(!schema.tree.foo); + + schema.virtual('foo').get(v => v || 99); + const Test = db.model('gh-8397', schema); const doc = new Test({ name: 'Test' }); - assert.equal(doc.foo, undefined); + assert.equal(doc.foo, 99); }); it('should allow deleting multiple virtuals gh-8397', async function() { diff --git a/test/schema.uuid.test.js b/test/schema.uuid.test.js index 8292c233d35..864c276ebb0 100644 --- a/test/schema.uuid.test.js +++ b/test/schema.uuid.test.js @@ -3,9 +3,9 @@ const start = require('./common'); const util = require('./util'); -const bson = require('bson'); - const assert = require('assert'); +const bson = require('bson'); +const { randomUUID } = require('crypto'); const mongoose = start.mongoose; const Schema = mongoose.Schema; @@ -129,6 +129,27 @@ describe('SchemaUUID', function() { assert.equal(organization, undefined); }); + it('works with populate (gh-13267)', async function() { + const userSchema = new mongoose.Schema({ + _id: { type: 'UUID', default: () => randomUUID() }, + name: String, + createdBy: { + type: 'UUID', + ref: 'User' + } + }); + const User = db.model('User', userSchema); + + const u1 = await User.create({ name: 'admin' }); + const { _id } = await User.create({ name: 'created', createdBy: u1._id }); + + const pop = await User.findById(_id).populate('createdBy').orFail(); + assert.equal(pop.createdBy.name, 'admin'); + assert.equal(pop.createdBy._id.toString(), u1._id.toString()); + + await pop.save(); + }); + // the following are TODOs based on SchemaUUID.prototype.$conditionalHandlers which are not tested yet it('should work with $bits* operators'); it('should work with $all operator'); diff --git a/test/types/models.test.ts b/test/types/models.test.ts index 13096f12b06..fbd4ac80fb7 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -17,7 +17,7 @@ import { } from 'mongoose'; import { expectAssignable, expectError, expectType } from 'tsd'; import { AutoTypedSchemaType, autoTypedSchema } from './schema.test'; -import { UpdateOneModel } from 'mongodb'; +import { UpdateOneModel, ChangeStreamInsertDocument } from 'mongodb'; function rawDocSyntax(): void { interface ITest { @@ -567,3 +567,14 @@ async function gh13151() { if (!test) return; expectType(test); } + +function gh13206() { + interface ITest { + name: string; + } + const TestSchema = new Schema({ name: String }); + const TestModel = model('Test', TestSchema); + TestModel.watch>([], { fullDocument: 'updateLookup' }).on('change', (change) => { + expectType>(change); + }); +} diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 717d38cd44c..00382a48993 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -10,7 +10,7 @@ import { Query, model, HydratedDocument, - SchemaOptions, + ResolveSchemaOptions, ObtainDocumentType, ObtainSchemaGeneric } from 'mongoose'; @@ -634,7 +634,8 @@ function gh12003() { type BaseSchemaType = InferSchemaType; - expectType<'type'>({} as ObtainSchemaGeneric['typeKey']); + type TSchemaOptions = ResolveSchemaOptions>; + expectType<'type'>({} as TSchemaOptions['typeKey']); expectType<{ name?: string }>({} as BaseSchemaType); } diff --git a/test/virtualtype.test.js b/test/virtualtype.test.js index 81e43269851..f39dfd6b2ef 100644 --- a/test/virtualtype.test.js +++ b/test/virtualtype.test.js @@ -1,13 +1,13 @@ 'use strict'; -const VirtualType = require('../lib/virtualtype'); const assert = require('assert'); +const start = require('./common'); describe('VirtualType', function() { describe('clone', function() { it('copies path and options correctly (gh-8587)', function() { const opts = { ref: 'User', localField: 'userId', foreignField: '_id' }; - const virtual = new VirtualType(Object.assign({}, opts), 'users'); + const virtual = new start.mongoose.VirtualType(Object.assign({}, opts), 'users'); const clone = virtual.clone(); assert.equal(clone.path, 'users'); diff --git a/types/index.d.ts b/types/index.d.ts index 9d389b97ad7..4eee53547d6 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -211,15 +211,21 @@ declare module 'mongoose' { TQueryHelpers = {}, TVirtuals = {}, TStaticMethods = {}, - TSchemaOptions extends ResolveSchemaOptions = DefaultSchemaOptions, - DocType extends ApplySchemaOptions, TSchemaOptions> = ApplySchemaOptions, TSchemaOptions>, + TSchemaOptions = DefaultSchemaOptions, + DocType extends ApplySchemaOptions< + ObtainDocumentType>, + ResolveSchemaOptions + > = ApplySchemaOptions< + ObtainDocumentType>, + ResolveSchemaOptions + >, THydratedDocumentType = HydratedDocument, TVirtuals & TInstanceMethods> > extends events.EventEmitter { /** * Create a new schema */ - constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions, TInstanceMethods, TQueryHelpers, TStaticMethods, TVirtuals, THydratedDocumentType> | TSchemaOptions); + constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions, TInstanceMethods, TQueryHelpers, TStaticMethods, TVirtuals, THydratedDocumentType> | ResolveSchemaOptions); /** Adds key path / schema type pairs to this schema. */ add(obj: SchemaDefinition> | Schema, prefix?: string): this; diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index e8f00d2446a..4379a45e112 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -46,7 +46,7 @@ declare module 'mongoose' { * @param {TSchema} TSchema A generic of schema type instance. * @param {alias} alias Targeted generic alias. */ - type ObtainSchemaGeneric = + type ObtainSchemaGeneric = TSchema extends Schema ? { EnforcedDocType: EnforcedDocType; @@ -60,8 +60,7 @@ declare module 'mongoose' { }[alias] : unknown; - // Without Omit, this gives us a "Type parameter 'TSchemaOptions' has a circular constraint." - type ResolveSchemaOptions = Omit, 'fakepropertyname'>; + type ResolveSchemaOptions = MergeType; type ApplySchemaOptions = ResolveTimestamps; diff --git a/types/models.d.ts b/types/models.d.ts index 714b4df2ab3..11647e7d90a 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -173,7 +173,14 @@ declare module 'mongoose' { * if you use `create()`) because with `bulkWrite()` there is only one network * round trip to the MongoDB server. */ - bulkWrite(writes: Array>, options?: mongodb.BulkWriteOptions & MongooseBulkWriteOptions): Promise; + bulkWrite( + writes: Array>, + options: mongodb.BulkWriteOptions & MongooseBulkWriteOptions & { ordered: false } + ): Promise; + bulkWrite( + writes: Array>, + options?: mongodb.BulkWriteOptions & MongooseBulkWriteOptions + ): Promise; /** * Sends multiple `save()` calls in a single `bulkWrite()`. This is faster than @@ -382,7 +389,7 @@ declare module 'mongoose' { validate(optional: any, pathsToValidate: PathsToValidate): Promise; /** Watches the underlying collection for changes using [MongoDB change streams](https://www.mongodb.com/docs/manual/changeStreams/). */ - watch(pipeline?: Array>, options?: mongodb.ChangeStreamOptions & { hydrate?: boolean }): mongodb.ChangeStream; + watch(pipeline?: Array>, options?: mongodb.ChangeStreamOptions & { hydrate?: boolean }): mongodb.ChangeStream; /** Adds a `$where` clause to this query */ $where(argument: string | Function): QueryWithHelpers, THydratedDocumentType, TQueryHelpers, TRawDocType>;