diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 96519ae8a18..861870b0dc7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - node: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + node: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] name: Node ${{ matrix.node }} steps: - uses: actions/checkout@v2 @@ -18,9 +18,13 @@ jobs: with: node-version: ${{ matrix.node }} - - name: Upgrade Node 5 npm + - name: Upgrade Node <= 5 npm run: npm install -g npm@3 - if: ${{ matrix.node == 5 }} + if: ${{ matrix.node <= 5 }} + + - name: Upgrade Node 7 npm + run: npm install -g npm@6 + if: ${{ matrix.node == 7 }} - run: npm install diff --git a/.gitignore b/.gitignore index aaf1108bee1..361b203fffb 100644 --- a/.gitignore +++ b/.gitignore @@ -34,8 +34,6 @@ tools/31* *.key -docs/*.html - # Webpack output dist test/files/main.js @@ -43,3 +41,10 @@ test/files/main.js package-lock.json .config* + +# Compiled docs +docs/*.html +docs/tutorials/*.html +docs/typescript/*.html +docs/api/*.html +index.html \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ad98e77aa83..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -language: node_js -sudo: false -node_js: [14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4] -install: - - travis_retry npm install -before_script: - - wget http://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.0.2.tgz - - tar -zxvf mongodb-linux-x86_64-ubuntu1604-4.0.2.tgz - - mkdir -p ./data/db/27017 - - mkdir -p ./data/db/27000 - - printf "\n--timeout 8000" >> ./test/mocha.opts - - ./mongodb-linux-x86_64-ubuntu1604-4.0.2/bin/mongod --fork --dbpath ./data/db/27017 --syslog --port 27017 - - export PATH=`pwd`/mongodb-linux-x86_64-ubuntu1604-4.0.2/bin/:$PATH - - sleep 2 - - mongod --version - - mkdir ./test/typescript/node_modules - - ln -s `pwd` ./test/typescript/node_modules/mongoose -matrix: - include: - - name: "👕Linter" - node_js: 10 - before_script: skip - script: npm run lint -notifications: - email: false diff --git a/History.md b/History.md index f28d0576bb6..dc466dbb1c0 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,444 @@ +5.13.15 / 2022-08-22 +==================== + * fix: backport fix for CVE-2022-2564 #12281 [shubanker](https://github.com/shubanker) + * docs: fix broken link from findandmodify method deprecation #11366 [laissonsilveira](https://github.com/laissonsilveira) + +5.13.14 / 2021-12-27 +==================== + * fix(timestamps): avoid setting createdAt on documents that already exist but dont have createdAt #11024 + * docs(models): fix up nModified example for 5.x #11055 + +5.13.13 / 2021-11-02 +==================== + * fix: upgrade to mongodb@3.7.3 #10909 [gaurav-sharma-gs](https://github.com/gaurav-sharma-gs) + * fix: correctly emit end event in before close #10916 [iovanom](https://github.com/iovanom) + * fix(index.d.ts): improve ts types for query set #10942 [jneal-afs](https://github.com/jneal-afs) + +5.13.12 / 2021-10-19 +==================== + * fix(cursor): use stream destroy method on close to prevent emitting duplicate 'close' #10897 [iovanom](https://github.com/iovanom) + * fix(index.d.ts): backport streamlining of FilterQuery and DocumentDefinition to avoid "excessively deep and possibly infinite" TS errors #10617 + +5.13.11 / 2021-10-12 +==================== + * fix: upgrade mongodb -> 3.7.2 #10871 [winstonralph](https://github.com/winstonralph) + * fix(connection): call setMaxListeners(0) on MongoClient to avoid event emitter memory leak warnings with `useDb()` #10732 + +5.13.10 / 2021-10-05 +==================== + * fix(index.d.ts): allow using type: SchemaDefinitionProperty in schema definitions #10674 + * fix(index.d.ts): allow AnyObject as param to findOneAndReplace() #10714 + +5.13.9 / 2021-09-06 +=================== + * fix(populate): avoid setting empty array on lean document when populate result is undefined #10599 + * fix(document): make depopulate() handle populated paths underneath document arrays #10592 + * fix: peg @types/bson version to 1.x || 4.0.x to avoid stubbed 4.2.x release #10678 + * fix(index.d.ts): simplify UpdateQuery to avoid "excessively deep and possibly infinite" errors with `extends Document` and `any` #10647 + * fix(index.d.ts): allow specifying weights as an IndexOption #10586 + * fix: upgrade to mpath v0.8.4 re: security issue #10683 + +5.13.8 / 2021-08-23 +=================== + * fix(populate): handle populating subdoc array virtual with sort #10552 + * fix(model): check for code instead of codeName when checking for existing collections for backwards compat with MongoDB 3.2 #10420 + * fix(index.d.ts): correct value of this for custom query helper methods #10545 + * fix(index.d.ts): allow strings for ObjectIds in nested properties #10573 + * fix(index.d.ts): add match to VirtualTypeOptions.options #8749 + * fix(index.d.ts): allow QueryOptions populate parameter type PopulateOptions #10587 [osmanakol](https://github.com/osmanakol) + * docs(api): add Document#$where to API docs #10583 + +5.13.7 / 2021-08-11 +=================== + * perf(index.d.ts): loosen up restrictions on ModelType generic for Schema for a ~50% perf improvement when compiling TypeScript and using intellisense #10536 #10515 #10349 + * fix(index.d.ts): fix broken `Schema#index()` types #10562 [JaredReisinger](https://github.com/JaredReisinger) + * fix(index.d.ts): allow using SchemaTypeOptions with array of raw document interfaces #10537 + * fix(index.d.ts): define IndexOptions in terms of mongodb.IndexOptions #10563 [JaredReisinger](https://github.com/JaredReisinger) + * fix(index.d.ts): improve intellisense for DocumentArray `push()` #10546 + * fix(index.d.ts): correct type for expires #10529 + * fix(index.d.ts): add Query#model property to ts bindings #10531 + * refactor(index.d.ts): make callbacks use the new Callback and CallbackWithoutResult types #10550 [thiagokisaki](https://github.com/thiagokisaki) + +5.13.6 / 2021-08-09 +=================== + * fix: upgrade mongodb driver -> 3.6.11 #10543 [maon-fp](https://github.com/maon-fp) + * fix(schema): throw more helpful error when defining a document array using a schema from a different copy of the Mongoose module #10453 + * fix: add explicit check on constructor property to avoid throwing an error when checking objects with null prototypes #10512 + * fix(cursor): make sure to clear stack every 1000 docs when calling `next()` to avoid stack overflow with large batch size #10449 + * fix(index.d.ts): improve types of Schema#(g|s)et #10555 [thiagokisaki](https://github.com/thiagokisaki) + * fix(index.d.ts): allow calling new Model(...) with generic Model param #10526 + * fix(index.d.ts): update type declarations of Schema.index method #10538 #10530 [Raader](https://github.com/Raader) + * fix(index.d.ts): add useNewUrlParser and useUnifiedTopology to ConnectOptions #10500 + * fix(index.d.ts): add missing type for diffIndexes #10547 [bvgusak](https://github.com/bvgusak) + * fix(index.d.ts): fixed incorrect type definition for Query's .map function #10544 [GCastilho](https://github.com/GCastilho) + * docs(schema): add more info and examples to Schema#indexes() docs #10446 + * chore: add types property to package.json #10557 [thiagokisaki](https://github.com/thiagokisaki) + +5.13.5 / 2021-07-30 +=================== + * perf(index.d.ts): improve typescript type checking performance #10515 [andreialecu](https://github.com/andreialecu) + * fix(index.d.ts): fix debug type in MongooseOptions #10510 [thiagokisaki](https://github.com/thiagokisaki) + * docs(api): clarify that `depopulate()` with no args depopulates all #10501 [gfrancz](https://github.com/gfrancz) + +5.13.4 / 2021-07-28 +=================== + * fix: avoid pulling non-schema paths from documents into nested paths #10449 + * fix(update): support overwriting nested map paths #10485 + * fix(update): apply timestamps to subdocs that would be newly created by `$setOnInsert` #10460 + * fix(map): correctly clone subdocs when calling toObject() on a map #10486 + * fix(cursor): cap parallel batchSize for populate at 5000 #10449 + * fix(index.d.ts): improve autocomplete for new Model() by making `doc` an object with correct keys #10475 + * fix(index.d.ts): add MongooseOptions interface #10471 [thiagokisaki](https://github.com/thiagokisaki) + * fix(index.d.ts): make LeanDocument work with PopulatedDoc #10494 + * docs(mongoose+connection): correct default value for bufferTimeoutMS #10476 + * chore: remove unnecessary 'eslint-disable' comments #10466 [thiagokisaki](https://github.com/thiagokisaki) + +5.13.3 / 2021-07-16 +=================== + * fix(model): avoid throwing error when bulkSave() called on a document with no changes #10437 + * fix(timestamps): apply timestamps when creating new subdocs with `$addToSet` and with positional operator #10447 + * fix(schema): allow calling Schema#loadClass() with class that has a static getter with no setter #10436 + * fix(model): handle re-applying object defaults after explicitly unsetting #10442 [semirturgay](https://github.com/semirturgay) + * fix: bump mongodb driver -> 3.6.10 #10440 [AbdelrahmanHafez](https://github.com/AbdelrahmanHafez) + * fix(index.d.ts): consistently use NativeDate instead of Date for Date validators and timestamps functions #10426 + * fix(index.d.ts): allow calling `discriminator()` with non-document #10452 #10421 [DouglasGabr](https://github.com/DouglasGabr) + * fix(index.d.ts): allow passing ResultType generic to Schema#path() #10435 + +5.13.2 / 2021-07-03 +=================== + * fix: hardcode @types/node version for now to avoid breaking changes from DefinitelyTyped/DefinitelyTyped#53669 #10415 + * fix(index.d.ts): allow using type: Date with Date paths in SchemaDefinitionType #10409 + * fix(index.d.ts): allow extra VirtualTypeOptions for better plugin support #10412 + * docs(api): add SchemaArray to docs #10397 + * docs(schema+validation): fix broken links #10396 + * docs(transactions): add note about creating a connection to transactions docs #10406 + +5.13.1 / 2021-07-02 +=================== + * fix(discriminator): allow using array as discriminator key in schema and as tied value #10303 + * fix(index.d.ts): allow using & Document in schema definition for required subdocument arrays #10370 + * fix(index.d.ts): if using DocType that doesn't extends Document, default to returning that DocType from `toObject()` and `toJSON()` #10345 + * fix(index.d.ts): use raw DocType instead of LeanDocument when using `lean()` with queries if raw DocType doesn't `extends Document` #10345 + * fix(index.d.ts): remove err: any in callbacks, use `err: CallbackError` instead #10340 + * fix(index.d.ts): allow defining map of schemas in TypeScript #10389 + * fix(index.d.ts): correct return type for Model.createCollection() #10359 + * docs(promises+discriminators): correctly escape () in regexp to pull in examples correctly #10364 + * docs(update): fix outdated URL about unindexed upsert #10406 [grimmer0125](https://github.com/grimmer0125) + * docs(index.d.ts): proper placement of mongoose.Date JSDoc [thiagokisaki](https://github.com/thiagokisaki) + +5.13.0 / 2021-06-28 +=================== + * feat(query): add sanitizeProjection option to opt in to automatically sanitizing untrusted query projections #10243 + * feat(model): add `bulkSave()` function that saves multiple docs in 1 `bulkWrite()` #9727 #9673 [AbdelrahmanHafez](https://github.com/AbdelrahmanHafez) + * feat(document): allow passing a list of virtuals or `pathsToSkip` to apply in `toObject()` and `toJSON()` #10120 + * fix(model): make Model.validate use object under validation as context by default #10360 [AbdelrahmanHafez](https://github.com/AbdelrahmanHafez) + * feat(document): add support for pathsToSkip in validate and validateSync #10375 [AbdelrahmanHafez](https://github.com/AbdelrahmanHafez) + * feat(model): add `diffIndexes()` function that calculates what indexes `syncIndexes()` will create/drop without actually executing any changes #10362 [IslandRhythms](https://github.com/IslandRhythms) + * feat(document): avoid using sessions that have ended, so you can use documents that were loaded in the session after calling `endSession()` #10306 + +5.12.15 / 2021-06-25 +==================== + * fix(index.d.ts): add extra TInstanceMethods generic param to `Schema` for cases when we can't infer from Model #10358 + * fix(index.d.ts): added typings for near() in model aggregation #10373 [tbhaxor](https://github.com/tbhaxor) + * fix(index.d.ts): correct function signature for `Query#cast()` #10388 [lkho](https://github.com/lkho) + * docs(transactions): add import statement #10365 [JimLynchCodes](https://github.com/JimLynchCodes) + * docs(schema): add missing `discriminatorKey` schema option #10386 #10376 [IslandRhythms](https://github.com/IslandRhythms) + * docs(index.d.ts): fix typo #10363 [houssemchebeb](https://github.com/houssemchebeb) + +5.12.14 / 2021-06-15 +==================== + * fix(schema): check that schema type is an object when setting isUnderneathDocArray #10361 [vmo-khanus](https://github.com/vmo-khanus) + * fix(document): avoid infinite recursion when setting single nested subdoc to array #10351 + * fix(populate): allow populating nested path in schema using `Model.populate()` #10335 + * fix(drivers): emit operation-start/operation-end events to allow inspecting when operations start and end + * fix(index.d.ts): improve typings for virtuals #10350 [thiagokisaki](https://github.com/thiagokisaki) + * fix(index.d.ts): correct constructor type for Document #10328 + * fix(index.d.ts): add `ValidationError` as a possible type for `ValidationError#errors` #10320 [IslandRhythms](https://github.com/IslandRhythms) + * fix: remove unnecessary async devDependency that's causing npm audit warnings #10281 + * docs(typescript): add schemas guide #10308 + * docs(model): add options parameter description to `Model.exists()` #10336 [Aminoiz](https://github.com/Aminoiz) + +5.12.13 / 2021-06-04 +==================== + * perf(document): avoid creating nested paths when running `$getAllSubdocs()` #10275 + * fix: make returnDocument option work with `findOneAndUpdate()` #10232 #10231 [cnwangjie](https://github.com/cnwangjie) + * fix(document): correctly reset subdocument when resetting a map subdocument underneath a single nested subdoc after save #10295 + * perf(query): avoid setting non-null sessions to avoid overhead from $getAllSubdocs() #10275 + * perf(document): pre split schematype paths when compiling schema to avoid extra overhead of splitting when hydrating documents #10275 + * perf(schema): pre-calculate mapPaths to avoid looping over every path for each path when initing doc #10275 + * fix(index.d.ts): drill down into nested arrays when creating LeanDocument type #10293 + +5.12.12 / 2021-05-28 +==================== + * fix(documentarray): retain atomics when setting to a new array #10272 + * fix(query+model): fix deprecation warning for `returnOriginal` with `findOneAndUpdate()` #10298 #10297 #10292 #10285 [IslandRhythms](https://github.com/IslandRhythms) + * fix(index.d.ts): make `map()` result an array if used over an array #10288 [quantumsheep](https://github.com/quantumsheep) + +5.12.11 / 2021-05-24 +==================== + * fix(populate): skip applying setters when casting arrays for populate() to avoid issues with arrays of immutable elements #10264 + * perf(schematype): avoid cloning setters every time we run setters #9588 + * perf(get): add benchmarks and extra cases to speed up get() #9588 + * perf(array): improve array constructor performance on small arrays to improve nested array perf #9588 + * fix(index.d.ts): allow using type: [String] with string[] when using SchemaDefinition with generic #10261 + * fix(index.d.ts): support ReadonlyArray as well as regular array where possible in schema definitions #10260 + * docs(connection): document noListener option to useDb #10278 [stuartpb](https://github.com/stuartpb) + * docs: migrate raw tutorial content from pug / JS to markdown #10271 + * docs: fix typo #10269 [sanjib](https://github.com/sanjib) + +5.12.10 / 2021-05-18 +==================== + * fix(query): allow setting `defaults` option on result documents from query options #7287 [IslandRhythms](https://github.com/IslandRhythms) + * fix(populate): handle populating embedded discriminator with custom tiedValue #10231 + * fix(document): allow passing space-delimited string of `pathsToValidate` to `validate()` and `validateSync()` #10258 + * fix(model+schema): support `loadClass()` on classes that have `collection` as a static property #10257 #10254 [IslandRhythms](https://github.com/IslandRhythms) + * fix(SchemaArrayOptions): correct property name #10236 + * fix(index.d.ts): add any to all query operators to minimize likelihood of "type instantiation is excessively deep" when querying docs with 4-level deep subdocs #10189 + * fix(index.d.ts): add $parent() in addition to parent() in TS definitions + * fix(index.d.ts): correct async iterator return type for QueryCursor #10253 #10252 #10251 [borfig](https://github.com/borfig) + * fix(index.d.ts): add `virtualsOnly` parameter to `loadClass()` function signature [IslandRhythms](https://github.com/IslandRhythms) + * docs(typescript): add typescript populate docs #10212 + * docs: switch from AWS to Azure Functions for search #10244 + +5.12.9 / 2021-05-13 +=================== + * fix(schema): ensure add() overwrites existing schema paths by default #10208 #10203 + * fix(schema): support creating nested paths underneath document arrays #10193 + * fix(update): convert nested dotted paths in update to nested paths to avoid ending up with dotted properties in update #10200 + * fix(document): allow calling validate() and validateSync() with `options` as first parameter #10216 + * fix(schema): apply static properties to model when using loadClass() #10206 + * fix(index.d.ts): allow returning Promise from middleware functions #10229 + * fix(index.d.ts): add pre('distinct') hooks to TypeScript #10192 + +5.12.8 / 2021-05-10 +=================== + * fix(populate): handle populating immutable array paths #10159 + * fix(CastError): add `toJSON()` function to ensure `name` property always ends up in `JSON.stringify()` output #10166 [IslandRhythms](https://github.com/IslandRhythms) + * fix(query): add allowDiskUse() method to improve setting MongoDB 4.4's new `allowDiskUse` option #10177 + * fix(populate): allow populating paths under mixed schematypes where some documents have non-object properties #10191 + * chore: remove unnecessary driver dynamic imports so Mongoose can work with Parcel #9603 + * fix(index.d.ts): allow any object as parameter to create() and `insertMany()` #10144 + * fix(index.d.ts): allow creating Model class with raw interface, no `extends Document` #10144 + * fix(index.d.ts): separate UpdateQuery from `UpdateWithAggregationPipeline` for cases when `UpdateQuery` is used as a function param #10186 + * fix(index.d.ts): don't require error value in pre/post hooks #10213 [michaln-q](https://github.com/michaln-q) + * docs(typescript): add a typescript intro tutorial and statics tutorial #10021 + * docs(typescript): add query helpers tutorial #10021 + * docs(deprecations): add note that you can safely ignore `useFindAndModify` and `useCreateIndex` deprecation warnings #10155 + * chore(workflows): add node 16 to github actions #10201 [AbdelrahmanHafez](https://github.com/AbdelrahmanHafez) + +5.12.7 / 2021-04-29 +=================== + * fix(document): make $getPopulatedDocs() return populated virtuals #10148 + * fix(discriminator): take discriminator schema's single nested paths over base schema's #10157 + * fix(discriminator): allow numbers and ObjectIds as tied values for discriminators #10130 + * fix(document): avoid double validating paths underneath mixed objects in save() #10141 + * fix(schema): allow path() to return single nested paths within document arrays #10164 + * fix(model+query): consistently wrap query callbacks in `process.nextTick()` to avoid clean stack traces causing memory leak when using synchronous recursion like `async.whilst()` #9864 + * fix(cursor): correctly report CastError when using noCursorTimeout flag #10150 + * fix(index.d.ts): add CastError constructor #10176 + * fix(index.d.ts): allow setting mongoose.pluralize(null) in TypeScript #10185 + * docs: add link to transactions guide from nav bar #10143 + * docs(validation): add section about custom error messages #10140 + * docs: make headers linkable via clicking #10156 + * docs: broken link in document.js #10190 [joostdecock](https://github.com/joostdecock) + * docs: make navbar responsive on legacy 2.x docs #10171 [ad99526](https://github.com/ad99526) + +5.12.6 / 2021-04-27 +=================== + * fix(query): allow setting `writeConcern` schema option to work around MongoDB driver's `writeConcern` deprecation warning #10083 #10009 [IslandRhythms](https://github.com/IslandRhythms) + * fix(populate): dedupe when virtual populate foreignField is an array to avoid duplicate docs in result #10117 + * fix(populate): add `localField` filter to `$elemMatch` on virtual populate when custom `match` has a `$elemMatch` and `foreignField` is an array #10117 + * fix(query): convert projection string values to numbers as a workaround for #10142 + * fix(document): set version key filter on `save()` when using `optimisticConcurrency` if no changes in document #10128 [IslandRhythms](https://github.com/IslandRhythms) + * fix(model): use `obj` as `context` in `Model.validate()` if `obj` is a document #10132 + * fix(connection): avoid db events deprecation warning when using `useDb()` with `useUnifiedTopology` #8267 + * fix: upgrade to sift@13.5.2 to work around transitive dev dependency security warning #10121 + * fix(index.d.ts): allow any object as parameter to `create()` and `insertMany()` #10144 + * fix(index.d.ts): clarify that `eachAsync()` callback receives a single doc rather than array of docs unless `batchSize` is set #10135 + * fix(index.d.ts): clarify that return value from `validateSync()` is a ValidationError #10147 [michaln-q](https://github.com/michaln-q) + * fix(index.d.ts): add generic type for Model constructor #10074 [Duchynko](https://github.com/Duchynko) + * fix(index.d.ts): add parameter type in merge #10168 [yoonhoGo](https://github.com/yoonhoGo) + +5.12.5 / 2021-04-19 +=================== + * fix(populate): handle populating underneath document array when document array property doesn't exist in db #10003 + * fix(populate): clear out dangling pointers to populated docs so query cursor with populate() can garbage collect populated subdocs #9864 + * fix(connection): pull correct `autoCreate` value from Mongoose global when creating new model before calling `connect()` #10091 + * fix(populate): handle populating paths on documents with discriminator keys that point to non-existent discriminators #10082 + * fix(index.d.ts): allow numbers as discriminator names #10115 + * fix(index.d.ts): allow `type: Boolean` in Schema definitions #10085 + * fix(index.d.ts): allow passing array of aggregation pipeline stages to `updateOne()` and `updateMany()` #10095 + * fix(index.d.ts): support legacy 2nd param callback syntax for `deleteOne()`, `deleteMany()` #10122 + * docs(mongoose): make `useCreateIndex` always `false` in docs #10033 + * docs(schema): fix incorrect links from schema API docs #10111 + +5.12.4 / 2021-04-15 +=================== + * fix: upgrade mongodb driver -> 3.6.6 #10079 + * fix: store fields set with select:false at schema-level when saving a new document #10101 [ptantiku](https://github.com/ptantiku) + * fix(populate): avoid turning already populated field to null when populating an existing lean document #10068 [IslandRhythms](https://github.com/IslandRhythms) + * fix(populate): correctly populate lean subdocs with `_id` property #10069 + * fix(model): insertedDocs may contain docs that weren't inserted #10098 [olnazx](https://github.com/olnazx) + * fix(schemaType): make type Mixed cast error objects to pojos #10131 [AbdelrahmanHafez](https://github.com/AbdelrahmanHafez) + * fix(populate): support populating embedded discriminators in nested arrays #9984 + * fix(populate): handle populating map paths using trailing `.$*` #10123 + * fix(populate): allow returning primitive from `transform()` function for single conventional populate #10064 + * fix(index.d.ts): allow generic classes of `T` to use `T & Document` internally #10046 + * fix(index.d.ts): allow `$pull` with `$` paths #10075 + * fix(index.d.ts): use correct `Date` type for `$currentDate` #10058 + * fix(index.d.ts): add missing asyncInterator to Query type def #10094 [borfig](https://github.com/borfig) + * fix(index.d.ts): allow RHS of `$unset` properties to be any value #10066 + * fix(index.d.ts): allow setting SchemaType `index` property to a string #10077 + * refactor(index.d.ts): move discriminator() to common interface #10109 [LoneRifle](https://github.com/LoneRifle) + +5.12.3 / 2021-03-31 +=================== + * fix: avoid setting schema-level collation on text indexes #10044 [IslandRhythms](https://github.com/IslandRhythms) + * fix(query): add `writeConcern()` method to avoid writeConcern deprecation warning #10009 + * fix(connection): use queueing instead of event emitter for `createCollection()` and other helpers to avoid event emitter warning #9778 + * fix(connection): scope `Connection#id` to Mongoose instance so id always lines up with `mongoose.connections` index #10025 [IslandRhythms](https://github.com/IslandRhythms) + * fix: avoid throwing in `promiseOrCallback()` if 3rd param isn't an EventEmitter #10055 [emrebass](https://github.com/emrebass) + * fix(index.d.ts): add Model as 2nd generic param to `Model.discriminator()` #10054 [coro101](https://github.com/coro101) + * fix(index.d.ts): add docs to `next()` callback for `pre('insertMany')` hooks #10078 #10072 [pezzu](https://github.com/pezzu) + * fix(index.d.ts): add `transform` to PopulateOptions interface #10061 + * fix(index.d.ts): add DocumentQuery type for backwards compatibility #10036 + +5.12.2 / 2021-03-22 +=================== + * fix(QueryCursor): consistently execute `post('find')` hooks with an array of docs #10015 #9982 [IslandRhythms](https://github.com/IslandRhythms) + * fix(schema): support setting `ref` as an option on an array SchemaType #10029 + * fix(query): apply schema-level `select` option from array schematypes #10029 + * fix(schema): avoid possible prototype pollution with `Schema()` constructor #10035 [zpbrent](https://github.com/zpbrent) + * fix(model): make bulkWrite skip timestamps with timestamps: false #10050 [SoftwareSing](https://github.com/SoftwareSing) + * fix(index.d.ts): make query methods return `QueryWithHelpers` so query helpers pass through chaining #10040 + * fix(index.d.ts): add `upserted` array to `updateOne()`, `updateMany()`, `update()` result #10042 + * fix(index.d.ts): add back `Aggregate#project()` types that were mistakenly removed in 5.12.0 #10043 + * fix(index.d.ts): always allow setting `type` in Schema to a SchemaType class or a Schema instance #10030 + * docs(transactions): introduce `session.withTransaction()` before `session.startTransaction()` because `withTransaction()` is the recommended approach #10008 + * docs(mongoose+browser): fix broken links to info about `mongoose.Types` #10016 + +5.12.1 / 2021-03-18 +=================== + * fix: update mongodb -> 3.6.5 to fix circular dependency warning #9900 + * fix(document): make `toObject()` use child schema `flattenMaps` option by default #9995 + * fix(ObjectId): make `isValidObjectId()` check that length 24 strings are hex chars only #10010 #9996 [IslandRhythms](https://github.com/IslandRhythms) + * fix(query): correctly cast embedded discriminator paths when discriminator key is specified in array filter #9977 + * fix(schema): skip `populated()` check when calling `applyGetters()` with a POJO for mongoose-lean-getters support #9986 + * fix(populate): support populating dotted subpath of a populated doc that has the same id as a populated doc #10005 + * fix(index.d.ts): correct `this` for query helpers #10028 [francescov1](https://github.com/francescov1) + * fix(index.d.ts): avoid omitting function property keys in LeanDocuments, because TS can't accurately infer what's a function if using generic functions #9989 + * fix(index.d.ts): correct type definition for `SchemaType#cast()` #10039 #9980 + * fix(index.d.ts): make SchemaTypeOptions a class, add missing `SchemaType#OptionsConstructor` #10001 + * fix(index.d.ts): support calling `findByIdAndUpdate()` with filter, update, callback params #9981 + +5.12.0 / 2021-03-11 +=================== + * feat(populate): add `transform` option that Mongoose will call on every populated doc #3775 + * feat(query): make `Query#pre()` and `Query#post()` public #9784 + * feat(document): add `Document#getPopulatedDocs()` to return an array of all populated documents in a document #9702 [IslandRhythms](https://github.com/IslandRhythms) + * feat(document): add `Document#getAllSubdocs()` to return an array of all single nested and array subdocuments #9764 [IslandRhythms](https://github.com/IslandRhythms) + * feat(schema): allow `schema` as a schema path name #8798 [IslandRhythms](https://github.com/IslandRhythms) + * feat(QueryCursor): Add batch processing for eachAsync #9902 [khaledosama999](https://github.com/khaledosama999) + * feat(connection): add `noListener` option to help with use cases where you're using `useDb()` on every request #9961 + * feat(index): emit 'createConnection' event when user calls `mongoose.createConnection()` #9985 + * feat(connection+index): emit 'model' and 'deleteModel' events on connections when creating and deleting models #9983 + * feat(query): allow passing `explain` option to `Model.exists()` #8098 [IslandRhythms](https://github.com/IslandRhythms) + +5.11.20 / 2021-03-11 +==================== + * fix(query+populate): avoid unnecessarily projecting in subpath when populating a path that uses an elemMatch projection #9973 + * fix(connection): avoid `db` events deprecation warning with 'close' events #10004 #9930 + * fix(index.d.ts): make `$pull` more permissive to allow dotted paths #9993 + +5.11.19 / 2021-03-05 +==================== + * fix(document): skip validating array elements that aren't modified when `validateModifiedOnly` is set #9963 + * fix(timestamps): apply timestamps on `findOneAndReplace()` #9951 + * fix(schema): correctly handle trailing array filters when looking up schema paths #9977 + * fix(schema): load child class getter for virtuals instead of base class when using `loadClass()` #9975 + * fix(index.d.ts): allow creating statics without passing generics to `Schema` constructor #9969 + * fix(index.d.ts): add QueryHelpers generic to schema and model, make all query methods instead return QueryWithHelpers #9850 + * fix(index.d.ts): support setting `type` to an array of schemas when using SchemaDefinitionType #9962 + * fix(index.d.ts): add generic to plugin schema definition #9968 [emiljanitzek](https://github.com/emiljanitzek) + * docs: small typo fix #9964 [KrishnaMoorthy12](https://github.com/KrishnaMoorthy12) + +5.11.18 / 2021-02-23 +==================== + * fix(connection): set connection state to `disconnected` if connecting string failed to parse #9921 + * fix(connection): remove `db` events deprecation warning if `useUnifiedTopology = true` #9930 + * fix(connection): fix promise chaining for openUri #9960 [lantw44](https://github.com/lantw44) + * fix(index.d.ts): add `PopulatedDoc` type to make it easier to define populated docs in interfaces #9818 + * fix(index.d.ts): allow explicitly overwriting `toObject()` return type for backwards compatibility #9944 + * fix(index.d.ts): correctly throw error when interface path type doesn't line up with schema path type #9958 [ShadiestGoat](https://github.com/ShadiestGoat) + * fix(index.d.ts): remove `any` from `deleteX()` and `updateX()` query params and return values #9959 [btd](https://github.com/btd) + * fix(index.d.ts): add non-generic versions of `Model.create()` for better autocomplete #9928 + * docs: correctly handle multiple `>` in API descriptions #9940 + +5.11.17 / 2021-02-17 +==================== + * fix(populate): handle `perDocumentLimit` when multiple documents reference the same populated doc #9906 + * fix(document): handle directly setting embedded document array element with projection #9909 + * fix(map): cast ObjectId to string inside of MongooseMap #9938 [HunterKohler](https://github.com/HunterKohler) + * fix(model): use schema-level default collation for indexes if index doesn't have collation #9912 + * fix(index.d.ts): make `SchemaTypeOptions#type` optional again to allow alternative typeKeys #9927 + * fix(index.d.ts): support `{ type: String }` in schema definition when using SchemaDefinitionType generic #9911 + * docs(populate+schematypes): document the `$*` syntax for populating every entry in a map #9907 + * docs(connection): clarify that `Connection#transaction()` promise resolves to a command result #9919 + +5.11.16 / 2021-02-12 +==================== + * fix(document): skip applying array element setters when init-ing an array #9889 + * fix: upgrade to mongodb driver 3.6.4 #9893 [jooeycheng](https://github.com/jooeycheng) + * fix: avoid copying Object.prototype properties when cloning #9876 + * fix(aggregate): automatically convert functions to strings when using `$function` operator #9897 + * fix: call pre-remove hooks for subdocuments #9895 #9885 [IslandRhythms](https://github.com/IslandRhythms) + * docs: fix confusing sentence in Schema docs #9914 [namenyi](https://github.com/namenyi) + +5.11.15 / 2021-02-03 +==================== + * fix(document): fix issues with `isSelected` as an path in a nested schema #9884 #9873 [IslandRhythms](https://github.com/IslandRhythms) + * fix(index.d.ts): better support for `SchemaDefinition` generics when creating schema #9863 #9862 #9789 + * fix(index.d.ts): allow required function in array definition #9888 [Ugzuzg](https://github.com/Ugzuzg) + * fix(index.d.ts): reorder create typings to allow array desctructuring #9890 [Ugzuzg](https://github.com/Ugzuzg) + * fix(index.d.ts): add missing overload to Model.validate #9878 #9877 [jonamat](https://github.com/jonamat) + * fix(index.d.ts): throw compiler error if schema says path is a String, but interface says path is a number #9857 + * fix(index.d.ts): make `Query` a class, allow calling `Query#where()` with object argument and with no arguments #9856 + * fix(index.d.ts): support calling `Schema#pre()` and `Schema#post()` with options and array of hooked function names #9844 + * docs(faq): mention other debug options than console #9887 [dandv](https://github.com/dandv) + * docs(connections): clarify that Mongoose can emit 'error' both during initial connection and after initial connection #9853 + +5.11.14 / 2021-01-28 +==================== + * fix(populate): avoid inferring `justOne` from parent when populating a POJO with a manually populated path #9833 [IslandRhythms](https://github.com/IslandRhythms) + * fix(document): apply setters on each element of the array when setting a populated array #9838 + * fix(map): handle change tracking on maps of subdocs #9811 [IslandRhythms](https://github.com/IslandRhythms) + * fix(document): remove dependency on `documentIsSelected` symbol #9841 [IslandRhythms](https://github.com/IslandRhythms) + * fix(error): make ValidationError.toJSON to include the error name correctly #9849 [hanzki](https://github.com/hanzki) + * fix(index.d.ts): indicate that `Document#remove()` returns a promise, not a query #9826 + * fix(index.d.ts): allow setting `SchemaType#enum` to TypeScript enum with `required: true` #9546 + +5.11.13 / 2021-01-20 +==================== + * fix(map): handle change tracking on map of arrays #9813 + * fix(connection): allow passing options to `Connection#transaction()` #9834 [pnutmath](https://github.com/pnutmath) + * fix(index.d.ts): make `Query#options#rawResult` take precedence over `new`+`upsert` #9816 + * fix(index.d.ts): changed setOptions's 'overwrite' argument to optional #9824 [pierissimo](https://github.com/pierissimo) + * fix(index.d.ts): allow setting `mongoose.Promise` #9820 + * fix(index.d.ts): add `Aggregate#replaceRoot()` #9814 + * fix(index.d.ts): make `Model.create()` with a spread return a promise of array rather than single doc #9817 + * fix(index.d.ts): use SchemaDefinitionProperty generic for SchemaTypeOptions if specified #9815 + * docs(populate): add note about setting `toObject` for populate virtuals #9822 + +5.11.12 / 2021-01-14 +==================== + * fix(document): handle using `db` as a document path #9798 + * fix(collection): make sure to call `onOpen()` if `autoCreate === false` #9807 + * fix(index.d.ts): correct query type for `findOneAndUpdate()` and `findByIdAndUpdate()` with `rawResult = true` #9803 + * fix(index.d.ts): require setting `new: true` or `returnOriginal: false` to skip null check with `findOneAndUpdate()` #9654 + * fix(index.d.ts): make methods and statics optional on schema #9801 + * fix(index.d.ts): remove non backwards compatible methods restriction #9801 + * docs: removed the extra word on comment doc #9794 [HenriqueLBorges](https://github.com/HenriqueLBorges) + 5.11.11 / 2021-01-08 ==================== * fix(model): support calling `create()` with `undefined` as first argument and no callback #9765 diff --git a/LICENSE.md b/LICENSE.md index 14cdf56d4cf..54b4a4c6c16 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,7 @@ # MIT License -Copyright (c) 2010 LearnBoost +Copyright (c) 2010-2013 LearnBoost +Copyright (c) 2013-2021 Automattic Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 28333cd67e2..aaff1ac2d00 100644 --- a/Makefile +++ b/Makefile @@ -41,10 +41,16 @@ copytmp: mkdir -p ./tmp/docs/css mkdir -p ./tmp/docs/js mkdir -p ./tmp/docs/images + mkdir -p ./tmp/docs/api + mkdir -p ./tmp/docs/tutorials + mkdir -p ./tmp/docs/typescript cp -R ./docs/*.html ./tmp/docs cp -R ./docs/css/*.css ./tmp/docs/css cp -R ./docs/js/*.js ./tmp/docs/js cp -R ./docs/images/* ./tmp/docs/images + cp -R ./docs/api/* ./tmp/docs/api + cp -R ./docs/tutorials/* ./tmp/docs/tutorials + cp -R ./docs/typescript/* ./tmp/docs/typescript cp index.html ./tmp gitreset: diff --git a/benchmarks/get.js b/benchmarks/get.js new file mode 100644 index 00000000000..a51d4ae65ae --- /dev/null +++ b/benchmarks/get.js @@ -0,0 +1,40 @@ +'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 diff --git a/docs/2.7.x/docs/model-definition.html b/docs/2.7.x/docs/model-definition.html index db649835530..17a4ff6c4c7 100644 --- a/docs/2.7.x/docs/model-definition.html +++ b/docs/2.7.x/docs/model-definition.html @@ -8,7 +8,147 @@ padding: 0 30px 30px; margin-top: 0; } + #model-definition-navToggle + { + display: block; + position: relative; + top: 50px; + left: 50px; + + z-index: 1; + + -webkit-user-select: none; + user-select: none; + } + + #model-definition-navToggle a + { + text-decoration: none; + color: #232323; + + transition: color 0.3s ease; + } + + #model-definition-navToggle a:hover + { + color: tomato; + } + + + #model-definition-navToggle input + { + display: block; + width: 40px; + height: 32px; + position: absolute; + top: -7px; + left: -5px; + + cursor: pointer; + + opacity: 0; /* hide this */ + z-index: 2; /* and place it over the hamburger */ + + -webkit-touch-callout: none; + } + + /* + * Just a quick hamburger + */ + #model-definition-navToggle span + { + display: block; + width: 33px; + height: 4px; + margin-bottom: 5px; + position: relative; + + background: #cdcdcd; + border-radius: 3px; + z-index: 1; + + transform-origin: 4px 0px; + + transition: transform 0.5s cubic-bezier(0.77,0.2,0.05,1.0), + background 0.5s cubic-bezier(0.77,0.2,0.05,1.0), + opacity 0.55s ease; + } + + #model-definition-navToggle span:first-child + { + transform-origin: 0% 0%; + } + + #model-definition-navToggle span:nth-last-child(2) + { + transform-origin: 0% 100%; + } + + /* + * Transform all the slices of hamburger + * into a crossmark. + */ + #model-definition-navToggle input:checked ~ span + { + opacity: 1; + transform: rotate(45deg) translate(-2px, -1px); + background: #232323; + } + + /* + * But let's hide the middle one. + */ + #model-definition-navToggle input:checked ~ span:nth-last-child(3) + { + opacity: 0; + transform: rotate(0deg) scale(0.2, 0.2); + } + + /* + * Ohyeah and the last one should go the other direction + */ + #model-definition-navToggle input:checked ~ span:nth-last-child(2) + { + transform: rotate(-45deg) translate(0, -1px); + } + + /* + * Make this absolute positioned + * at the top left of the screen + */ + .model-definition-nav + { + position: absolute; + width: 300px; + margin: -100px 0 0 -50px; + padding: 50px; + padding-top: 125px; + + background: #ededed; + list-style-type: none; + -webkit-font-smoothing: antialiased; + /* to stop flickering of text in safari */ + + transform-origin: 0% 0%; + transform: translate(-100%, 0); + + transition: transform 0.5s cubic-bezier(0.77,0.2,0.05,1.0); + } + + .model-definition-nav li + { + padding: 10px 0; + font-size: 22px; + } + + /* + * And let's slide it in from the left + */ + #model-definition-navToggle input:checked ~ ul + { + transform: none; + } #wrap { background: url('/docs/2.7.x/images/pattern.png') no-repeat -134px -211px; min-height: 600px; @@ -178,29 +318,37 @@

Mongoose

diff --git a/docs/advanced_schemas.html b/docs/advanced_schemas.html deleted file mode 100644 index 0407e74c155..00000000000 --- a/docs/advanced_schemas.html +++ /dev/null @@ -1,101 +0,0 @@ -Mongoose v5.6.0: Advanced Schemas

Creating from ES6 Classes Using loadClass()

Mongoose allows creating schemas from ES6 classes. -The loadClass() function lets you pull in methods, -statics, and virtuals from an ES6 class. A class method maps to a schema -method, a static method maps to a schema static, and getters/setters map -to virtuals.

- -
const schema = new Schema({ firstName: String, lastName: String });
-
-class PersonClass {
-  // `fullName` becomes a virtual
-  get fullName() {
-    return `${this.firstName} ${this.lastName}`;
-  }
-
-  set fullName(v) {
-    const firstSpace = v.indexOf(' ');
-    this.firstName = v.split(' ')[0];
-    this.lastName = firstSpace === -1 ? '' : v.substr(firstSpace + 1);
-  }
-
-  // `getFullName()` becomes a document method
-  getFullName() {
-    return `${this.firstName} ${this.lastName}`;
-  }
-
-  // `findByFullName()` becomes a static
-  static findByFullName(name) {
-    const firstSpace = name.indexOf(' ');
-    const firstName = name.split(' ')[0];
-    const lastName = firstSpace === -1 ? '' : name.substr(firstSpace + 1);
-    return this.findOne({ firstName, lastName });
-  }
-}
-
-schema.loadClass(PersonClass);
-var Person = db.model('Person', schema);
-
-Person.create({ firstName: 'Jon', lastName: 'Snow' }).
-  then(doc => {
-    assert.equal(doc.fullName, 'Jon Snow');
-    doc.fullName = 'Jon Stark';
-    assert.equal(doc.firstName, 'Jon');
-    assert.equal(doc.lastName, 'Stark');
-    return Person.findByFullName('Jon Snow');
-  }).
-  then(doc => {
-    assert.equal(doc.fullName, 'Jon Snow');
-  });
-
\ No newline at end of file diff --git a/docs/advanced_schemas.md b/docs/advanced_schemas.md new file mode 100644 index 00000000000..c1f6207b131 --- /dev/null +++ b/docs/advanced_schemas.md @@ -0,0 +1,13 @@ +## Advanced Schemas + +### Creating from ES6 Classes Using `loadClass()` + +Mongoose allows creating schemas from [ES6 classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). +The `loadClass()` function lets you pull in methods, +statics, and virtuals from an ES6 class. A class method maps to a schema +method, a static method maps to a schema static, and getters/setters map +to virtuals. + +```javascript +[require:Creating from ES6 Classes Using `loadClass\(\)`] +``` \ No newline at end of file diff --git a/docs/api.html b/docs/api.html deleted file mode 100644 index c1050e23c3a..00000000000 --- a/docs/api.html +++ /dev/null @@ -1,5061 +0,0 @@ -Mongoose v5.6.0: API docs

API Docs


Mongoose


Mongoose()

Mongoose constructor.

- -

The exports object of the mongoose module is an instance of this class. Most apps will only use this one instance.


Mongoose.prototype.Aggregate()

The Mongoose Aggregate constructor


Mongoose.prototype.CastError()

Parameters
  • type -«String» The name of the type
  • value -«Any» The value that failed to cast
  • path -«String» The path a.b.c in the doc where this cast error occurred
  • [reason] -«Error» The original error that was thrown

The Mongoose CastError constructor


Mongoose.prototype.Collection()

The Mongoose Collection constructor


Mongoose.prototype.Connection()

The Mongoose Connection constructor


Mongoose.prototype.Decimal128

Type:
  • «property»

The Mongoose Decimal128 SchemaType. Used for declaring paths in your schema that should be 128-bit decimal floating points. Do not use this to create a new Decimal128 instance, use mongoose.Types.Decimal128 instead.

- -

Example:

- -
const vehicleSchema = new Schema({ fuelLevel: mongoose.Decimal128 });

Mongoose.prototype.Document()

The Mongoose Document constructor.


Mongoose.prototype.DocumentProvider()

The Mongoose DocumentProvider constructor. Mongoose users should not have to use this directly


Mongoose.prototype.Error()

The MongooseError constructor.


Mongoose.prototype.Mixed

Type:
  • «property»

The Mongoose Mixed SchemaType. Used for declaring paths in your schema that Mongoose's change tracking, casting, and validation should ignore.

- -

Example:

- -
const schema = new Schema({ arbitrary: mongoose.Mixed });

Mongoose.prototype.Model()

The Mongoose Model constructor.


Mongoose.prototype.Mongoose()

The Mongoose constructor

- -

The exports of the mongoose module is an instance of this class.

- -

Example:

- -
var mongoose = require('mongoose');
-var mongoose2 = new mongoose.Mongoose();

Mongoose.prototype.Number

Type:
  • «property»

The Mongoose Number SchemaType. Used for declaring paths in your schema that Mongoose should cast to numbers.

- -

Example:

- -
const schema = new Schema({ num: mongoose.Number });
-// Equivalent to:
-const schema = new Schema({ num: 'number' });

Mongoose.prototype.ObjectId

Type:
  • «property»

The Mongoose ObjectId SchemaType. Used for declaring paths in your schema that should be MongoDB ObjectIds. Do not use this to create a new ObjectId instance, use mongoose.Types.ObjectId instead.

- -

Example:

- -
const childSchema = new Schema({ parentId: mongoose.ObjectId });

Mongoose.prototype.Promise

Type:
  • «property»

The Mongoose Promise constructor.


Mongoose.prototype.PromiseProvider()

Storage layer for mongoose promises


Mongoose.prototype.Query()

The Mongoose Query constructor.


Mongoose.prototype.STATES

Type:
  • «property»

Expose connection states for user-land


Mongoose.prototype.Schema()

The Mongoose Schema constructor

- -

Example:

- -
var mongoose = require('mongoose');
-var Schema = mongoose.Schema;
-var CatSchema = new Schema(..);

Mongoose.prototype.SchemaType()

The Mongoose SchemaType constructor


Mongoose.prototype.SchemaTypes

Type:
  • «property»

The various Mongoose SchemaTypes.

- -

Note:

- -

Alias of mongoose.Schema.Types for backwards compatibility.


Mongoose.prototype.Types

Type:
  • «property»

The various Mongoose Types.

- -

Example:

- -
var mongoose = require('mongoose');
-var array = mongoose.Types.Array;
- -

Types:

- - - -

Using this exposed access to the ObjectId type, we can construct ids on demand.

- -
var ObjectId = mongoose.Types.ObjectId;
-var id1 = new ObjectId;

Mongoose.prototype.VirtualType()

The Mongoose VirtualType constructor


Mongoose.prototype.connect()

Parameters
  • uri(s) -«String»
  • [options] -«Object» passed down to the MongoDB driver's connect() function, except for 4 mongoose-specific options explained below.
    • [options.dbName] -«String» The name of the database we want to use. If not provided, use database name from connection string.
    • [options.user] -«String» username for authentication, equivalent to options.auth.user. Maintained for backwards compatibility.
    • [options.pass] -«String» password for authentication, equivalent to options.auth.password. Maintained for backwards compatibility.
    • [options.autoIndex=true] -«Boolean» Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection.
    • [options.bufferCommands=true] -«Boolean» Mongoose specific option. Set to false to disable buffering on all models associated with this connection.
    • [options.useFindAndModify=true] -«Boolean» True by default. Set to false to make findOneAndUpdate() and findOneAndRemove() use native findOneAndUpdate() rather than findAndModify().
    • [options.useNewUrlParser=false] -«Boolean» False by default. Set to true to make all connections set the useNewUrlParser option by default.
  • [callback] -«Function»
Returns:
  • «Promise» resolves to this if connection succeeded

Opens the default mongoose connection.

- -

Example:

- -
mongoose.connect('mongodb://user:pass@localhost:port/database');
-
-// replica sets
-var uri = 'mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/mydatabase';
-mongoose.connect(uri);
-
-// with options
-mongoose.connect(uri, options);
-
-// optional callback that gets fired when initial connection completed
-var uri = 'mongodb://nonexistent.domain:27000';
-mongoose.connect(uri, function(error) {
-  // if error is truthy, the initial connection failed.
-})

Mongoose.prototype.connection

Type:
  • «Connection»

The Mongoose module's default connection. Equivalent to mongoose.connections][0], see connections.

- -

Example:

- -
var mongoose = require('mongoose');
-mongoose.connect(...);
-mongoose.connection.on('error', cb);
- -

This is the connection used by default for every model created using mongoose.model.

- -

To create a new connection, use createConnection().


Mongoose.prototype.connections

Type:
  • «Array»

An array containing all connections associated with this Mongoose instance. By default, there is 1 connection. Calling createConnection() adds a connection to this array.

- -

Example:

- -
const mongoose = require('mongoose');
-mongoose.connections.length; // 1, just the default connection
-mongoose.connections[0] === mongoose.connection; // true
-
-mongoose.createConnection('mongodb://localhost:27017/test');
-mongoose.connections.length; // 2

Mongoose.prototype.createConnection()

Parameters
  • [uri] -«String» a mongodb:// URI
  • [options] -«Object» passed down to the MongoDB driver's connect() function, except for 4 mongoose-specific options explained below.
    • [options.user] -«String» username for authentication, equivalent to options.auth.user. Maintained for backwards compatibility.
    • [options.pass] -«String» password for authentication, equivalent to options.auth.password. Maintained for backwards compatibility.
    • [options.autoIndex=true] -«Boolean» Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection.
    • [options.bufferCommands=true] -«Boolean» Mongoose specific option. Set to false to disable buffering on all models associated with this connection.
Returns:
  • «Connection» the created Connection object. Connections are thenable, so you can do await mongoose.createConnection()

Creates a Connection instance.

- -

Each connection instance maps to a single database. This method is helpful when mangaging multiple db connections.

- -

Options passed take precedence over options included in connection strings.

- -

Example:

- -
// with mongodb:// URI
-db = mongoose.createConnection('mongodb://user:pass@localhost:port/database');
-
-// and options
-var opts = { db: { native_parser: true }}
-db = mongoose.createConnection('mongodb://user:pass@localhost:port/database', opts);
-
-// replica sets
-db = mongoose.createConnection('mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/database');
-
-// and options
-var opts = { replset: { strategy: 'ping', rs_name: 'testSet' }}
-db = mongoose.createConnection('mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/database', opts);
-
-// and options
-var opts = { server: { auto_reconnect: false }, user: 'username', pass: 'mypassword' }
-db = mongoose.createConnection('localhost', 'database', port, opts)
-
-// initialize now, connect later
-db = mongoose.createConnection();
-db.openUri('localhost', 'database', port, [opts]);

Mongoose.prototype.deleteModel()

Parameters
  • name -«String|RegExp» if string, the name of the model to remove. If regexp, removes all models whose name matches the regexp.
Returns:
  • «Mongoose» this

Removes the model named name from the default connection, if it exists. You can use this function to clean up any models you created in your tests to prevent OverwriteModelErrors.

- -

Equivalent to mongoose.connection.deleteModel(name).

- -

Example:

- -
mongoose.model('User', new Schema({ name: String }));
-console.log(mongoose.model('User')); // Model object
-mongoose.deleteModel('User');
-console.log(mongoose.model('User')); // undefined
-
-// Usually useful in a Mocha `afterEach()` hook
-afterEach(function() {
-  mongoose.deleteModel(/.+/); // Delete every model
-});

Mongoose.prototype.disconnect()

Parameters
  • [callback] -«Function» called after all connection close, or when first error occurred.
Returns:
  • «Promise» resolves when all connections are closed, or rejects with the first error that occurred.

Runs .close() on all connections in parallel.


Mongoose.prototype.driver

Type:
  • «property»

The underlying driver this Mongoose instance uses to communicate with the database. A driver is a Mongoose-specific interface that defines functions like find().


Mongoose.prototype.get()

Parameters
  • key -«String»

Gets mongoose options

- -

Example:

- -
mongoose.get('test') // returns the 'test' value

Mongoose.prototype.model()

Parameters
  • name -«String|Function» model name or class extending Model
  • [schema] -«Schema» the schema to use.
  • [collection] -«String» name (optional, inferred from model name)
  • [skipInit] -«Boolean» whether to skip initialization (defaults to false)
Returns:
  • «Model» The model associated with name. Mongoose will create the model if it doesn't already exist.

Defines a model or retrieves it.

- -

Models defined on the mongoose instance are available to all connection created by the same mongoose instance.

- -

If you call mongoose.model() with twice the same name but a different schema, you will get an OverwriteModelError. If you call mongoose.model() with the same name and same schema, you'll get the same schema back.

- -

Example:

- -
var mongoose = require('mongoose');
-
-// define an Actor model with this mongoose instance
-const Schema = new Schema({ name: String });
-mongoose.model('Actor', schema);
-
-// create a new connection
-var conn = mongoose.createConnection(..);
-
-// create Actor model
-var Actor = conn.model('Actor', schema);
-conn.model('Actor') === Actor; // true
-conn.model('Actor', schema) === Actor; // true, same schema
-conn.model('Actor', schema, 'actors') === Actor; // true, same schema and collection name
-
-// This throws an `OverwriteModelError` because the schema is different.
-conn.model('Actor', new Schema({ name: String }));
- -

When no collection argument is passed, Mongoose uses the model name. If you don't like this behavior, either pass a collection name, use mongoose.pluralize(), or set your schemas collection name option.

- -

Example:

- -
var schema = new Schema({ name: String }, { collection: 'actor' });
-
-// or
-
-schema.set('collection', 'actor');
-
-// or
-
-var collectionName = 'actor'
-var M = mongoose.model('Actor', schema, collectionName)

Mongoose.prototype.modelNames()

Returns:
  • «Array»

Returns an array of model names created on this instance of Mongoose.

- -

Note:

- -

Does not include names of models created using connection.model().


Mongoose.prototype.mongo

Type:
  • «property»

The node-mongodb-native driver Mongoose uses.


Mongoose.prototype.mquery

Type:
  • «property»

The mquery query builder Mongoose uses.


Mongoose.prototype.now()

Mongoose uses this function to get the current time when setting timestamps. You may stub out this function using a tool like Sinon for testing.


Mongoose.prototype.plugin()

Parameters
  • fn -«Function» plugin callback
  • [opts] -«Object» optional options
Returns:
  • «Mongoose» this

Declares a global plugin executed on all Schemas.

- -

Equivalent to calling .plugin(fn) on each Schema you create.


Mongoose.prototype.pluralize()

Parameters
  • [fn] -«Function|null» overwrites the function used to pluralize collection names
Returns:
  • «Function,null» the current function used to pluralize collection names, defaults to the legacy function from mongoose-legacy-pluralize.

Getter/setter around function for pluralizing collection names.


Mongoose.prototype.set()

Parameters
  • key -«String»
  • value -«String|Function|Boolean»

Sets mongoose options

- -

Example:

- -
mongoose.set('test', value) // sets the 'test' option to `value`
-
-mongoose.set('debug', true) // enable logging collection methods + arguments to the console
-
-mongoose.set('debug', function(collectionName, methodName, arg1, arg2...) {}); // use custom function to log collection methods + arguments
- -

Currently supported options are

- -
    -
  • 'debug': prints the operations mongoose sends to MongoDB to the console
  • -
  • 'bufferCommands': enable/disable mongoose's buffering mechanism for all connections and models
  • -
  • 'useCreateIndex': false by default. Set to true to make Mongoose's default index build use createIndex() instead of ensureIndex() to avoid deprecation warnings from the MongoDB driver.
  • -
  • 'useFindAndModify': true by default. Set to false to make findOneAndUpdate() and findOneAndRemove() use native findOneAndUpdate() rather than findAndModify().
  • -
  • 'useNewUrlParser': false by default. Set to true to make all connections set the useNewUrlParser option by default
  • -
  • 'cloneSchemas': false by default. Set to true to clone() all schemas before compiling into a model.
  • -
  • '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.
  • -
  • '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.
  • -
  • 'runValidators': false by default. Set to true to enable update validators for all validators by default.
  • -
  • 'toObject': { transform: true, flattenDecimals: true } by default. Overwrites default objects to toObject()
  • -
  • 'toJSON': { transform: true, flattenDecimals: true } by default. Overwrites default objects to toJSON(), for determining how Mongoose documents get serialized by JSON.stringify()
  • -
  • 'strict': true by default, may be false, true, or 'throw'. Sets the default strict mode for schemas.
  • -
  • '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.
  • -

Mongoose.prototype.startSession()

Parameters
  • [options] -«Object» see the mongodb driver options
    • [options.causalConsistency=true] -«Boolean» set to false to disable causal consistency
  • [callback] -«Function»
Returns:
  • «Promise<ClientSession>» promise that resolves to a MongoDB driver ClientSession

Requires MongoDB >= 3.6.0. Starts a MongoDB session for benefits like causal consistency, retryable writes, and transactions.

- -

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.


Mongoose.prototype.version

Type:
  • «property»

The Mongoose version

- -

Example

- -
console.log(mongoose.version); // '5.x.x'

Schema


Schema()

Parameters
  • [definition] -«Object|Schema|Array» Can be one of: object describing schema paths, or schema to copy, or array of objects and schemas
  • [options] -«Object»

Schema constructor.

- -

Example:

- -
var child = new Schema({ name: String });
-var schema = new Schema({ name: String, age: Number, children: [child] });
-var Tree = mongoose.model('Tree', schema);
-
-// setting schema options
-new Schema({ name: String }, { _id: false, autoIndex: false })
- -

Options:

- - - -

Note:

- -

When nesting schemas, (children in the example above), always declare the child schema first before passing it into its parent.


Schema.Types

Type:
  • «property»

The various built-in Mongoose Schema Types.

- -

Example:

- -
var mongoose = require('mongoose');
-var ObjectId = mongoose.Schema.Types.ObjectId;
- -

Types:

- - - -

Using this exposed access to the Mixed SchemaType, we can use them in our schema.

- -
var Mixed = mongoose.Schema.Types.Mixed;
-new mongoose.Schema({ _user: Mixed })

Schema.indexTypes

Type:
  • «property»

The allowed index types


Schema.prototype.add()

Parameters
  • obj -«Object|Schema» plain object with paths to add, or another schema
  • [prefix] -«String» path to prefix the newly added paths with
Returns:
  • «Schema» the Schema instance

Adds key path / schema type pairs to this schema.

- -

Example:

- -
const ToySchema = new Schema();
-ToySchema.add({ name: 'string', color: 'string', price: 'number' });
-
-const TurboManSchema = new Schema();
-// You can also `add()` another schema and copy over all paths, virtuals,
-// getters, setters, indexes, methods, and statics.
-TurboManSchema.add(ToySchema).add({ year: Number });

Schema.prototype.childSchemas

Type:
  • «property»

Array of child schemas (from document arrays and single nested subdocs) and their corresponding compiled models. Each element of the array is an object with 2 properties: schema and model.

- -

This property is typically only useful for plugin authors and advanced users. You do not need to interact with this property at all to use mongoose.


Schema.prototype.clone()

Returns:
  • «Schema» the cloned schema

Returns a deep copy of the schema

- -

Example:

- -
const schema = new Schema({ name: String });
-const clone = schema.clone();
-clone === schema; // false
-clone.path('name'); // SchemaString { ... }

Schema.prototype.eachPath()

Parameters
  • fn -«Function» callback function
Returns:
  • «Schema» this

Iterates the schemas paths similar to Array#forEach.

- -

The callback is passed the pathname and the schemaType instance.

- -

Example:

- -
const userSchema = new Schema({ name: String, registeredAt: Date });
-userSchema.eachPath((pathname, schematype) => {
-  // Prints twice:
-  // name SchemaString { ... }
-  // registeredAt SchemaDate { ... }
-  console.log(pathname, schematype);
-});

Schema.prototype.get()

Parameters
  • key -«String» option name
Returns:
  • «Any» the option's value

Gets a schema option.

- -

Example:

- -
schema.get('strict'); // true
-schema.set('strict', false);
-schema.get('strict'); // false

Schema.prototype.index()

Parameters
  • fields -«Object»
  • [options] -«Object» Options to pass to MongoDB driver's createIndex() function
    • [options.expires=null] -«String» Mongoose-specific syntactic sugar, uses ms to convert expires option into seconds for the expireAfterSeconds in the above link.

Defines an index (most likely compound) for this schema.

- -

Example

- -
schema.index({ first: 1, last: -1 })

Schema.prototype.indexes()

Returns:
  • «Array» list of indexes defined in the schema

Returns a list of indexes that this schema declares, via schema.index() or by index: true in a path's options.

- -

Example:

- -
const userSchema = new Schema({
-  email: { type: String, required: true, unique: true },
-  registeredAt: { type: Date, index: true }
-});
-
-// [ [ { email: 1 }, { unique: true, background: true } ],
-//   [ { registeredAt: 1 }, { background: true } ] ]
-userSchema.indexes();

Schema.prototype.loadClass()

Parameters
  • model -«Function»
  • [virtualsOnly] -«Boolean» if truthy, only pulls virtuals from the class, not methods or statics

Loads an ES6 class into a schema. Maps setters + getters, static methods, and instance methods to schema virtuals, statics, and methods.

- -

Example:

- -
const md5 = require('md5');
-const userSchema = new Schema({ email: String });
-class UserClass {
-  // `gravatarImage` becomes a virtual
-  get gravatarImage() {
-    const hash = md5(this.email.toLowerCase());
-    return `https://www.gravatar.com/avatar/${hash}`;
-  }
-
-  // `getProfileUrl()` becomes a document method
-  getProfileUrl() {
-    return `https://mysite.com/${this.email}`;
-  }
-
-  // `findByEmail()` becomes a static
-  static findByEmail(email) {
-    return this.findOne({ email });
-  }
-}
-
-// `schema` will now have a `gravatarImage` virtual, a `getProfileUrl()` method,
-// and a `findByEmail()` static
-userSchema.loadClass(UserClass);
-

Schema.prototype.method()

Parameters
  • method -«String|Object» name
  • [fn] -«Function»

Adds an instance method to documents constructed from Models compiled from this schema.

- -

Example

- -
var schema = kittySchema = new Schema(..);
-
-schema.method('meow', function () {
-  console.log('meeeeeoooooooooooow');
-})
-
-var Kitty = mongoose.model('Kitty', schema);
-
-var fizz = new Kitty;
-fizz.meow(); // meeeeeooooooooooooow
- -

If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as methods.

- -
schema.method({
-    purr: function () {}
-  , scratch: function () {}
-});
-
-// later
-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


Schema.prototype.obj

Type:
  • «property»

The original object passed to the schema constructor

- -

Example:

- -
var schema = new Schema({ a: String }).add({ b: String });
-schema.obj; // { a: String }

Schema.prototype.path()

Parameters
  • path -«String»
  • constructor -«Object»

Gets/sets schema paths.

- -

Sets a path (if arity 2) Gets a path (if arity 1)

- -

Example

- -
schema.path('name') // returns a SchemaType
-schema.path('name', Number) // changes the schemaType of `name` to Number

Schema.prototype.pathType()

Parameters
  • path -«String»
Returns:
  • «String»

Returns the pathType of path for this schema.

- -

Given a path, returns whether it is a real, virtual, nested, or ad-hoc/undefined path.

- -

Example:

- -
const s = new Schema({ name: String, nested: { foo: String } });
-s.virtual('foo').get(() => 42);
-s.pathType('name'); // "real"
-s.pathType('nested'); // "nested"
-s.pathType('foo'); // "virtual"
-s.pathType('fail'); // "adhocOrUndefined"

Schema.prototype.plugin()

Parameters
  • plugin -«Function» callback
  • [opts] -«Object»

Registers a plugin for this schema.

- -

Example:

- -
const s = new Schema({ name: String });
-s.plugin(schema => console.log(schema.path('name').path));
-mongoose.model('Test', schema); // Prints 'name'

Schema.prototype.post()

Parameters
  • The -«String|RegExp» method name or regular expression to match method name
  • [options] -«Object»
    • [options.document] -«Boolean» If name is a hook for both document and query middleware, set to true to run on document middleware.
    • [options.query] -«Boolean» If name is a hook for both document and query middleware, set to true to run on query middleware.
  • fn -«Function» callback

Defines a post hook for the document

- -
var schema = new Schema(..);
-schema.post('save', function (doc) {
-  console.log('this fired after a document was saved');
-});
-
-schema.post('find', function(docs) {
-  console.log('this fired after you ran a find query');
-});
-
-schema.post(/Many$/, function(res) {
-  console.log('this fired after you ran `updateMany()` or `deleteMany()`);
-});
-
-var Model = mongoose.model('Model', schema);
-
-var m = new Model(..);
-m.save(function(err) {
-  console.log('this fires after the `post` hook');
-});
-
-m.find(function(err, docs) {
-  console.log('this fires after the post find hook');
-});

Schema.prototype.pre()

Parameters
  • The -«String|RegExp» method name or regular expression to match method name
  • [options] -«Object»
    • [options.document] -«Boolean» If name is a hook for both document and query middleware, set to true to run on document middleware.
    • [options.query] -«Boolean» If name is a hook for both document and query middleware, set to true to run on query middleware.
  • callback -«Function»

Defines a pre hook for the document.

- -

Example

- -
var toySchema = new Schema({ name: String, created: Date });
-
-toySchema.pre('save', function(next) {
-  if (!this.created) this.created = new Date;
-  next();
-});
-
-toySchema.pre('validate', function(next) {
-  if (this.name !== 'Woody') this.name = 'Woody';
-  next();
-});
-
-// Equivalent to calling `pre()` on `find`, `findOne`, `findOneAndUpdate`.
-toySchema.pre(/^find/, function(next) {
-  console.log(this.getFilter());
-});

Schema.prototype.queue()

Parameters
  • name -«String» name of the document method to call later
  • args -«Array» arguments to pass to the method

Adds a method call to the queue.

- -

Example:

- -
schema.methods.print = function() { console.log(this); };
-schema.queue('print', []); // Print the doc every one is instantiated
-
-const Model = mongoose.model('Test', schema);
-new Model({ name: 'test' }); // Prints '{"_id": ..., "name": "test" }'

Schema.prototype.remove()

Parameters
  • path -«String|Array»
Returns:
  • «Schema» the Schema instance

Removes the given path (or [paths]).

- -

Example:

- -
const schema = new Schema({ name: String, age: Number });
-schema.remove('name');
-schema.path('name'); // Undefined
-schema.path('age'); // SchemaNumber { ... }

Schema.prototype.requiredPaths()

Parameters
  • invalidate -«Boolean» refresh the cache
Returns:
  • «Array»

Returns an Array of path strings that are required by this schema.

- -

Example:

- -
const s = new Schema({
-  name: { type: String, required: true },
-  age: { type: String, required: true },
-  notes: String
-});
-s.requiredPaths(); // [ 'age', 'name' ]

Schema.prototype.set()

Parameters
  • key -«String» option name
  • [value] -«Object» if not passed, the current option value is returned

Sets/gets a schema option.

- -

Example

- -
schema.set('strict'); // 'true' by default
-schema.set('strict', false); // Sets 'strict' to false
-schema.set('strict'); // 'false'

Schema.prototype.static()

Parameters
  • name -«String|Object»
  • [fn] -«Function»

Adds static "class" methods to Models compiled from this schema.

- -

Example

- -
const schema = new Schema(..);
-// Equivalent to `schema.statics.findByName = function(name) {}`;
-schema.static('findByName', function(name) {
-  return this.find({ name: name });
-});
-
-const Drink = mongoose.model('Drink', schema);
-await Drink.findByName('LaCroix');
- -

If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as statics.


Schema.prototype.virtual()

Parameters
  • name -«String»
  • [options] -«Object»
    • [options.ref] -«String|Model» model name or model instance. Marks this as a populate virtual.
    • [options.localField] -«String|Function» Required for populate virtuals. See populate virtual docs for more information.
    • [options.foreignField] -«String|Function» Required for populate virtuals. See populate virtual docs for more information.
    • [options.justOne=false] -«Boolean|Function» Only works with populate virtuals. If truthy, will be a single doc or null. Otherwise, the populate virtual will be an array.
    • [options.count=false] -«Boolean» Only works with populate virtuals. If truthy, this populate virtual will contain the number of documents rather than the documents themselves when you populate().
Returns:
  • «VirtualType»

Creates a virtual type with the given name.


Schema.prototype.virtualpath()

Parameters
  • name -«String»
Returns:
  • «VirtualType»

Returns the virtual type with the given name.


Schema.reserved

Type:
  • «property»

Reserved document keys.

- -

Keys in this object are names that are rejected in schema declarations because they conflict with Mongoose functionality. If you create a schema using new Schema() with one of these property names, Mongoose will throw an error.

- -
    -
  • prototype
  • -
  • emit
  • -
  • on
  • -
  • once
  • -
  • listeners
  • -
  • removeListener
  • -
  • collection
  • -
  • db
  • -
  • errors
  • -
  • init
  • -
  • isModified
  • -
  • isNew
  • -
  • get
  • -
  • modelName
  • -
  • save
  • -
  • schema
  • -
  • toObject
  • -
  • validate
  • -
  • remove
  • -
  • populated
  • -
  • _pres
  • -
  • _posts
  • -
- -

NOTE: Use of these terms as method names is permitted, but play at your own risk, as they may be existing mongoose document methods you are stomping on.

- -
var schema = new Schema(..);
- schema.methods.init = function () {} // potentially breaking

Connection


Connection()

Parameters
  • base -«Mongoose» a mongoose instance

Connection constructor

- -

For practical reasons, a Connection equals a Db.


Connection.prototype.close()

Parameters
  • [force] -«Boolean» optional
  • [callback] -«Function» optional
Returns:
  • «Connection» self

Closes the connection


Connection.prototype.collection()

Parameters
  • name -«String» of the collection
  • [options] -«Object» optional collection options
Returns:
  • «Collection» collection instance

Retrieves a collection, creating it if not cached.

- -

Not typically needed by applications. Just talk to your collection through your model.


Connection.prototype.collections

Type:
  • «property»

A hash of the collections associated with this connection


Connection.prototype.config

Type:
  • «property»

A hash of the global options that are associated with this connection


Connection.prototype.createCollection()

Parameters
  • collection -«string» The collection to create
  • [options] -«Object» see MongoDB driver docs
  • [callback] -«Function»
Returns:
  • «Promise»

Helper for createCollection(). Will explicitly create the given collection with specified options. Used to create capped collections and views from mongoose.

- -

Options are passed down without modification to the MongoDB driver's createCollection() function


Connection.prototype.db

Type:
  • «property»

The mongodb.Db instance, set when the connection is opened


Connection.prototype.deleteModel()

Parameters
  • name -«String|RegExp» if string, the name of the model to remove. If regexp, removes all models whose name matches the regexp.
Returns:
  • «Connection» this

Removes the model named name from this connection, if it exists. You can use this function to clean up any models you created in your tests to prevent OverwriteModelErrors.

- -

Example:

- -
conn.model('User', new Schema({ name: String }));
-console.log(conn.model('User')); // Model object
-conn.deleteModel('User');
-console.log(conn.model('User')); // undefined
-
-// Usually useful in a Mocha `afterEach()` hook
-afterEach(function() {
-  conn.deleteModel(/.+/); // Delete every model
-});

Connection.prototype.dropCollection()

Parameters
  • collection -«string» The collection to delete
  • [callback] -«Function»
Returns:
  • «Promise»

Helper for dropCollection(). Will delete the given collection, including all documents and indexes.


Connection.prototype.dropDatabase()

Parameters
  • [callback] -«Function»
Returns:
  • «Promise»

Helper for dropDatabase(). Deletes the given database, including all collections, documents, and indexes.

- -

Example:

- -
const conn = mongoose.createConnection('mongodb://localhost:27017/mydb');
-// Deletes the entire 'mydb' database
-await conn.dropDatabase();

Connection.prototype.get()

Parameters
  • key -«String»

Gets the value of the option key. Equivalent to conn.options[key]

- -

Example:

- -
conn.get('test'); // returns the 'test' value

Connection.prototype.host

Type:
  • «property»

The host name portion of the URI. If multiple hosts, such as a replica set, this will contain the first host name in the URI

- -

Example

- -
mongoose.createConnection('mongodb://localhost:27017/mydb').host; // "localhost"

Connection.prototype.model()

Parameters
  • name -«String|Function» the model name or class extending Model
  • [schema] -«Schema» a schema. necessary when defining a model
  • [collection] -«String» name of mongodb collection (optional) if not given it will be induced from model name
Returns:
  • «Model» The compiled model

Defines or retrieves a model.

- -
var mongoose = require('mongoose');
-var db = mongoose.createConnection(..);
-db.model('Venue', new Schema(..));
-var Ticket = db.model('Ticket', new Schema(..));
-var Venue = db.model('Venue');
- -

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:

- -
var schema = new Schema({ name: String }, { collection: 'actor' });
-
-// or
-
-schema.set('collection', 'actor');
-
-// or
-
-var collectionName = 'actor'
-var M = conn.model('Actor', schema, collectionName)

Connection.prototype.modelNames()

Returns:
  • «Array»

Returns an array of model names created on this connection.


Connection.prototype.name

Type:
  • «property»

The name of the database this connection points to.

- -

Example

- -
mongoose.createConnection('mongodb://localhost:27017/mydb').name; // "mydb"

Connection.prototype.openUri()

Parameters
  • uri -«String» The URI to connect with.
  • [options] -«Object» Passed on to http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html#connect
  • [callback] -«Function»

Opens the connection with a URI using MongoClient.connect().


Connection.prototype.pass

Type:
  • «property»

The password specified in the URI

- -

Example

- -
mongoose.createConnection('mongodb://val:psw@localhost:27017/mydb').pass; // "psw"

Connection.prototype.plugin()

Parameters
  • fn -«Function» plugin callback
  • [opts] -«Object» optional options
Returns:
  • «Connection» this

Declares a plugin executed on all schemas you pass to conn.model()

- -

Equivalent to calling .plugin(fn) on each schema you create.

- -

Example:

- -
const db = mongoose.createConnection('mongodb://localhost:27017/mydb');
-db.plugin(() => console.log('Applied'));
-db.plugins.length; // 1
-
-db.model('Test', new Schema({})); // Prints "Applied"

Connection.prototype.plugins

Type:
  • «property»

The plugins that will be applied to all models created on this connection.

- -

Example:

- -
const db = mongoose.createConnection('mongodb://localhost:27017/mydb');
-db.plugin(() => console.log('Applied'));
-db.plugins.length; // 1
-
-db.model('Test', new Schema({})); // Prints "Applied"

Connection.prototype.port

Type:
  • «property»

The port portion of the URI. If multiple hosts, such as a replica set, this will contain the port from the first host name in the URI.

- -

Example

- -
mongoose.createConnection('mongodb://localhost:27017/mydb').port; // 27017

Connection.prototype.readyState

Type:
  • «property»

Connection ready state

- -
    -
  • 0 = disconnected
  • -
  • 1 = connected
  • -
  • 2 = connecting
  • -
  • 3 = disconnecting
  • -
- -

Each state change emits its associated event name.

- -

Example

- -
conn.on('connected', callback);
-conn.on('disconnected', callback);

Connection.prototype.set()

Parameters
  • key -«String»
  • val -«Any»

Sets the value of the option key. Equivalent to conn.options[key] = val

- -

Supported options include

- - - -

Example:

- -
conn.set('test', 'foo');
-conn.get('test'); // 'foo'
-conn.options.test; // 'foo'

Connection.prototype.startSession()

Parameters
  • [options] -«Object» see the mongodb driver options
    • [options.causalConsistency=true] -«Boolean» set to false to disable causal consistency
  • [callback] -«Function»
Returns:
  • «Promise<ClientSession>» promise that resolves to a MongoDB driver ClientSession

Requires MongoDB >= 3.6.0. Starts a MongoDB session for benefits like causal consistency, retryable writes, and transactions.

- -

Example:

- -
const session = await conn.startSession();
-let doc = await Person.findOne({ name: 'Ned Stark' }, null, { session });
-await doc.remove();
-// `doc` will always be null, even if reading from a replica set
-// secondary. Without causal consistency, it is possible to
-// get a doc back from the below query if the query reads from a
-// secondary that is experiencing replication lag.
-doc = await Person.findOne({ name: 'Ned Stark' }, null, { session, readPreference: 'secondary' });

Connection.prototype.useDb()

Parameters
  • name -«String» The database name
Returns:
  • «Connection» New Connection Object

Switches to a different database using the same connection pool.

- -

Returns a new connection object, with the new db.


Connection.prototype.user

Type:
  • «property»

The username specified in the URI

- -

Example

- -
mongoose.createConnection('mongodb://val:psw@localhost:27017/mydb').user; // "val"

Document


Document.prototype.$ignore()

Parameters
  • path -«String» the path to ignore

Don't run validation on this path or persist changes to this path.

- -

Example:

- -
doc.foo = null;
-doc.$ignore('foo');
-doc.save(); // changes to foo will not be persisted and validators won't be run

Document.prototype.$isDefault()

Parameters
  • [path] -«String»
Returns:
  • «Boolean»

Checks if a path is set to its default.

- -

Example

- -
MyModel = mongoose.model('test', { name: { type: String, default: 'Val '} });
-var m = new MyModel();
-m.$isDefault('name'); // true

Document.prototype.$isDeleted()

Parameters
  • [val] -«Boolean» optional, overrides whether mongoose thinks the doc is deleted
Returns:
  • «Boolean» whether mongoose thinks this doc is deleted.

Getter/setter, determines whether the document was removed or not.

- -

Example:

- -
product.remove(function (err, product) {
-  product.$isDeleted(); // true
-  product.remove(); // no-op, doesn't send anything to the db
-
-  product.$isDeleted(false);
-  product.$isDeleted(); // false
-  product.remove(); // will execute a remove against the db
-})

Document.prototype.$isEmpty()

Returns:
  • «Boolean»

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.

- -

Example:

- -
const schema = new Schema({ nested: { foo: String } });
-const Model = mongoose.model('Test', schema);
-const doc = new Model({});
-doc.$isEmpty('nested'); // true
-doc.nested.$isEmpty(); // true
-
-doc.nested.foo = 'bar';
-doc.$isEmpty('nested'); // false
-doc.nested.$isEmpty(); // false

Document.prototype.$locals

Type:
  • «property»

Empty object that you can use for storing properties on the document. This is handy for passing data to middleware without conflicting with Mongoose internals.

- -

Example:

- -
schema.pre('save', function() {
-  // Mongoose will set `isNew` to `false` if `save()` succeeds
-  this.$locals.wasNew = this.isNew;
-});
-
-schema.post('save', function() {
-  // Prints true if `isNew` was set before `save()`
-  console.log(this.$locals.wasNew);
-});

Document.prototype.$markValid()

Parameters
  • path -«String» the field to mark as valid

Marks a path as valid, removing existing validation errors.


Document.prototype.$session()

Parameters
  • [session] -«ClientSession» overwrite the current session
Returns:
  • «ClientSession»

Getter/setter around the session associated with this document. Used to automatically set session if you save() a doc that you got from a query with an associated session.

- -

Example:

- -
const session = MyModel.startSession();
-const doc = await MyModel.findOne().session(session);
-doc.$session() === session; // true
-doc.$session(null);
-doc.$session() === null; // true
- -

If this is a top-level document, setting the session propagates to all child docs.


Document.prototype.$set()

Parameters
  • path -«String|Object» path or object of key/vals to set
  • val -«Any» the value to set
  • [type] -«Schema|String|Number|Buffer|*» optionally specify a type for "on-the-fly" attributes
  • [options] -«Object» optionally specify options that modify the behavior of the set

Alias for set(), used internally to avoid conflicts


Document.prototype.depopulate()

Parameters
  • path -«String»
Returns:
  • «Document» this

Takes a populated field and returns it to its unpopulated state.

- -

Example:

- -
Model.findOne().populate('author').exec(function (err, doc) {
-  console.log(doc.author.name); // Dr.Seuss
-  console.log(doc.depopulate('author'));
-  console.log(doc.author); // '5144cf8050f071d979c118a7'
-})
- -

If the path was not populated, this is a no-op.


Document.prototype.directModifiedPaths()

Returns:
  • «Array»

Returns the list of paths that have been directly modified. A direct modified path is a path that you explicitly set, whether via doc.foo = 'bar', Object.assign(doc, { foo: 'bar' }), or doc.set('foo', 'bar').

- -

A path a may be in modifiedPaths() but not in directModifiedPaths() because a child of a was directly modified.

- -

Example

- -
const schema = new Schema({ foo: String, nested: { bar: String } });
-const Model = mongoose.model('Test', schema);
-await Model.create({ foo: 'original', nested: { bar: 'original' } });
-
-const doc = await Model.findOne();
-doc.nested.bar = 'modified';
-doc.directModifiedPaths(); // ['nested.bar']
-doc.modifiedPaths(); // ['nested', 'nested.bar']

Document.prototype.equals()

Parameters
  • doc -«Document» a document to compare
Returns:
  • «Boolean»

Returns true if the Document stores the same data as doc.

- -

Documents are considered equal when they have matching _ids, unless neither document has an _id, in which case this function falls back to using deepEqual().


Document.prototype.errors

Type:
  • «property»

Hash containing current validation errors.


Document.prototype.execPopulate()

Parameters
  • [callback] -«Function» optional callback. If specified, a promise will not be returned
Returns:
  • «Promise» promise that resolves to the document when population is done

Explicitly executes population and returns a promise. Useful for ES2015 integration.

- -

Example:

- -
var promise = doc.
-  populate('company').
-  populate({
-    path: 'notes',
-    match: /airline/,
-    select: 'text',
-    model: 'modelName'
-    options: opts
-  }).
-  execPopulate();
-
-// summary
-doc.execPopulate().then(resolve, reject);

Document.prototype.get()

Parameters
  • path -«String»
  • [type] -«Schema|String|Number|Buffer|*» optionally specify a type for on-the-fly attributes
  • [options] -«Object»
    • [options.virtuals=false] -«Boolean» Apply virtuals before getting this path
    • [options.getters=true] -«Boolean» If false, skip applying getters and just get the raw value

Returns the value of a path.

- -

Example

- -
// path
-doc.get('age') // 47
-
-// dynamic casting to a string
-doc.get('age', String) // "47"

Document.prototype.id

Type:
  • «property»

The string version of this documents _id.

- -

Note:

- -

This getter exists on all documents by default. The getter can be disabled by setting the id option of its Schema to false at construction time.

- -
new Schema({ name: String }, { id: false });

Document.prototype.init()

Parameters
  • doc -«Object» document returned by mongo

Initializes the document without setters or marking anything modified.

- -

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. Note that init hooks are synchronous.


Document.prototype.inspect()

Helper for console.log


Document.prototype.invalidate()

Parameters
  • path -«String» the field to invalidate
  • errorMsg -«String|Error» the error which states the reason path was invalid
  • value -«Object|String|Number|any» optional invalid value
  • [kind] -«String» optional kind property for the error
Returns:
  • «ValidationError» the current ValidationError, with all currently invalidated paths

Marks a path as invalid, causing validation to fail.

- -

The errorMsg argument will become the message of the ValidationError.

- -

The value argument (if passed) will be available through the ValidationError.value property.

- -
doc.invalidate('size', 'must be less than 20', 14);
-
-doc.validate(function (err) {
-  console.log(err)
-  // prints
-  { message: 'Validation failed',
-    name: 'ValidationError',
-    errors:
-     { size:
-        { message: 'must be less than 20',
-          name: 'ValidatorError',
-          path: 'size',
-          type: 'user defined',
-          value: 14 } } }
-})

Document.prototype.isDirectModified()

Parameters
  • path -«String»
Returns:
  • «Boolean»

Returns true if path was directly set and modified, else false.

- -

Example

- -
doc.set('documents.0.title', 'changed');
-doc.isDirectModified('documents.0.title') // true
-doc.isDirectModified('documents') // false

Document.prototype.isDirectSelected()

Parameters
  • path -«String»
Returns:
  • «Boolean»

Checks if path was explicitly selected. If no projection, always returns true.

- -

Example

- -
Thing.findOne().select('nested.name').exec(function (err, doc) {
-   doc.isDirectSelected('nested.name') // true
-   doc.isDirectSelected('nested.otherName') // false
-   doc.isDirectSelected('nested')  // false
-})

Document.prototype.isInit()

Parameters
  • path -«String»
Returns:
  • «Boolean»

Checks if path was initialized.


Document.prototype.isModified()

Parameters
  • [path] -«String» optional
Returns:
  • «Boolean»

Returns true if this document was modified, else false.

- -

If path is given, checks if a path or any full path containing path as part of its path chain has been modified.

- -

Example

- -
doc.set('documents.0.title', 'changed');
-doc.isModified()                      // true
-doc.isModified('documents')           // true
-doc.isModified('documents.0.title')   // true
-doc.isModified('documents otherProp') // true
-doc.isDirectModified('documents')     // false

Document.prototype.isNew

Type:
  • «property»

Boolean flag specifying if the document is new.


Document.prototype.isSelected()

Parameters
  • path -«String»
Returns:
  • «Boolean»

Checks if path was selected in the source query which initialized this document.

- -

Example

- -
Thing.findOne().select('name').exec(function (err, doc) {
-   doc.isSelected('name') // true
-   doc.isSelected('age')  // false
-})

Document.prototype.markModified()

Parameters
  • path -«String» the path to mark modified
  • [scope] -«Document» the scope to run validators with

Marks the path as having pending changes to write to the db.

- -

Very helpful when using Mixed types.

- -

Example:

- -
doc.mixed.type = 'changed';
-doc.markModified('mixed.type');
-doc.save() // changes to mixed.type are now persisted

Document.prototype.modifiedPaths()

Parameters
  • [options] -«Object»
    • [options.includeChildren=false] -«Boolean» if true, returns children of modified paths as well. For example, if false, the list of modified paths for doc.colors = { primary: 'blue' }; will not contain colors.primary. If true, modifiedPaths() will return an array that contains colors.primary.
Returns:
  • «Array»

Returns the list of paths that have been modified.


Document.prototype.overwrite()

Parameters
  • obj -«Object» the object to overwrite this document with

Overwrite all values in this document with the values of obj, except for immutable properties. Behaves similarly to set(), except for it unsets all properties that aren't in obj.


Document.prototype.populate()

Parameters
  • [path] -«String|Object» The path to populate or an options object
  • [callback] -«Function» When passed, population is invoked
Returns:
  • «Document» this

Populates document references, executing the callback when complete. If you want to use promises instead, use this function with execPopulate()

- -

Example:

- -
doc
-.populate('company')
-.populate({
-  path: 'notes',
-  match: /airline/,
-  select: 'text',
-  model: 'modelName'
-  options: opts
-}, function (err, user) {
-  assert(doc._id === user._id) // the document itself is passed
-})
-
-// summary
-doc.populate(path)                   // not executed
-doc.populate(options);               // not executed
-doc.populate(path, callback)         // executed
-doc.populate(options, callback);     // executed
-doc.populate(callback);              // executed
-doc.populate(options).execPopulate() // executed, returns promise
- -

NOTE:

- -

Population does not occur unless a callback is passed or you explicitly call execPopulate(). Passing the same path a second time will overwrite the previous path options. See Model.populate() for explaination of options.


Document.prototype.populated()

Parameters
  • path -«String»
Returns:
  • «Array,ObjectId,Number,Buffer,String,undefined»

Gets _id(s) used during population of the given path.

- -

Example:

- -
Model.findOne().populate('author').exec(function (err, doc) {
-  console.log(doc.author.name)         // Dr.Seuss
-  console.log(doc.populated('author')) // '5144cf8050f071d979c118a7'
-})
- -

If the path was not populated, undefined is returned.


Document.prototype.replaceOne()

Parameters
  • doc -«Object»
  • options -«Object»
  • callback -«Function»
Returns:
  • «Query»

Sends a replaceOne command with this document _id as the query selector.

- -

Valid options:

- -

Document.prototype.save()

Parameters
  • [options] -«Object» options optional options
    • [options.validateBeforeSave] -«Boolean» set to false to save without validating.
  • [fn] -«Function» optional callback
Returns:
  • «Promise,undefined» Returns undefined if used with callback or a Promise otherwise.

Saves this document.

- -

Example:

- -
product.sold = Date.now();
-product.save(function (err, product) {
-  if (err) ..
-})
- -

The callback will receive two parameters

- -
    -
  1. err if an error occurred
  2. -
  3. product which is the saved product
  4. -
- -

As an extra measure of flow control, save will return a Promise.

- -

Example:

- -
product.save().then(function(product) {
-   ...
-});

Document.prototype.schema

Type:
  • «property»

The documents schema.


Document.prototype.set()

Parameters
  • path -«String|Object» path or object of key/vals to set
  • val -«Any» the value to set
  • [type] -«Schema|String|Number|Buffer|*» optionally specify a type for "on-the-fly" attributes
  • [options] -«Object» optionally specify options that modify the behavior of the set

Sets the value of a path, or many paths.

- -

Example:

- -
// path, value
-doc.set(path, value)
-
-// object
-doc.set({
-    path  : value
-  , path2 : {
-       path  : value
-    }
-})
-
-// on-the-fly cast to number
-doc.set(path, value, Number)
-
-// on-the-fly cast to string
-doc.set(path, value, String)
-
-// changing strict mode behavior
-doc.set(path, value, { strict: false });

Document.prototype.toJSON()

Parameters
  • options -«Object»
Returns:
  • «Object»

The return value of this method is used in calls to JSON.stringify(doc).

- -

This method accepts the same options as Document#toObject. To apply the options to every document of your schema by default, set your schemas toJSON option to the same argument.

- -
schema.set('toJSON', { virtuals: true })
- -

See schema options for details.


Document.prototype.toObject()

Parameters
  • [options] -«Object»
    • [options.getters=false] -«Boolean» if true, apply all getters, including virtuals
    • [options.virtuals=false] -«Boolean» if true, apply virtuals. Use { getters: true, virtuals: false } to just apply getters, not virtuals
    • [options.minimize=true] -«Boolean» if true, omit any empty objects from the output
    • [options.transform=null] -«Function|null» if set, mongoose will call this function to allow you to transform the returned object
    • [options.depopulate=false] -«Boolean» if true, replace any conventionally populated paths with the original id in the output. Has no affect on virtual populated paths.
    • [options.versionKey=true] -«Boolean» if false, exclude the version key (__v by default) from the output
    • [options.flattenMaps=false] -«Boolean» if true, convert Maps to POJOs. Useful if you want to JSON.stringify() the result of toObject().
Returns:
  • «Object» js object

Converts this document into a plain javascript object, ready for storage in MongoDB.

- -

Buffers are converted to instances of mongodb.Binary for proper storage.

- -

Options:

- -
    -
  • getters apply all getters (path and virtual getters), defaults to false
  • -
  • virtuals apply virtual getters (can override getters option), defaults to false
  • -
  • minimize remove empty objects (defaults to true)
  • -
  • transform a transform function to apply to the resulting document before returning
  • -
  • depopulate depopulate any populated paths, replacing them with their original refs (defaults to false)
  • -
  • versionKey whether to include the version key (defaults to true)
  • -
- -

Getters/Virtuals

- -

Example of only applying path getters

- -
doc.toObject({ getters: true, virtuals: false })
- -

Example of only applying virtual getters

- -
doc.toObject({ virtuals: true })
- -

Example of applying both path and virtual getters

- -
doc.toObject({ getters: true })
- -

To apply these options to every document of your schema by default, set your schemas toObject option to the same argument.

- -
schema.set('toObject', { virtuals: true })
- -

Transform

- -

We may need to perform a transformation of the resulting object based on some criteria, say to remove some sensitive information or return a custom object. In this case we set the optional transform function.

- -

Transform functions receive three arguments

- -
function (doc, ret, options) {}
- -
    -
  • doc The mongoose document which is being converted
  • -
  • ret The plain object representation which has been converted
  • -
  • options The options in use (either schema options or the options passed inline)
  • -
- -

Example

- -
// specify the transform schema option
-if (!schema.options.toObject) schema.options.toObject = {};
-schema.options.toObject.transform = function (doc, ret, options) {
-  // remove the _id of every document before returning the result
-  delete ret._id;
-  return ret;
-}
-
-// without the transformation in the schema
-doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
-
-// with the transformation
-doc.toObject(); // { name: 'Wreck-it Ralph' }
- -

With transformations we can do a lot more than remove properties. We can even return completely new customized objects:

- -
if (!schema.options.toObject) schema.options.toObject = {};
-schema.options.toObject.transform = function (doc, ret, options) {
-  return { movie: ret.name }
-}
-
-// without the transformation in the schema
-doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
-
-// with the transformation
-doc.toObject(); // { movie: 'Wreck-it Ralph' }
- -

Note: if a transform function returns undefined, the return value will be ignored.

- -

Transformations may also be applied inline, overridding any transform set in the options:

- -
function xform (doc, ret, options) {
-  return { inline: ret.name, custom: true }
-}
-
-// pass the transform as an inline option
-doc.toObject({ transform: xform }); // { inline: 'Wreck-it Ralph', custom: true }
- -

If you want to skip transformations, use transform: false:

- -
if (!schema.options.toObject) schema.options.toObject = {};
-schema.options.toObject.hide = '_id';
-schema.options.toObject.transform = function (doc, ret, options) {
-  if (options.hide) {
-    options.hide.split(' ').forEach(function (prop) {
-      delete ret[prop];
-    });
-  }
-  return ret;
-}
-
-var doc = new Doc({ _id: 'anId', secret: 47, name: 'Wreck-it Ralph' });
-doc.toObject();                                        // { secret: 47, name: 'Wreck-it Ralph' }
-doc.toObject({ hide: 'secret _id', transform: false });// { _id: 'anId', secret: 47, name: 'Wreck-it Ralph' }
-doc.toObject({ hide: 'secret _id', transform: true }); // { name: 'Wreck-it Ralph' }
- -

Transforms are applied only to the document and are not applied to sub-documents.

- -

Transforms, like all of these options, are also available for toJSON.

- -

See schema options for some more details.

- -

During save, no custom options are applied to the document before being sent to the database.


Document.prototype.toString()

Helper for console.log


Document.prototype.unmarkModified()

Parameters
  • path -«String» the path to unmark modified

Clears the modified state on the specified path.

- -

Example:

- -
doc.foo = 'bar';
-doc.unmarkModified('foo');
-doc.save(); // changes to foo will not be persisted

Document.prototype.update()

Parameters
  • doc -«Object»
  • options -«Object»
  • callback -«Function»
Returns:
  • «Query»

Sends an update command with this document _id as the query selector.

- -

Example:

- -
weirdCar.update({$inc: {wheels:1}}, { w: 1 }, callback);
- -

Valid options:

- -

Document.prototype.updateOne()

Parameters
  • doc -«Object»
  • options -«Object»
  • callback -«Function»
Returns:
  • «Query»

Sends an updateOne command with this document _id as the query selector.

- -

Example:

- -
weirdCar.updateOne({$inc: {wheels:1}}, { w: 1 }, callback);
- -

Valid options:

- -

Document.prototype.validate()

Parameters
  • optional -«Object» options internal options
  • callback -«Function» optional callback called after validation completes, passing an error if one occurred
Returns:
  • «Promise» Promise

Executes registered validation rules for this document.

- -

Note:

- -

This method is called pre save and if a validation rule is violated, save is aborted and the error is returned to your callback.

- -

Example:

- -
doc.validate(function (err) {
-  if (err) handleError(err);
-  else // validation passed
-});

Document.prototype.validateSync()

Parameters
  • pathsToValidate -«Array|string» only validate the given paths
Returns:
  • «ValidationError,undefined» ValidationError if there are errors during validation, or undefined if there is no error.

Executes registered validation rules (skipping asynchronous validators) for this document.

- -

Note:

- -

This method is useful if you need synchronous validation.

- -

Example:

- -
var err = doc.validateSync();
-if ( err ){
-  handleError( err );
-} else {
-  // validation passed
-}

Model


Model()

Parameters
  • doc -«Object» values for initial set
  • optional -«[fields]» 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.

A Model is a class that's your primary tool for interacting with MongoDB. An instance of a Model is called a 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() and connection.model() functions create subclasses of mongoose.Model as shown below.

- -

Example:

- -
// `UserModel` is a "Model", a subclass of `mongoose.Model`.
-const UserModel = mongoose.model('User', new Schema({ name: String }));
-
-// You can use a Model to create new documents using `new`:
-const userDoc = new UserModel({ name: 'Foo' });
-await userDoc.save();
-
-// You also use a model to create queries:
-const userFromDb = await UserModel.findOne({ name: 'Foo' });

Model.aggregate()

Parameters
  • [pipeline] -«Array» aggregation pipeline as an array of objects
  • [callback] -«Function»
Returns:
  • «Aggregate»

Performs aggregations on the models collection.

- -

If a callback is passed, the aggregate is executed and a Promise is returned. If a callback is not passed, the aggregate itself is returned.

- -

This function triggers the following middleware.

- -
    -
  • aggregate()
  • -
- -

Example:

- -
// Find the max balance of all accounts
-Users.aggregate([
-  { $group: { _id: null, maxBalance: { $max: '$balance' }}},
-  { $project: { _id: 0, maxBalance: 1 }}
-]).
-then(function (res) {
-  console.log(res); // [ { maxBalance: 98000 } ]
-});
-
-// Or use the aggregation pipeline builder.
-Users.aggregate().
-  group({ _id: null, maxBalance: { $max: '$balance' } }).
-  project('-id maxBalance').
-  exec(function (err, res) {
-    if (err) return handleError(err);
-    console.log(res); // [ { maxBalance: 98 } ]
-  });
- -

NOTE:

- -
    -
  • Arguments are not cast to the model's schema because $project operators allow redefining the "shape" of the documents at any stage of the pipeline, which may leave documents in an incompatible format.
  • -
  • The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned).
  • -
  • Requires MongoDB >= 2.1
  • -

Model.bulkWrite()

Parameters
  • ops -«Array»
    • [ops.insertOne.document] -«Object» The document to insert
    • [opts.updateOne.filter] -«Object» Update the first document that matches this filter
    • [opts.updateOne.upsert=false] -«Boolean» If true, insert a doc if none match
    • [opts.updateOne.arrayFilters] -«Array» The array filters used in update
    • [opts.updateMany.filter] -«Object» Update all the documents that match this filter
    • [opts.updateMany.upsert=false] -«Boolean» If true, insert a doc if no documents match filter
    • [opts.updateMany.arrayFilters] -«Array» The array filters used in update
    • [opts.deleteOne.filter] -«Object» Delete the first document that matches this filter
    • [opts.deleteMany.filter] -«Object» Delete all documents that match this filter
    • [opts.replaceOne.filter] -«Object» Replace the first document that matches this filter
    • [opts.replaceOne.replacement] -«Object» The replacement document
    • [opts.replaceOne.upsert=false] -«Boolean» If true, insert a doc if no documents match filter
  • [options] -«Object»
    • [options.ordered=true] -«Boolean» 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.
    • [options.session=null] -«ClientSession» The session associated with this bulk write. See transactions docs.
  • [callback] -«Function» callback function(error, bulkWriteOpResult) {}
Returns:

Sends multiple insertOne, updateOne, updateMany, replaceOne, deleteOne, and/or deleteMany operations to the MongoDB server in one command. This is faster than sending multiple independent operations (like) if you use create()) because with bulkWrite() there is only one round trip to MongoDB.

- -

Mongoose will perform casting on all operations you provide.

- -

This function does not trigger any middleware, not save() nor update(). If you need to trigger save() middleware for every document use create() instead.

- -

Example:

- -
Character.bulkWrite([
-  {
-    insertOne: {
-      document: {
-        name: 'Eddard Stark',
-        title: 'Warden of the North'
-      }
-    }
-  },
-  {
-    updateOne: {
-      filter: { name: 'Eddard Stark' },
-      // If you were using the MongoDB driver directly, you'd need to do
-      // `update: { $set: { title: ... } }` but mongoose adds $set for
-      // you.
-      update: { title: 'Hand of the King' }
-    }
-  },
-  {
-    deleteOne: {
-      {
-        filter: { name: 'Eddard Stark' }
-      }
-    }
-  }
-]).then(res => {
- // Prints "1 1 1"
- console.log(res.insertedCount, res.modifiedCount, res.deletedCount);
-});
- -

The supported operations are:

- -
    -
  • insertOne
  • -
  • updateOne
  • -
  • updateMany
  • -
  • deleteOne
  • -
  • deleteMany
  • -
  • replaceOne
  • -

Model.count()

Parameters
  • filter -«Object»
  • [callback] -«Function»
Returns:
  • «Query»

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 instead. Otherwise, use the countDocuments() function instead.

- -

Example:

- -
Adventure.count({ type: 'jungle' }, function (err, count) {
-  if (err) ..
-  console.log('there are %d jungle adventures', count);
-});

Model.countDocuments()

Parameters
  • filter -«Object»
  • [callback] -«Function»
Returns:
  • «Query»

Counts number of documents matching filter in a database collection.

- -

Example:

- -
Adventure.countDocuments({ type: 'jungle' }, function (err, count) {
-  console.log('there are %d jungle adventures', count);
-});
- -

If you want to count all documents in a large collection, use the estimatedDocumentCount() function instead. If you call countDocuments({}), MongoDB will always execute a full collection scan and not use any indexes.

- -

The countDocuments() function is similar to count(), but there are a few operators that countDocuments() does not support. Below are the operators that count() supports but countDocuments() does not, and the suggested replacement:

- -

Model.create()

Parameters
  • docs -«Array|Object» Documents to insert, as a spread or array
  • [options] -«Object» Options passed down to save(). To specify options, docs must be an array, not a spread.
  • [callback] -«Function» callback
Returns:
  • «Promise»

Shortcut for saving one or more documents to the database. MyModel.create(docs) does new MyModel(doc).save() for every doc in docs.

- -

This function triggers the following middleware.

- -
    -
  • save()
  • -
- -

Example:

- -
// pass a spread of docs and a callback
-Candy.create({ type: 'jelly bean' }, { type: 'snickers' }, function (err, jellybean, snickers) {
-  if (err) // ...
-});
-
-// pass an array of docs
-var array = [{ type: 'jelly bean' }, { type: 'snickers' }];
-Candy.create(array, function (err, candies) {
-  if (err) // ...
-
-  var jellybean = candies[0];
-  var snickers = candies[1];
-  // ...
-});
-
-// callback is optional; use the returned promise if you like:
-var promise = Candy.create({ type: 'jawbreaker' });
-promise.then(function (jawbreaker) {
-  // ...
-})

Model.createCollection()

Parameters

Create the collection for this model. By default, if no indexes are specified, mongoose will not create the collection for the model until any documents are created. Use this method to create the collection explicitly.

- -

Note 1: You may need to call this before starting a transaction See https://docs.mongodb.com/manual/core/transactions/#transactions-and-operations

- -

Note 2: You don't have to call this if your schema contains index or unique field. In that case, just use Model.init()

- -

Example:

- -
var userSchema = new Schema({ name: String })
-var User = mongoose.model('User', userSchema);
-
-User.createCollection().then(function(collection) {
-  console.log('Collection is created!');
-});

Model.createIndexes()

Parameters
  • [options] -«Object» internal options
  • [cb] -«Function» optional callback
Returns:
  • «Promise»

Similar to ensureIndexes(), except for it uses the createIndex function.


Model.deleteMany()

Parameters
Returns:
  • «Query»

Deletes all of the documents that match conditions from the collection. Behaves like remove(), but deletes all documents that match conditions regardless of the single option.

- -

Example:

- -
Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }, function (err) {});
- -

Note:

- -

Like Model.remove(), this function does not trigger pre('remove') or post('remove') hooks.


Model.deleteOne()

Parameters
Returns:
  • «Query»

Deletes the first document that matches conditions from the collection. Behaves like remove(), but deletes at most one document regardless of the single option.

- -

Example:

- -
Character.deleteOne({ name: 'Eddard Stark' }, function (err) {});
- -

Note:

- -

Like Model.remove(), this function does not trigger pre('remove') or post('remove') hooks.


Model.discriminator()

Parameters
  • name -«String» discriminator model name
  • schema -«Schema» discriminator model schema
  • value -«String» the string stored in the discriminatorKey property

Adds a discriminator type.

- -

Example:

- -
function BaseSchema() {
-  Schema.apply(this, arguments);
-
-  this.add({
-    name: String,
-    createdAt: Date
-  });
-}
-util.inherits(BaseSchema, Schema);
-
-var PersonSchema = new BaseSchema();
-var BossSchema = new BaseSchema({ department: String });
-
-var Person = mongoose.model('Person', PersonSchema);
-var Boss = Person.discriminator('Boss', BossSchema);
-new Boss().__t; // "Boss". `__t` is the default `discriminatorKey`
-
-var employeeSchema = new Schema({ boss: ObjectId });
-var Employee = Person.discriminator('Employee', employeeSchema, 'staff');
-new Employee().__t; // "staff" because of 3rd argument above

Model.distinct()

Parameters
  • field -«String»
  • [conditions] -«Object» optional
  • [callback] -«Function»
Returns:
  • «Query»

Creates a Query for a distinct operation.

- -

Passing a callback executes the query.

- -

Example

- -
Link.distinct('url', { clicks: {$gt: 100}}, function (err, result) {
-  if (err) return handleError(err);
-
-  assert(Array.isArray(result));
-  console.log('unique urls with more than 100 clicks', result);
-})
-
-var query = Link.distinct('url');
-query.exec(callback);

Model.ensureIndexes()

Parameters
  • [options] -«Object» internal options
  • [cb] -«Function» optional callback
Returns:
  • «Promise»

Sends createIndex commands to mongo for each index declared in the schema. The createIndex commands are sent in series.

- -

Example:

- -
Event.ensureIndexes(function (err) {
-  if (err) return handleError(err);
-});
- -

After completion, an index event is emitted on this Model passing an error if one occurred.

- -

Example:

- -
var eventSchema = new Schema({ thing: { type: 'string', unique: true }})
-var Event = mongoose.model('Event', eventSchema);
-
-Event.on('index', function (err) {
-  if (err) console.error(err); // error occurred during index creation
-})
- -

NOTE: It is not recommended that you run this in production. Index creation may impact database performance depending on your load. Use with caution.


Model.estimatedDocumentCount()

Parameters
  • [options] -«Object»
  • [callback] -«Function»
Returns:
  • «Query»

Estimates the number of documents in the MongoDB collection. Faster than using countDocuments() for large collections because estimatedDocumentCount() uses collection metadata rather than scanning the entire collection.

- -

Example:

- -
const numAdventures = Adventure.estimatedDocumentCount();

Model.events

Type:
  • «property»

Event emitter that reports any errors that occurred. Useful for global error handling.

- -

Example:

- -
MyModel.events.on('error', err => console.log(err.message));
-
-// Prints a 'CastError' because of the above handler
-await MyModel.findOne({ _id: 'notanid' }).catch(noop);

Model.exists()

Parameters
  • filter -«Object»
  • [callback] -«Function» callback
Returns:
  • «Promise»

Returns true if at least one document exists in the database that matches the given filter, and false otherwise.

- -

Under the hood, MyModel.exists({ answer: 42 }) is equivalent to MyModel.findOne({ answer: 42 }).select({ _id: 1 }).lean().then(doc => !!doc)

- -

Example:

- -
await Character.deleteMany({});
-await Character.create({ name: 'Jean-Luc Picard' });
-
-await Character.exists({ name: /picard/i }); // true
-await Character.exists({ name: /riker/i }); // false
- -

This function triggers the following middleware.

- -
    -
  • findOne()
  • -

Model.find()

Parameters
Returns:
  • «Query»

Finds documents

- -

The conditions are cast to their respective SchemaTypes before the command is sent.

- -

Examples:

- -
// named john and at least 18
-MyModel.find({ name: 'john', age: { $gte: 18 }});
-
-// executes, passing results to callback
-MyModel.find({ name: 'john', age: { $gte: 18 }}, function (err, docs) {});
-
-// executes, name LIKE john and only selecting the "name" and "friends" fields
-MyModel.find({ name: /john/i }, 'name friends', function (err, docs) { })
-
-// passing options
-MyModel.find({ name: /john/i }, null, { skip: 10 })
-
-// passing options and executes
-MyModel.find({ name: /john/i }, null, { skip: 10 }, function (err, docs) {});
-
-// executing a query explicitly
-var query = MyModel.find({ name: /john/i }, null, { skip: 10 })
-query.exec(function (err, docs) {});
-
-// using the promise returned from executing a query
-var query = MyModel.find({ name: /john/i }, null, { skip: 10 });
-var promise = query.exec();
-promise.addBack(function (err, docs) {});

Model.findById()

Parameters
Returns:
  • «Query»

Finds a single document by its _id field. findById(id) is almost* equivalent to findOne({ _id: id }). If you want to query by a document's _id, use findById() instead of findOne().

- -

The id is cast based on the Schema before sending the command.

- -

This function triggers the following middleware.

- -
    -
  • findOne()
  • -
- -

* Except for how it treats undefined. If you use findOne(), you'll see that findOne(undefined) and findOne({ _id: undefined }) are equivalent to findOne({}) and return arbitrary documents. However, mongoose translates findById(undefined) into findOne({ _id: null }).

- -

Example:

- -
// find adventure by id and execute
-Adventure.findById(id, function (err, adventure) {});
-
-// same as above
-Adventure.findById(id).exec(callback);
-
-// select only the adventures name and length
-Adventure.findById(id, 'name length', function (err, adventure) {});
-
-// same as above
-Adventure.findById(id, 'name length').exec(callback);
-
-// include all properties except for `length`
-Adventure.findById(id, '-length').exec(function (err, adventure) {});
-
-// passing options (in this case return the raw js objects, not mongoose documents by passing `lean`
-Adventure.findById(id, 'name', { lean: true }, function (err, doc) {});
-
-// same as above
-Adventure.findById(id, 'name').lean().exec(function (err, doc) {});

Model.findByIdAndDelete()

Parameters
Returns:
  • «Query»

Issue a MongoDB findOneAndDelete() command by a document's _id field. In other words, findByIdAndDelete(id) is a shorthand for findOneAndDelete({ _id: id }).

- -

This function triggers the following middleware.

- -
    -
  • findOneAndDelete()
  • -

Model.findByIdAndRemove()

Parameters
Returns:
  • «Query»

Issue a mongodb findAndModify remove command by a document's _id field. findByIdAndRemove(id, ...) is equivalent to findOneAndRemove({ _id: id }, ...).

- -

Finds a matching document, removes it, passing the found document (if any) to the callback.

- -

Executes the query if callback is passed.

- -

This function triggers the following middleware.

- -
    -
  • findOneAndRemove()
  • -
- -

Options:

- -
    -
  • sort: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
  • -
  • select: sets the document fields to return
  • -
  • rawResult: if true, returns the raw result from the MongoDB driver
  • -
  • strict: overwrites the schema's strict mode option for this update
  • -
- -

Examples:

- -
A.findByIdAndRemove(id, options, callback) // executes
-A.findByIdAndRemove(id, options)  // return Query
-A.findByIdAndRemove(id, callback) // executes
-A.findByIdAndRemove(id) // returns Query
-A.findByIdAndRemove()           // returns Query

Model.findByIdAndUpdate()

Parameters
  • id -«Object|Number|String» value of _id to query by
  • [update] -«Object»
  • [options] -«Object» optional see Query.prototype.setOptions()
    • [options.lean] -«Object» if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See Query.lean() and the Mongoose lean tutorial.
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
  • [callback] -«Function»
Returns:
  • «Query»

Issues a mongodb findAndModify update command by a document's _id field. findByIdAndUpdate(id, ...) is equivalent to findOneAndUpdate({ _id: id }, ...).

- -

Finds a matching document, updates it according to the update arg, passing any options, and returns the found document (if any) to the callback. The query executes if callback is passed.

- -

This function triggers the following middleware.

- -
    -
  • findOneAndUpdate()
  • -
- -

Options:

- -
    -
  • new: bool - true to return the modified document rather than the original. defaults to false
  • -
  • upsert: bool - creates the object if it doesn't exist. defaults to false.
  • -
  • runValidators: if true, runs update validators on this command. Update validators validate the update operation against the model's schema.
  • -
  • setDefaultsOnInsert: if this and upsert are true, mongoose will apply the defaults specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on MongoDB's $setOnInsert operator.
  • -
  • sort: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
  • -
  • select: sets the document fields to return
  • -
  • rawResult: if true, returns the raw result from the MongoDB driver
  • -
  • strict: overwrites the schema's strict mode option for this update
  • -
- -

Examples:

- -
A.findByIdAndUpdate(id, update, options, callback) // executes
-A.findByIdAndUpdate(id, update, options)  // returns Query
-A.findByIdAndUpdate(id, update, callback) // executes
-A.findByIdAndUpdate(id, update)           // returns Query
-A.findByIdAndUpdate()                     // returns Query
- -

Note:

- -

All top level update keys which are not atomic operation names are treated as set operations:

- -

Example:

- -
Model.findByIdAndUpdate(id, { name: 'jason bourne' }, options, callback)
-
-// is sent as
-Model.findByIdAndUpdate(id, { $set: { name: 'jason bourne' }}, options, callback)
- -

This helps prevent accidentally overwriting your document with { name: 'jason bourne' }.

- -

Note:

- -

Values are cast to their appropriate types when using the findAndModify helpers. However, the below are not executed by default.

- -
    -
  • defaults. Use the setDefaultsOnInsert option to override.
  • -
- -

findAndModify helpers support limited validation. You can enable these by setting the runValidators options, respectively.

- -

If you need full-fledged validation, use the traditional approach of first retrieving the document.

- -
Model.findById(id, function (err, doc) {
-  if (err) ..
-  doc.name = 'jason bourne';
-  doc.save(callback);
-});

Model.findOne()

Parameters
Returns:
  • «Query»

Finds one document.

- -

The conditions are cast to their respective SchemaTypes before the command is sent.

- -

Note: conditions is optional, and if conditions is null or undefined, mongoose will send an empty findOne command to MongoDB, which will return an arbitrary document. If you're querying by _id, use findById() instead.

- -

Example:

- -
// find one iphone adventures - iphone adventures??
-Adventure.findOne({ type: 'iphone' }, function (err, adventure) {});
-
-// same as above
-Adventure.findOne({ type: 'iphone' }).exec(function (err, adventure) {});
-
-// select only the adventures name
-Adventure.findOne({ type: 'iphone' }, 'name', function (err, adventure) {});
-
-// same as above
-Adventure.findOne({ type: 'iphone' }, 'name').exec(function (err, adventure) {});
-
-// specify options, in this case lean
-Adventure.findOne({ type: 'iphone' }, 'name', { lean: true }, callback);
-
-// same as above
-Adventure.findOne({ type: 'iphone' }, 'name', { lean: true }).exec(callback);
-
-// chaining findOne queries (same as above)
-Adventure.findOne({ type: 'iphone' }).select('name').lean().exec(callback);

Model.findOneAndDelete()

Parameters
Returns:
  • «Query»

Issue a MongoDB findOneAndDelete() command.

- -

Finds a matching document, removes it, and passes the found document (if any) to the callback.

- -

Executes the query if callback is passed.

- -

This function triggers the following middleware.

- -
    -
  • findOneAndDelete()
  • -
- -

This function differs slightly from Model.findOneAndRemove() in that findOneAndRemove() becomes a MongoDB findAndModify() command, as opposed to a findOneAndDelete() command. For most mongoose use cases, this distinction is purely pedantic. You should use findOneAndDelete() unless you have a good reason not to.

- -

Options:

- -
    -
  • 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
  • -
  • select: sets the document fields to return
  • -
  • projection: like select, it determines which fields to return, ex. { projection: { _id: 0 } }
  • -
  • rawResult: if true, returns the raw result from the MongoDB driver
  • -
  • strict: overwrites the schema's strict mode option for this update
  • -
- -

Examples:

- -
A.findOneAndDelete(conditions, options, callback) // executes
-A.findOneAndDelete(conditions, options)  // return Query
-A.findOneAndDelete(conditions, callback) // executes
-A.findOneAndDelete(conditions) // returns Query
-A.findOneAndDelete()           // returns Query
- -

Values are cast to their appropriate types when using the findAndModify helpers. However, the below are not executed by default.

- -
    -
  • defaults. Use the setDefaultsOnInsert option to override.
  • -
- -

findAndModify helpers support limited validation. You can enable these by setting the runValidators options, respectively.

- -

If you need full-fledged validation, use the traditional approach of first retrieving the document.

- -
Model.findById(id, function (err, doc) {
-  if (err) ..
-  doc.name = 'jason bourne';
-  doc.save(callback);
-});

Model.findOneAndRemove()

Parameters
Returns:
  • «Query»

Issue a mongodb findAndModify remove command.

- -

Finds a matching document, removes it, passing the found document (if any) to the callback.

- -

Executes the query if callback is passed.

- -

This function triggers the following middleware.

- -
    -
  • findOneAndRemove()
  • -
- -

Options:

- -
    -
  • 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
  • -
  • select: sets the document fields to return
  • -
  • projection: like select, it determines which fields to return, ex. { projection: { _id: 0 } }
  • -
  • rawResult: if true, returns the raw result from the MongoDB driver
  • -
  • strict: overwrites the schema's strict mode option for this update
  • -
- -

Examples:

- -
A.findOneAndRemove(conditions, options, callback) // executes
-A.findOneAndRemove(conditions, options)  // return Query
-A.findOneAndRemove(conditions, callback) // executes
-A.findOneAndRemove(conditions) // returns Query
-A.findOneAndRemove()           // returns Query
- -

Values are cast to their appropriate types when using the findAndModify helpers. However, the below are not executed by default.

- -
    -
  • defaults. Use the setDefaultsOnInsert option to override.
  • -
- -

findAndModify helpers support limited validation. You can enable these by setting the runValidators options, respectively.

- -

If you need full-fledged validation, use the traditional approach of first retrieving the document.

- -
Model.findById(id, function (err, doc) {
-  if (err) ..
-  doc.name = 'jason bourne';
-  doc.save(callback);
-});

Model.findOneAndReplace()

Parameters
  • filter -«Object» Replace the first document that matches this filter
  • [replacement] -«Object» Replace with this document
  • [options] -«Object» optional see Query.prototype.setOptions()
    • [options.lean] -«Object» if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See Query.lean().
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
  • [callback] -«Function»
Returns:
  • «Query»

Issue a MongoDB findOneAndReplace() command.

- -

Finds a matching document, replaces it with the provided doc, and passes the returned doc to the callback.

- -

Executes the query if callback is passed.

- -

This function triggers the following query middleware.

- -
    -
  • findOneAndReplace()
  • -
- -

Options:

- -
    -
  • 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
  • -
  • select: sets the document fields to return
  • -
  • projection: like select, it determines which fields to return, ex. { projection: { _id: 0 } }
  • -
  • rawResult: if true, returns the raw result from the MongoDB driver
  • -
  • strict: overwrites the schema's strict mode option for this update
  • -
- -

Examples:

- -
A.findOneAndReplace(conditions, options, callback) // executes
-A.findOneAndReplace(conditions, options)  // return Query
-A.findOneAndReplace(conditions, callback) // executes
-A.findOneAndReplace(conditions) // returns Query
-A.findOneAndReplace()           // returns Query
- -

Values are cast to their appropriate types when using the findAndModify helpers. However, the below are not executed by default.

- -
    -
  • defaults. Use the setDefaultsOnInsert option to override.
  • -

Model.findOneAndUpdate()

Parameters
  • [conditions] -«Object»
  • [update] -«Object»
  • [options] -«Object» optional see Query.prototype.setOptions()
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
  • [callback] -«Function»
Returns:
  • «Query»

Issues a mongodb findAndModify update command.

- -

Finds a matching document, updates it according to the update arg, passing any options, and returns the found document (if any) to the callback. The query executes if callback is passed else a Query object is returned.

- -

Options:

- -
    -
  • new: bool - if true, return the modified document rather than the original. defaults to false (changed in 4.0)
  • -
  • upsert: bool - creates the object if it doesn't exist. defaults to false.
  • -
  • fields: {Object|String} - Field selection. Equivalent to .select(fields).findOneAndUpdate()
  • -
  • maxTimeMS: puts a time limit on the query - requires mongodb >= 2.6.0
  • -
  • sort: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
  • -
  • runValidators: if true, runs update validators on this command. Update validators validate the update operation against the model's schema.
  • -
  • setDefaultsOnInsert: if this and upsert are true, mongoose will apply the defaults specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on MongoDB's $setOnInsert operator.
  • -
  • rawResult: if true, returns the raw result from the MongoDB driver
  • -
  • strict: overwrites the schema's strict mode option for this update
  • -
- -

Examples:

- -
A.findOneAndUpdate(conditions, update, options, callback) // executes
-A.findOneAndUpdate(conditions, update, options)  // returns Query
-A.findOneAndUpdate(conditions, update, callback) // executes
-A.findOneAndUpdate(conditions, update)           // returns Query
-A.findOneAndUpdate()                             // returns Query
- -

Note:

- -

All top level update keys which are not atomic operation names are treated as set operations:

- -

Example:

- -
var query = { name: 'borne' };
-Model.findOneAndUpdate(query, { name: 'jason bourne' }, options, callback)
-
-// is sent as
-Model.findOneAndUpdate(query, { $set: { name: 'jason bourne' }}, options, callback)
- -

This helps prevent accidentally overwriting your document with { name: 'jason bourne' }.

- -

Note:

- -

Values are cast to their appropriate types when using the findAndModify helpers. However, the below are not executed by default.

- -
    -
  • defaults. Use the setDefaultsOnInsert option to override.
  • -
- -

findAndModify helpers support limited validation. You can enable these by setting the runValidators options, respectively.

- -

If you need full-fledged validation, use the traditional approach of first retrieving the document.

- -
Model.findById(id, function (err, doc) {
-  if (err) ..
-  doc.name = 'jason bourne';
-  doc.save(callback);
-});

Model.geoSearch()

Parameters
  • conditions -«Object» an object that specifies the match condition (required)
  • options -«Object» for the geoSearch, some (near, maxDistance) are required
    • [options.lean] -«Object|Boolean» if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See Query.lean() and the Mongoose lean tutorial.
  • [callback] -«Function» optional callback
Returns:
  • «Promise»

Implements $geoSearch functionality for Mongoose

- -

This function does not trigger any middleware

- -

Example:

- -
var options = { near: [10, 10], maxDistance: 5 };
-Locations.geoSearch({ type : "house" }, options, function(err, res) {
-  console.log(res);
-});
- -

Options:

- -
    -
  • near {Array} x,y point to search for
  • -
  • maxDistance {Number} the maximum distance from the point near that a result can be
  • -
  • limit {Number} The maximum number of results to return
  • -
  • lean {Object|Boolean} return the raw object instead of the Mongoose Model
  • -

Model.hydrate()

Parameters
  • obj -«Object»
Returns:
  • «Model» document instance

Shortcut for creating a new Document from existing raw data, pre-saved in the DB. The document returned has no paths marked as modified initially.

- -

Example:

- -
// hydrate previous data into a Mongoose document
-var mongooseCandy = Candy.hydrate({ _id: '54108337212ffb6d459f854c', type: 'jelly bean' });

Model.init()

Parameters
  • [callback] -«Function»

This function is responsible for building indexes, unless autoIndex is turned off.

- -

Mongoose calls this function automatically when a model is created using mongoose.model() or * connection.model(), so you don't need to call it. This function is also idempotent, so you may call it to get back a promise that will resolve when your indexes are finished building as an alternative to MyModel.on('index')

- -

Example:

- -
var eventSchema = new Schema({ thing: { type: 'string', unique: true }})
-// This calls `Event.init()` implicitly, so you don't need to call
-// `Event.init()` on your own.
-var Event = mongoose.model('Event', eventSchema);
-
-Event.init().then(function(Event) {
-  // You can also use `Event.on('index')` if you prefer event emitters
-  // over promises.
-  console.log('Indexes are done building!');
-});

Model.insertMany()

Parameters
  • doc(s) -«Array|Object|*»
  • [options] -«Object» see the mongodb driver options
  • [options.ordered -«Boolean» = true] if true, will fail fast on the first error encountered. If false, will insert all the documents it can and report errors later. An insertMany() with ordered = false is called an "unordered" insertMany().
  • [options.rawResult -«Boolean» = false] if false, the returned promise resolves to the documents that passed mongoose document validation. If true, will return the raw result from the MongoDB driver with a mongoose property that contains validationErrors if this is an unordered insertMany.
  • [callback] -«Function» callback
Returns:
  • «Promise»

Shortcut for validating an array of documents and inserting them into MongoDB if they're all valid. This function is faster than .create() because it only sends one operation to the server, rather than one for each document.

- -

Mongoose always validates each document before sending insertMany to MongoDB. So if one document has a validation error, no documents will be saved, unless you set the ordered option to false.

- -

This function does not trigger save middleware.

- -

This function triggers the following middleware.

- -
    -
  • insertMany()
  • -
- -

Example:

- -
var arr = [{ name: 'Star Wars' }, { name: 'The Empire Strikes Back' }];
-Movies.insertMany(arr, function(error, docs) {});

Model.listIndexes()

Parameters
  • [cb] -«Function» optional callback
Returns:
  • «Promise,undefined» Returns undefined if callback is specified, returns a promise if no callback.

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 and if you build indexes manually.


Model.mapReduce()

Parameters
  • o -«Object» an object specifying map-reduce options
  • [callback] -«Function» optional callback
Returns:
  • «Promise»

Executes a mapReduce command.

- -

o is an object specifying all mapReduce options as well as the map and reduce functions. All options are delegated to the driver implementation. See node-mongodb-native mapReduce() documentation for more detail about options.

- -

This function does not trigger any middleware.

- -

Example:

- -
var o = {};
-// `map()` and `reduce()` are run on the MongoDB server, not Node.js,
-// these functions are converted to strings
-o.map = function () { emit(this.name, 1) };
-o.reduce = function (k, vals) { return vals.length };
-User.mapReduce(o, function (err, results) {
-  console.log(results)
-})
- -

Other options:

- -
    -
  • query {Object} query filter object.
  • -
  • sort {Object} sort input objects using this key
  • -
  • limit {Number} max number of documents
  • -
  • keeptemp {Boolean, default:false} keep temporary data
  • -
  • finalize {Function} finalize function
  • -
  • scope {Object} scope variables exposed to map/reduce/finalize during execution
  • -
  • jsMode {Boolean, default:false} it is possible to make the execution stay in JS. Provided in MongoDB > 2.0.X
  • -
  • verbose {Boolean, default:false} provide statistics on job execution time.
  • -
  • readPreference {String}
  • -
  • out* {Object, default: {inline:1}} sets the output target for the map reduce job.
  • -
- -

* out options:

- -
    -
  • {inline:1} the results are returned in an array
  • -
  • {replace: 'collectionName'} add the results to collectionName: the results replace the collection
  • -
  • {reduce: 'collectionName'} add the results to collectionName: if dups are detected, uses the reducer / finalize functions
  • -
  • {merge: 'collectionName'} add the results to collectionName: if dups exist the new docs overwrite the old
  • -
- -

If options.out is set to replace, merge, or reduce, a Model instance is returned that can be used for further querying. Queries run against this model are all executed with the lean option; meaning only the js object is returned and no Mongoose magic is applied (getters, setters, etc).

- -

Example:

- -
var o = {};
-// You can also define `map()` and `reduce()` as strings if your
-// linter complains about `emit()` not being defined
-o.map = 'function () { emit(this.name, 1) }';
-o.reduce = 'function (k, vals) { return vals.length }';
-o.out = { replace: 'createdCollectionNameForResults' }
-o.verbose = true;
-
-User.mapReduce(o, function (err, model, stats) {
-  console.log('map reduce took %d ms', stats.processtime)
-  model.find().where('value').gt(10).exec(function (err, docs) {
-    console.log(docs);
-  });
-})
-
-// `mapReduce()` returns a promise. However, ES6 promises can only
-// resolve to exactly one value,
-o.resolveToObject = true;
-var promise = User.mapReduce(o);
-promise.then(function (res) {
-  var model = res.model;
-  var stats = res.stats;
-  console.log('map reduce took %d ms', stats.processtime)
-  return model.find().where('value').gt(10).exec();
-}).then(function (docs) {
-   console.log(docs);
-}).then(null, handleError).end()

Model.populate()

Parameters
  • docs -«Document|Array» Either a single document or array of documents to populate.
  • options -«Object» A hash of key/val (path, options) used for population.
    • [options.retainNullValues=false] -«boolean» by default, Mongoose removes null and undefined values from populated arrays. Use this option to make populate() retain null and undefined array entries.
    • [options.getters=false] -«boolean» 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.
    • [options.clone=false] -«boolean» 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.
    • [options.match=null] -«Object|Function» Add an additional filter to the populate query. Can be a filter object containing MongoDB query syntax, or a function that returns a filter object.
    • [options.skipInvalidIds=false] -«Boolean» 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.
  • [callback(err,doc)] -«Function» Optional callback, executed upon completion. Receives err and the doc(s).
Returns:
  • «Promise»

Populates document references.

- -

Available top-level options:

- -
    -
  • path: space delimited path(s) to populate
  • -
  • select: optional fields to select
  • -
  • match: optional query conditions to match
  • -
  • model: optional name of the model to use for population
  • -
  • options: optional query options like sort, limit, etc
  • -
  • justOne: optional boolean, if true Mongoose will always set path to an array. Inferred from schema by default.
  • -
- -

Examples:

- -
// populates a single object
-User.findById(id, function (err, user) {
-  var opts = [
-    { path: 'company', match: { x: 1 }, select: 'name' },
-    { path: 'notes', options: { limit: 10 }, model: 'override' }
-  ];
-
-  User.populate(user, opts, function (err, user) {
-    console.log(user);
-  });
-});
-
-// populates an array of objects
-User.find(match, function (err, users) {
-  var opts = [{ path: 'company', match: { x: 1 }, select: 'name' }];
-
-  var promise = User.populate(users, opts);
-  promise.then(console.log).end();
-})
-
-// imagine a Weapon model exists with two saved documents:
-//   { _id: 389, name: 'whip' }
-//   { _id: 8921, name: 'boomerang' }
-// and this schema:
-// new Schema({
-//   name: String,
-//   weapon: { type: ObjectId, ref: 'Weapon' }
-// });
-
-var user = { name: 'Indiana Jones', weapon: 389 };
-Weapon.populate(user, { path: 'weapon', model: 'Weapon' }, function (err, user) {
-  console.log(user.weapon.name); // whip
-})
-
-// populate many plain objects
-var users = [{ name: 'Indiana Jones', weapon: 389 }]
-users.push({ name: 'Batman', weapon: 8921 })
-Weapon.populate(users, { path: 'weapon' }, function (err, users) {
-  users.forEach(function (user) {
-    console.log('%s uses a %s', users.name, user.weapon.name)
-    // Indiana Jones uses a whip
-    // Batman uses a boomerang
-  });
-});
-// Note that we didn't need to specify the Weapon model because
-// it is in the schema's ref

Model.prototype.$where

Type:
  • «property»

Additional properties to attach to the query when calling save() and isNew is false.


Model.prototype.$where()

Parameters
  • argument -«String|Function» is a javascript string or anonymous function
Returns:
  • «Query»

Creates a Query and specifies a $where condition.

- -

Sometimes you need to query for things in mongodb using a JavaScript expression. You can do so via find({ $where: javascript }), or you can use the mongoose shortcut method $where via a Query chain or from your mongoose Model.

- -
Blog.$where('this.username.indexOf("val") !== -1').exec(function (err, docs) {});

Model.prototype.base

Type:
  • «property»

Base Mongoose instance the model uses.


Model.prototype.baseModelName

Type:
  • «property»

If this is a discriminator model, baseModelName is the name of the base model.


Model.prototype.collection

Type:
  • «property»

Collection the model uses.

- -

This property is read-only. Modifying this property is a no-op.


Model.prototype.db

Type:
  • «property»

Connection the model uses.


Model.prototype.delete

Type:
  • «property»

Alias for remove


Model.prototype.deleteOne()

Parameters
  • [fn] -«function(err|product)» optional callback
Returns:
  • «Promise» Promise

Removes this document from the db. Equivalent to .remove().

- -

Example:

- -
product = await product.deleteOne();
-await Product.findById(product._id); // null

Model.prototype.discriminators

Type:
  • «property»

Registered discriminators for this model.


Model.prototype.increment()

Signal that we desire an increment of this documents version.

- -

Example:

- -
Model.findById(id, function (err, doc) {
-  doc.increment();
-  doc.save(function (err) { .. })
-})

Model.prototype.model()

Parameters
  • name -«String» model name

Returns another Model instance.

- -

Example:

- -
var doc = new Tank;
-doc.model('User').findById(id, callback);

Model.prototype.modelName

Type:
  • «property»

The name of the model


Model.prototype.remove()

Parameters
  • [fn] -«function(err|product)» optional callback
Returns:
  • «Promise» Promise

Removes this document from the db.

- -

Example:

- -
product.remove(function (err, product) {
-  if (err) return handleError(err);
-  Product.findById(product._id, function (err, product) {
-    console.log(product) // null
-  })
-})
- -

As an extra measure of flow control, remove will return a Promise (bound to fn if passed) so it could be chained, or hooked to recieve errors

- -

Example:

- -
product.remove().then(function (product) {
-   ...
-}).catch(function (err) {
-   assert.ok(err)
-})

Model.prototype.save()

Parameters
Returns:
  • «Promise,undefined» Returns undefined if used with callback or a Promise otherwise.

Saves this document.

- -

Example:

- -
product.sold = Date.now();
-product = await product.save();
- -

If save is successful, the returned promise will fulfill with the document saved.

- -

Example:

- -
const newProduct = await product.save();
-newProduct === product; // true

Model.prototype.schema

Type:
  • «property»

Schema the model uses.


Model.remove()

Parameters
  • conditions -«Object»
  • [callback] -«Function»
Returns:
  • «Query»

Removes all documents that match conditions from the collection. To remove just the first document that matches conditions, set the single option to true.

- -

Example:

- -
const res = await Character.remove({ name: 'Eddard Stark' });
-res.deletedCount; // Number of documents removed
- -

Note:

- -

This method sends a remove command directly to MongoDB, no Mongoose documents are involved. Because no Mongoose documents are involved, Mongoose does not execute document middleware.


Model.replaceOne()

Parameters
  • filter -«Object»
  • doc -«Object»
  • [options] -«Object» optional see Query.prototype.setOptions()
    • [options.upsert=false] -«Boolean» if true, and no documents found, insert a new document
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [options.timestamps=null] -«Boolean» If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
  • [callback] -«Function» function(error, res) {} where res has 3 properties: n, nModified, ok.
Returns:
  • «Query»

Same as update(), except MongoDB replace the existing document with the given document (no atomic operators like $set).

- -

Example:

- -
const res = await Person.replaceOne({ _id: 24601 }, { name: 'Jean Valjean' });
-res.n; // Number of documents matched
-res.nModified; // Number of documents modified
- -

This function triggers the following middleware.

- -
    -
  • replaceOne()
  • -

Model.startSession()

Parameters
  • [options] -«Object» see the mongodb driver options
    • [options.causalConsistency=true] -«Boolean» set to false to disable causal consistency
  • [callback] -«Function»
Returns:
  • «Promise<ClientSession>» promise that resolves to a MongoDB driver ClientSession

Requires MongoDB >= 3.6.0. Starts a MongoDB session for benefits like causal consistency, retryable writes, and transactions.

- -

Calling MyModel.startSession() is equivalent to calling MyModel.db.startSession().

- -

This function does not trigger any middleware.

- -

Example:

- -
const session = await Person.startSession();
-let doc = await Person.findOne({ name: 'Ned Stark' }, null, { session });
-await doc.remove();
-// `doc` will always be null, even if reading from a replica set
-// secondary. Without causal consistency, it is possible to
-// get a doc back from the below query if the query reads from a
-// secondary that is experiencing replication lag.
-doc = await Person.findOne({ name: 'Ned Stark' }, null, { session, readPreference: 'secondary' });

Model.syncIndexes()

Parameters
  • [options] -«Object» options to pass to ensureIndexes()
  • [callback] -«Function» optional callback
Returns:
  • «Promise,undefined» Returns undefined if callback is specified, returns a promise if no callback.

Makes the indexes in MongoDB match the indexes defined in this model's schema. This function will drop any indexes that are not defined in the model's schema except the _id index, and build any indexes that are in your schema but not in MongoDB.

- -

See the introductory blog post for more information.

- -

Example:

- -
const schema = new Schema({ name: { type: String, unique: true } });
-const Customer = mongoose.model('Customer', schema);
-await Customer.createIndex({ age: 1 }); // Index is not in schema
-// Will drop the 'age' index and create an index on `name`
-await Customer.syncIndexes();

Model.translateAliases()

Parameters
  • raw -«Object» fields/conditions that may contain aliased keys
Returns:
  • «Object» the translated 'pure' fields/conditions

Translate any aliases fields/conditions so the final query or document object is pure

- -

Example:

- -
Character
-  .find(Character.translateAliases({
-    '名': 'Eddard Stark' // Alias for 'name'
-  })
-  .exec(function(err, characters) {})
- -

Note:

- -

Only translate arguments of object type anything else is returned raw


Model.update()

Parameters
  • filter -«Object»
  • doc -«Object»
  • [options] -«Object» optional see Query.prototype.setOptions()
    • [options.upsert=false] -«Boolean» if true, and no documents found, insert a new document
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [options.multi=false] -«Boolean» whether multiple documents should be updated or just the first one that matches filter.
    • [options.runValidators=false] -«Boolean» if true, runs update validators on this command. Update validators validate the update operation against the model's schema.
    • [options.setDefaultsOnInsert=false] -«Boolean» if this and upsert are true, mongoose will apply the defaults specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on MongoDB's $setOnInsert operator.
    • [options.timestamps=null] -«Boolean» If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
    • [options.overwrite=false] -«Boolean» By default, if you don't include any update operators in doc, Mongoose will wrap doc in $set for you. This prevents you from accidentally overwriting the document. This option tells Mongoose to skip adding $set.
  • [callback] -«Function» params are (error, writeOpResult)
  • [callback] -«Function»
Returns:
  • «Query»

Updates one document in the database without returning it.

- -

This function triggers the following middleware.

- -
    -
  • update()
  • -
- -

Examples:

- -
MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn);
-
-const res = await MyModel.update({ name: 'Tobi' }, { ferret: true });
-res.n; // Number of documents that matched `{ name: 'Tobi' }`
-// Number of documents that were changed. If every doc matched already
-// had `ferret` set to `true`, `nModified` will be 0.
-res.nModified;
- -

Valid options:

- -
    -
  • strict (boolean): overrides the schema-level strict option for this update
  • -
  • upsert (boolean): whether to create the doc if it doesn't match (false)
  • -
  • writeConcern (object): sets the write concern for replica sets. Overrides the schema-level write concern
  • -
  • omitUndefined (boolean): If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
  • -
  • multi (boolean): whether multiple documents should be updated (false)
  • -
  • runValidators: if true, runs update validators on this command. Update validators validate the update operation against the model's schema.
  • -
  • setDefaultsOnInsert (boolean): if this and upsert are true, mongoose will apply the defaults specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on MongoDB's $setOnInsert operator.
  • -
  • timestamps (boolean): If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
  • -
  • overwrite (boolean): disables update-only mode, allowing you to overwrite the doc (false)
  • -
- -

All update values are cast to their appropriate SchemaTypes before being sent.

- -

The callback function receives (err, rawResponse).

- -
    -
  • err is the error if any occurred
  • -
  • rawResponse is the full response from Mongo
  • -
- -

Note:

- -

All top level keys which are not atomic operation names are treated as set operations:

- -

Example:

- -
var query = { name: 'borne' };
-Model.update(query, { name: 'jason bourne' }, options, callback);
-
-// is sent as
-Model.update(query, { $set: { name: 'jason bourne' }}, options, function(err, res));
-// if overwrite option is false. If overwrite is true, sent without the $set wrapper.
-
- -

This helps prevent accidentally overwriting all documents in your collection with { name: 'jason bourne' }.

- -

Note:

- -

Be careful to not use an existing model instance for the update clause (this won't work and can cause weird behavior like infinite loops). Also, ensure that the update clause does not have an _id property, which causes Mongo to return a "Mod on _id not allowed" error.

- -

Note:

- -

Mongoose casts values and runs setters when using update. The following features are not applied by default.

- - - -

If you need document middleware and fully-featured validation, load the document first and then use save().

- -
Model.findOne({ name: 'borne' }, function (err, doc) {
-  if (err) ..
-  doc.name = 'jason bourne';
-  doc.save(callback);
-})

Model.updateMany()

Parameters
  • filter -«Object»
  • doc -«Object»
  • [options] -«Object» optional see Query.prototype.setOptions()
    • [options.upsert=false] -«Boolean» if true, and no documents found, insert a new document
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [options.timestamps=null] -«Boolean» If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
  • [callback] -«Function» function(error, res) {} where res has 3 properties: n, nModified, ok.
Returns:
  • «Query»

Same as update(), except MongoDB will update all documents that match filter (as opposed to just the first one) regardless of the value of the multi option.

- -

Note updateMany will not fire update middleware. Use pre('updateMany') and post('updateMany') instead.

- -

Example:

- -
const res = await Person.updateMany({ name: /Stark$/ }, { isDeleted: true });
-res.n; // Number of documents matched
-res.nModified; // Number of documents modified
- -

This function triggers the following middleware.

- -
    -
  • updateMany()
  • -

Model.updateOne()

Parameters
  • filter -«Object»
  • doc -«Object»
  • [options] -«Object» optional see Query.prototype.setOptions()
    • [options.upsert=false] -«Boolean» if true, and no documents found, insert a new document
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [options.timestamps=null] -«Boolean» If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
  • [callback] -«Function» params are (error, writeOpResult)
Returns:
  • «Query»

Same as update(), except it does not support the multi or overwrite options.

- -
    -
  • MongoDB will update only the first document that matches filter regardless of the value of the multi option.
  • -
  • Use replaceOne() if you want to overwrite an entire document rather than using atomic operators like $set.
  • -
- -

Example:

- -
const res = await Person.updateOne({ name: 'Jean-Luc Picard' }, { ship: 'USS Enterprise' });
-res.n; // Number of documents matched
-res.nModified; // Number of documents modified
- -

This function triggers the following middleware.

- -
    -
  • updateOne()
  • -

Model.watch()

Parameters
Returns:
  • «ChangeStream» mongoose-specific change stream wrapper, inherits from EventEmitter

Requires a replica set running MongoDB >= 3.6.0. Watches the underlying collection for changes using MongoDB change streams.

- -

This function does not trigger any middleware. In particular, it does not trigger aggregate middleware.

- -

The ChangeStream object is an event emitter that emits the following events

- -
    -
  • 'change': A change occurred, see below example
  • -
  • 'error': An unrecoverable error occurred. In particular, change streams currently error out if they lose connection to the replica set primary. Follow this GitHub issue for updates.
  • -
  • 'end': Emitted if the underlying stream is closed
  • -
  • 'close': Emitted if the underlying stream is closed
  • -
- -

Example:

- -
const doc = await Person.create({ name: 'Ned Stark' });
-const changeStream = Person.watch().on('change', change => console.log(change));
-// Will print from the above `console.log()`:
-// { _id: { _data: ... },
-//   operationType: 'delete',
-//   ns: { db: 'mydb', coll: 'Person' },
-//   documentKey: { _id: 5a51b125c5500f5aa094c7bd } }
-await doc.remove();

Model.where()

Parameters
  • path -«String»
  • [val] -«Object» optional value
Returns:
  • «Query»

Creates a Query, applies the passed conditions, and returns the Query.

- -

For example, instead of writing:

- -
User.find({age: {$gte: 21, $lte: 65}}, callback);
- -

we can instead write:

- -
User.where('age').gte(21).lte(65).exec(callback);
- -

Since the Query class also supports where you can continue chaining

- -
User
-.where('age').gte(21).lte(65)
-.where('name', /^b/i)
-... etc

function Object() { [native code] }.prototype.inspect()

Helper for console.log


Query


Query()

Parameters
  • [options] -«Object»
  • [model] -«Object»
  • [conditions] -«Object»
  • [collection] -«Object» Mongoose collection

Query constructor used for building queries. You do not need to instantiate a Query directly. Instead use Model functions like Model.find().

- -

Example:

- -
const query = MyModel.find(); // `query` is an instance of `Query`
-query.setOptions({ lean : true });
-query.collection(MyModel.collection);
-query.where('age').gte(21).exec(callback);
-
-// You can instantiate a query directly. There is no need to do
-// this unless you're an advanced user with a very good reason to.
-const query = new mongoose.Query();

Query.prototype.$where()

Parameters
  • js -«String|Function» javascript string or function
Returns:
  • «Query» this

Specifies a javascript function or expression to pass to MongoDBs query system.

- -

Example

- -
query.$where('this.comments.length === 10 || this.name.length === 5')
-
-// or
-
-query.$where(function () {
-  return this.comments.length === 10 || this.name.length === 5;
-})
- -

NOTE:

- -

Only use $where when you have a condition that cannot be met using other MongoDB operators like $lt. Be sure to read about all of its caveats before using.


Query.prototype.Symbol.asyncIterator()

Returns an asyncIterator for use with for/await/of loops This function only works for find() queries. You do not need to call this function explicitly, the JavaScript runtime will call it for you.

- -

Example

- -
for await (const doc of Model.aggregate([{ $sort: { name: 1 } }])) {
-  console.log(doc.name);
-}
- -

Node.js 10.x supports async iterators natively without any flags. You can enable async iterators in Node.js 8.x using the --harmony_async_iteration flag.

- -

Note: This function is not if Symbol.asyncIterator is undefined. If Symbol.asyncIterator is undefined, that means your Node.js version does not support async iterators.


Query.prototype.all()

Parameters
  • [path] -«String»
  • val -«Array»

Specifies an $all query condition.

- -

When called with one argument, the most recent path passed to where() is used.

- -

Example:

- -
MyModel.find().where('pets').all(['dog', 'cat', 'ferret']);
-// Equivalent:
-MyModel.find().all('pets', ['dog', 'cat', 'ferret']);

Query.prototype.and()

Parameters
  • array -«Array» array of conditions
Returns:
  • «Query» this

Specifies arguments for a $and condition.

- -

Example

- -
query.and([{ color: 'green' }, { status: 'ok' }])

Query.prototype.batchSize()

Parameters
  • val -«Number»

Specifies the batchSize option.

- -

Example

- -
query.batchSize(100)
- -

Note

- -

Cannot be used with distinct()


Query.prototype.box()

Parameters
  • val -«Object»
  • Upper -«[Array]» Right Coords
Returns:
  • «Query» this

Specifies a $box condition

- -

Example

- -
var lowerLeft = [40.73083, -73.99756]
-var upperRight= [40.741404,  -73.988135]
-
-query.where('loc').within().box(lowerLeft, upperRight)
-query.box({ ll : lowerLeft, ur : upperRight })

Query.prototype.cast()

Parameters
  • [model] -«Model» the model to cast to. If not set, defaults to this.model
  • [obj] -«Object»
Returns:
  • «Object»

Casts this query to the schema of model

- -

Note

- -

If obj is present, it is cast instead of this query.


Query.prototype.catch()

Parameters
  • [reject] -«Function»
Returns:
  • «Promise»

Executes the query returning a Promise which will be resolved with either the doc(s) or rejected with the error. Like .then(), but only takes a rejection handler.


Query.prototype.center()

DEPRECATED Alias for circle

- -

Deprecated. Use circle instead.


Query.prototype.centerSphere()

Parameters
  • [path] -«String»
  • val -«Object»
Returns:
  • «Query» this

DEPRECATED Specifies a $centerSphere condition

- -

Deprecated. Use circle instead.

- -

Example

- -
var area = { center: [50, 50], radius: 10 };
-query.where('loc').within().centerSphere(area);

Query.prototype.circle()

Parameters
  • [path] -«String»
  • area -«Object»
Returns:
  • «Query» this

Specifies a $center or $centerSphere condition.

- -

Example

- -
var area = { center: [50, 50], radius: 10, unique: true }
-query.where('loc').within().circle(area)
-// alternatively
-query.circle('loc', area);
-
-// spherical calculations
-var area = { center: [50, 50], radius: 10, unique: true, spherical: true }
-query.where('loc').within().circle(area)
-// alternatively
-query.circle('loc', area);

Query.prototype.collation()

Parameters
  • value -«Object»
Returns:
  • «Query» this

Adds a collation to this op (MongoDB 3.4 and up)


Query.prototype.comment()

Parameters
  • val -«String»

Specifies the comment option.

- -

Example

- -
query.comment('login query')
- -

Note

- -

Cannot be used with distinct()


Query.prototype.count()

Parameters
  • [filter] -«Object» count documents that match this object
  • [callback] -«Function» optional params are (error, count)
Returns:
  • «Query» this

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 instead. Otherwise, use the countDocuments() function instead.

- -

Passing a callback executes the query.

- -

This function triggers the following middleware.

- -
    -
  • count()
  • -
- -

Example:

- -
var countQuery = model.where({ 'color': 'black' }).count();
-
-query.count({ color: 'black' }).count(callback)
-
-query.count({ color: 'black' }, callback)
-
-query.where('color', 'black').count(function (err, count) {
-  if (err) return handleError(err);
-  console.log('there are %d kittens', count);
-})

Query.prototype.countDocuments()

Parameters
  • [filter] -«Object» mongodb selector
  • [callback] -«Function» optional params are (error, count)
Returns:
  • «Query» this

Specifies this query as a countDocuments() query. Behaves like count(), except it always does a full collection scan when passed an empty filter {}.

- -

There are also minor differences in how countDocuments() handles $where and a couple geospatial operators. versus count().

- -

Passing a callback executes the query.

- -

This function triggers the following middleware.

- -
    -
  • countDocuments()
  • -
- -

Example:

- -
const countQuery = model.where({ 'color': 'black' }).countDocuments();
-
-query.countDocuments({ color: 'black' }).count(callback);
-
-query.countDocuments({ color: 'black' }, callback);
-
-query.where('color', 'black').countDocuments(function(err, count) {
-  if (err) return handleError(err);
-  console.log('there are %d kittens', count);
-});
- -

The countDocuments() function is similar to count(), but there are a few operators that countDocuments() does not support. Below are the operators that count() supports but countDocuments() does not, and the suggested replacement:

- -

Query.prototype.cursor()

Parameters
  • [options] -«Object»
Returns:
  • «QueryCursor»

Returns a wrapper around a mongodb driver cursor. A QueryCursor exposes a Streams3 interface, as well as a .next() function.

- -

The .cursor() function triggers pre find hooks, but not post find hooks.

- -

Example

- -
// There are 2 ways to use a cursor. First, as a stream:
-Thing.
-  find({ name: /^hello/ }).
-  cursor().
-  on('data', function(doc) { console.log(doc); }).
-  on('end', function() { console.log('Done!'); });
-
-// Or you can use `.next()` to manually get the next doc in the stream.
-// `.next()` returns a promise, so you can use promises or callbacks.
-var cursor = Thing.find({ name: /^hello/ }).cursor();
-cursor.next(function(error, doc) {
-  console.log(doc);
-});
-
-// Because `.next()` returns a promise, you can use co
-// to easily iterate through all documents without loading them
-// all into memory.
-co(function*() {
-  const cursor = Thing.find({ name: /^hello/ }).cursor();
-  for (let doc = yield cursor.next(); doc != null; doc = yield cursor.next()) {
-    console.log(doc);
-  }
-});
- -

Valid options

- -
    -
  • transform: optional function which accepts a mongoose document. The return value of the function will be emitted on data and returned by .next().
  • -

Query.prototype.deleteMany()

Parameters
  • [filter] -«Object|Query» mongodb selector
  • [callback] -«Function» optional params are (error, mongooseDeleteResult)
Returns:
  • «Query» this

Declare and/or execute this query as a deleteMany() operation. Works like remove, except it deletes every document that matches filter in the collection, regardless of the value of single.

- -

This function does not trigger any middleware

- -

Example

- -
Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }, callback)
-Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }).then(next)
- -

This function calls the MongoDB driver's Collection#deleteMany() function. The returned promise resolves to an object that contains 3 properties:

- -
    -
  • ok: 1 if no errors occurred
  • -
  • deletedCount: the number of documents deleted
  • -
  • n: the number of documents deleted. Equal to deletedCount.
  • -
- -

Example

- -
const res = await Character.deleteMany({ name: /Stark/, age: { $gte: 18 } });
-// `0` if no docs matched the filter, number of docs deleted otherwise
-res.deletedCount;

Query.prototype.deleteOne()

Parameters
  • [filter] -«Object|Query» mongodb selector
  • [callback] -«Function» optional params are (error, mongooseDeleteResult)
Returns:
  • «Query» this

Declare and/or execute this query as a deleteOne() operation. Works like remove, except it deletes at most one document regardless of the single option.

- -

This function does not trigger any middleware.

- -

Example

- -
Character.deleteOne({ name: 'Eddard Stark' }, callback);
-Character.deleteOne({ name: 'Eddard Stark' }).then(next);
- -

This function calls the MongoDB driver's Collection#deleteOne() function. The returned promise resolves to an object that contains 3 properties:

- -
    -
  • ok: 1 if no errors occurred
  • -
  • deletedCount: the number of documents deleted
  • -
  • n: the number of documents deleted. Equal to deletedCount.
  • -
- -

Example

- -
const res = await Character.deleteOne({ name: 'Eddard Stark' });
-// `1` if MongoDB deleted a doc, `0` if no docs matched the filter `{ name: ... }`
-res.deletedCount;

Query.prototype.distinct()

Parameters
  • [field] -«String»
  • [filter] -«Object|Query»
  • [callback] -«Function» optional params are (error, arr)
Returns:
  • «Query» this

Declares or executes a distinct() operation.

- -

Passing a callback executes the query.

- -

This function does not trigger any middleware.

- -

Example

- -
distinct(field, conditions, callback)
-distinct(field, conditions)
-distinct(field, callback)
-distinct(field)
-distinct(callback)
-distinct()

Query.prototype.elemMatch()

Parameters
  • path -«String|Object|Function»
  • filter -«Object|Function»
Returns:
  • «Query» this

Specifies an $elemMatch condition

- -

Example

- -
query.elemMatch('comment', { author: 'autobot', votes: {$gte: 5}})
-
-query.where('comment').elemMatch({ author: 'autobot', votes: {$gte: 5}})
-
-query.elemMatch('comment', function (elem) {
-  elem.where('author').equals('autobot');
-  elem.where('votes').gte(5);
-})
-
-query.where('comment').elemMatch(function (elem) {
-  elem.where({ author: 'autobot' });
-  elem.where('votes').gte(5);
-})

Query.prototype.equals()

Parameters
  • val -«Object»
Returns:
  • «Query» this

Specifies the complementary comparison value for paths specified with where()

- -

Example

- -
User.where('age').equals(49);
-
-// is the same as
-
-User.where('age', 49);

Query.prototype.error()

Parameters
  • err -«Error|null» if set, exec() will fail fast before sending the query to MongoDB
Returns:
  • «Query» this

Gets/sets the error flag on this query. If this flag is not null or undefined, the exec() promise will reject without executing.

- -

Example:

- -
Query().error(); // Get current error value
-Query().error(null); // Unset the current error
-Query().error(new Error('test')); // `exec()` will resolve with test
-Schema.pre('find', function() {
-  if (!this.getQuery().userId) {
-    this.error(new Error('Not allowed to query without setting userId'));
-  }
-});
- -

Note that query casting runs after hooks, so cast errors will override custom errors.

- -

Example:

- -
var TestSchema = new Schema({ num: Number });
-var TestModel = db.model('Test', TestSchema);
-TestModel.find({ num: 'not a number' }).error(new Error('woops')).exec(function(error) {
-  // `error` will be a cast error because `num` failed to cast
-});

Query.prototype.estimatedDocumentCount()

Parameters
  • [options] -«Object» passed transparently to the MongoDB driver
  • [callback] -«Function» optional params are (error, count)
Returns:
  • «Query» this

Specifies this query as a estimatedDocumentCount() query. Faster than using countDocuments() for large collections because estimatedDocumentCount() uses collection metadata rather than scanning the entire collection.

- -

estimatedDocumentCount() does not accept a filter. Model.find({ foo: bar }).estimatedDocumentCount() is equivalent to Model.find().estimatedDocumentCount()

- -

This function triggers the following middleware.

- -
    -
  • estimatedDocumentCount()
  • -
- -

Example:

- -
await Model.find().estimatedDocumentCount();

Query.prototype.exec()

Parameters
  • [operation] -«String|Function»
  • [callback] -«Function» optional params depend on the function being called
Returns:
  • «Promise»

Executes the query

- -

Examples:

- -
var promise = query.exec();
-var promise = query.exec('update');
-
-query.exec(callback);
-query.exec('find', callback);

Query.prototype.exists()

Parameters
  • [path] -«String»
  • val -«Number»
Returns:
  • «Query» this

Specifies an $exists condition

- -

Example

- -
// { name: { $exists: true }}
-Thing.where('name').exists()
-Thing.where('name').exists(true)
-Thing.find().exists('name')
-
-// { name: { $exists: false }}
-Thing.where('name').exists(false);
-Thing.find().exists('name', false);

Query.prototype.explain()

Parameters
  • [verbose] -«String» The verbosity mode. Either 'queryPlanner', 'executionStats', or 'allPlansExecution'. The default is 'queryPlanner'
Returns:
  • «Query» this

Sets the explain option, which makes this query return detailed execution stats instead of the actual query result. This method is useful for determining what index your queries use.

- -

Calling query.explain(v) is equivalent to query.setOption({ explain: v })

- -

Example:

- -
const query = new Query();
-const res = await query.find({ a: 1 }).explain('queryPlanner');
-console.log(res);

Query.prototype.find()

Parameters
  • [filter] -«Object» mongodb selector. If not specified, returns all documents.
  • [callback] -«Function»
Returns:
  • «Query» this

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()

- -

Example

- -
// Using async/await
-const arr = await Movie.find({ year: { $gte: 1980, $lte: 1989 } });
-
-// Using callbacks
-Movie.find({ year: { $gte: 1980, $lte: 1989 } }, function(err, arr) {});

Query.prototype.findOne()

Parameters
  • [filter] -«Object» mongodb selector
  • [projection] -«Object» optional fields to return
  • [options] -«Object» see setOptions()
  • [callback] -«Function» optional params are (error, document)
Returns:
  • «Query» this

Declares the query a findOne operation. When executed, the first found document is passed to the callback.

- -

Passing a callback executes the query. The result of the query is a single document.

- -
    -
  • Note: conditions is optional, and if conditions is null or undefined, -mongoose will send an empty findOne command to MongoDB, which will return -an arbitrary document. If you're querying by _id, use Model.findById() -instead.
  • -
- -

This function triggers the following middleware.

- -
    -
  • findOne()
  • -
- -

Example

- -
var query  = Kitten.where({ color: 'white' });
-query.findOne(function (err, kitten) {
-  if (err) return handleError(err);
-  if (kitten) {
-    // doc may be null if no document matched
-  }
-});

Query.prototype.findOneAndDelete()

Parameters
  • [conditions] -«Object»
  • [options] -«Object»
  • [callback] -«Function» optional params are (error, document)
Returns:
  • «Query» this

Issues a MongoDB findOneAndDelete command.

- -

Finds a matching document, removes it, and passes the found document (if any) to the callback. Executes if callback is passed.

- -

This function triggers the following middleware.

- -
    -
  • findOneAndDelete()
  • -
- -

This function differs slightly from Model.findOneAndRemove() in that findOneAndRemove() becomes a MongoDB findAndModify() command, as opposed to a findOneAndDelete() command. For most mongoose use cases, this distinction is purely pedantic. You should use findOneAndDelete() unless you have a good reason not to.

- -

Available options

- -
    -
  • 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
  • -
  • rawResult: if true, resolves to the raw result from the MongoDB driver
  • -
- -

Callback Signature

- -
function(error, doc) {
-  // error: any errors that occurred
-  // doc: the document before updates are applied if `new: false`, or after updates if `new = true`
-}
- -

Examples

- -
A.where().findOneAndDelete(conditions, options, callback) // executes
-A.where().findOneAndDelete(conditions, options)  // return Query
-A.where().findOneAndDelete(conditions, callback) // executes
-A.where().findOneAndDelete(conditions) // returns Query
-A.where().findOneAndDelete(callback)   // executes
-A.where().findOneAndDelete()           // returns Query

Query.prototype.findOneAndRemove()

Parameters
  • [conditions] -«Object»
  • [options] -«Object»
  • [callback] -«Function» optional params are (error, document)
Returns:
  • «Query» this

Issues a mongodb findAndModify remove command.

- -

Finds a matching document, removes it, passing the found document (if any) to the callback. Executes if callback is passed.

- -

This function triggers the following middleware.

- -
    -
  • findOneAndRemove()
  • -
- -

Available options

- -
    -
  • 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
  • -
  • rawResult: if true, resolves to the raw result from the MongoDB driver
  • -
- -

Callback Signature

- -
function(error, doc) {
-  // error: any errors that occurred
-  // doc: the document before updates are applied if `new: false`, or after updates if `new = true`
-}
- -

Examples

- -
A.where().findOneAndRemove(conditions, options, callback) // executes
-A.where().findOneAndRemove(conditions, options)  // return Query
-A.where().findOneAndRemove(conditions, callback) // executes
-A.where().findOneAndRemove(conditions) // returns Query
-A.where().findOneAndRemove(callback)   // executes
-A.where().findOneAndRemove()           // returns Query

Query.prototype.findOneAndReplace()

Parameters
  • [filter] -«Object»
  • [replacement] -«Object»
  • [options] -«Object»
    • [options.lean] -«Object» if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See Query.lean().
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
  • [callback] -«Function» optional params are (error, document)
Returns:
  • «Query» this

Issues a MongoDB findOneAndReplace command.

- -

Finds a matching document, removes it, and passes the found document (if any) to the callback. Executes if callback is passed.

- -

This function triggers the following middleware.

- -
    -
  • findOneAndReplace()
  • -
- -

Available options

- -
    -
  • 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
  • -
  • rawResult: if true, resolves to the raw result from the MongoDB driver
  • -
- -

Callback Signature

- -
function(error, doc) {
-  // error: any errors that occurred
-  // doc: the document before updates are applied if `new: false`, or after updates if `new = true`
-}
- -

Examples

- -
A.where().findOneAndReplace(filter, replacement, options, callback); // executes
-A.where().findOneAndReplace(filter, replacement, options); // return Query
-A.where().findOneAndReplace(filter, replacement, callback); // executes
-A.where().findOneAndReplace(filter); // returns Query
-A.where().findOneAndReplace(callback); // executes
-A.where().findOneAndReplace(); // returns Query

Query.prototype.findOneAndUpdate()

Parameters
  • [filter] -«Object|Query»
  • [doc] -«Object»
  • [options] -«Object»
    • [options.multipleCastError] -«Boolean» by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
    • [options.lean] -«Object» if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See Query.lean().
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
  • [callback] -«Function» optional params are (error, doc), unless rawResult is used, in which case params are (error, writeOpResult)
Returns:
  • «Query» this

Issues a mongodb findAndModify update command.

- -

Finds a matching document, updates it according to the update arg, passing any options, and returns the found document (if any) to the callback. The query executes if callback is passed.

- -

This function triggers the following middleware.

- -
    -
  • findOneAndUpdate()
  • -
- -

Available options

- -
    -
  • new: bool - if true, return the modified document rather than the original. defaults to false (changed in 4.0)
  • -
  • upsert: bool - creates the object if it doesn't exist. defaults to false.
  • -
  • 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 on this command. Update validators validate the update operation against the model's schema.
  • -
  • setDefaultsOnInsert: if this and upsert are true, mongoose will apply the defaults specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on MongoDB's $setOnInsert operator.
  • -
  • rawResult: if true, returns the raw result from the MongoDB driver
  • -
  • context (string) if set to 'query' and runValidators is on, this will refer to the query in custom validator functions that update validation runs. Does nothing if runValidators is false.
  • -
- -

Callback Signature

- -
function(error, doc) {
-  // error: any errors that occurred
-  // doc: the document before updates are applied if `new: false`, or after updates if `new = true`
-}
- -

Examples

- -
query.findOneAndUpdate(conditions, update, options, callback) // executes
-query.findOneAndUpdate(conditions, update, options)  // returns Query
-query.findOneAndUpdate(conditions, update, callback) // executes
-query.findOneAndUpdate(conditions, update)           // returns Query
-query.findOneAndUpdate(update, callback)             // returns Query
-query.findOneAndUpdate(update)                       // returns Query
-query.findOneAndUpdate(callback)                     // executes
-query.findOneAndUpdate()                             // returns Query

Query.prototype.geometry()

Parameters
  • object -«Object» Must contain a type property which is a String and a coordinates property which is an Array. See the examples.
Returns:
  • «Query» this

Specifies a $geometry condition

- -

Example

- -
var polyA = [[[ 10, 20 ], [ 10, 40 ], [ 30, 40 ], [ 30, 20 ]]]
-query.where('loc').within().geometry({ type: 'Polygon', coordinates: polyA })
-
-// or
-var polyB = [[ 0, 0 ], [ 1, 1 ]]
-query.where('loc').within().geometry({ type: 'LineString', coordinates: polyB })
-
-// or
-var polyC = [ 0, 0 ]
-query.where('loc').within().geometry({ type: 'Point', coordinates: polyC })
-
-// or
-query.where('loc').intersects().geometry({ type: 'Point', coordinates: polyC })
- -

The argument is assigned to the most recent path passed to where().

- -

NOTE:

- -

geometry() must come after either intersects() or within().

- -

The object argument must contain type and coordinates properties. - type {String} - coordinates {Array}


Query.prototype.getFilter()

Returns:
  • «Object» current query filter

Returns the current query filter (also known as conditions) as a POJO.

- -

Example:

- -
const query = new Query();
-query.find({ a: 1 }).where('b').gt(2);
-query.getFilter(); // { a: 1, b: { $gt: 2 } }

Query.prototype.getOptions()

Returns:
  • «Object» the options

Gets query options.

- -

Example:

- -
var query = new Query();
-query.limit(10);
-query.setOptions({ maxTimeMS: 1000 })
-query.getOptions(); // { limit: 10, maxTimeMS: 1000 }

Query.prototype.getPopulatedPaths()

Returns:
  • «Array» an array of strings representing populated paths

Gets a list of paths to be populated by this query

- -

Example:

- -
bookSchema.pre('findOne', function() {
-   let keys = this.getPopulatedPaths(); // ['author']
- });
- ...
- Book.findOne({}).populate('author');
- -

Example:

- -
// Deep populate
- const q = L1.find().populate({
-   path: 'level2',
-   populate: { path: 'level3' }
- });
- q.getPopulatedPaths(); // ['level2', 'level2.level3']

Query.prototype.getQuery()

Returns:
  • «Object» current query filter

Returns the current query filter. Equivalent to getFilter().

- -

You should use getFilter() instead of getQuery() where possible. getQuery() will likely be deprecated in a future release.

- -

Example:

- -
var query = new Query();
-query.find({ a: 1 }).where('b').gt(2);
-query.getQuery(); // { a: 1, b: { $gt: 2 } }

Query.prototype.getUpdate()

Returns:
  • «Object» current update operations

Returns the current update operations as a JSON object.

- -

Example:

- -
var query = new Query();
-query.update({}, { $set: { a: 5 } });
-query.getUpdate(); // { $set: { a: 5 } }

Query.prototype.gt()

Parameters
  • [path] -«String»
  • val -«Number»

Specifies a $gt query condition.

- -

When called with one argument, the most recent path passed to where() is used.

- -

Example

- -
Thing.find().where('age').gt(21)
-
-// or
-Thing.find().gt('age', 21)

Query.prototype.gte()

Parameters
  • [path] -«String»
  • val -«Number»

Specifies a $gte query condition.

- -

When called with one argument, the most recent path passed to where() is used.


Query.prototype.hint()

Parameters
  • val -«Object» a hint object
Returns:
  • «Query» this

Sets query hints.

- -

Example

- -
query.hint({ indexA: 1, indexB: -1})
- -

Note

- -

Cannot be used with distinct()


Query.prototype.in()

Parameters
  • [path] -«String»
  • val -«Number»

Specifies an $in query condition.

- -

When called with one argument, the most recent path passed to where() is used.


Query.prototype.intersects()

Parameters
  • [arg] -«Object»
Returns:
  • «Query» this

Declares an intersects query for geometry().

- -

Example

- -
query.where('path').intersects().geometry({
-    type: 'LineString'
-  , coordinates: [[180.0, 11.0], [180, 9.0]]
-})
-
-query.where('path').intersects({
-    type: 'LineString'
-  , coordinates: [[180.0, 11.0], [180, 9.0]]
-})
- -

NOTE:

- -

MUST be used after where().

- -

NOTE:

- -

In Mongoose 3.7, intersects changed from a getter to a function. If you need the old syntax, use this.


Query.prototype.j()

Parameters
  • val -«boolean»
Returns:
  • «Query» this

Requests acknowledgement that this operation has been persisted to MongoDB's on-disk journal.

- -

This option is only valid for operations that write to the database

- -
    -
  • deleteOne()
  • -
  • deleteMany()
  • -
  • findOneAndDelete()
  • -
  • findOneAndReplace()
  • -
  • findOneAndUpdate()
  • -
  • remove()
  • -
  • update()
  • -
  • updateOne()
  • -
  • updateMany()
  • -
- -

Defaults to the schema's writeConcern.j option

- -

Example:

- -
await mongoose.model('Person').deleteOne({ name: 'Ned Stark' }).j(true);

Query.prototype.lean()

Parameters
  • bool -«Boolean|Object» defaults to true
Returns:
  • «Query» this

Sets the lean option.

- -

Documents returned from queries with the lean option enabled are plain javascript objects, not Mongoose Documents. They have no save method, getters/setters, virtuals, or other Mongoose features.

- -

Example:

- -
new Query().lean() // true
-new Query().lean(true)
-new Query().lean(false)
-
-const docs = await Model.find().lean();
-docs[0] instanceof mongoose.Document; // false
- -

Lean is great for high-performance, read-only cases, especially when combined with cursors.


Query.prototype.limit()

Parameters
  • val -«Number»

Specifies the maximum number of documents the query will return.

- -

Example

- -
query.limit(20)
- -

Note

- -

Cannot be used with distinct()


Query.prototype.lt()

Parameters
  • [path] -«String»
  • val -«Number»

Specifies a $lt query condition.

- -

When called with one argument, the most recent path passed to where() is used.


Query.prototype.lte()

Parameters
  • [path] -«String»
  • val -«Number»

Specifies a $lte query condition.

- -

When called with one argument, the most recent path passed to where() is used.


Query.prototype.map()

Parameters
  • fn -«Function» function to run to transform the query result
Returns:
  • «Query» this

Runs a function fn and treats the return value of fn as the new value for the query to resolve to.

- -

Any functions you pass to map() will run after any post hooks.

- -

Example:

- -
const res = await MyModel.findOne().map(res => {
-  // Sets a `loadedAt` property on the doc that tells you the time the
-  // document was loaded.
-  return res == null ?
-    res :
-    Object.assign(res, { loadedAt: new Date() });
-});

Query.prototype.maxDistance()

Parameters
  • [path] -«String»
  • val -«Number»

Specifies a maxDistance query condition.

- -

When called with one argument, the most recent path passed to where() is used.


Query.prototype.maxScan()

Parameters
  • val -«Number»

Specifies the maxScan option.

- -

Example

- -
query.maxScan(100)
- -

Note

- -

Cannot be used with distinct()


Query.prototype.maxTimeMS()

Parameters
  • [ms] -«Number» The number of milliseconds
Returns:
  • «Query» this

Sets the maxTimeMS option. This will tell the MongoDB server to abort if the query or write op has been running for more than ms milliseconds.

- -

Calling query.maxTimeMS(v) is equivalent to query.setOption({ maxTimeMS: v })

- -

Example:

- -
const query = new Query();
-// Throws an error 'operation exceeded time limit' as long as there's
-// >= 1 doc in the queried collection
-const res = await query.find({ $where: 'sleep(1000) || true' }).maxTimeMS(100);

Query.prototype.maxscan()

DEPRECATED Alias of maxScan


Query.prototype.merge()

Parameters
  • source -«Query|Object»
Returns:
  • «Query» this

Merges another Query or conditions object into this one.

- -

When a Query is passed, conditions, field selection and options are merged.


Query.prototype.mod()

Parameters
  • [path] -«String»
  • val -«Array» must be of length 2, first element is divisor, 2nd element is remainder.
Returns:
  • «Query» this

Specifies a $mod condition, filters documents for documents whose path property is a number that is equal to remainder modulo divisor.

- -

Example

- -
// All find products whose inventory is odd
-Product.find().mod('inventory', [2, 1]);
-Product.find().where('inventory').mod([2, 1]);
-// This syntax is a little strange, but supported.
-Product.find().where('inventory').mod(2, 1);

Query.prototype.mongooseOptions()

Parameters
  • options -«Object» if specified, overwrites the current options
Returns:
  • «Object» the options

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()
  • -
  • lean: if truthy, Mongoose will not hydrate any documents that are returned from this query. See 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 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 for backwards compatibility, which means Mongoose will allow Model.find({ foo: 'bar' }) even if foo is not in the schema. See the strictQuery docs for more information.
  • -
  • useFindAndModify: used to work around the findAndModify() deprecation warning
  • -
  • omitUndefined: delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
  • -
  • nearSphere: use $nearSphere instead of near(). See the Query.prototype.nearSphere() docs
  • -
- -

Mongoose maintains a separate object for internal options because Mongoose sends Query.prototype.options to the MongoDB server, and the above options are not relevant for the MongoDB server.


Query.prototype.ne()

Parameters
  • [path] -«String»
  • val -«Number»

Specifies a $ne query condition.

- -

When called with one argument, the most recent path passed to where() is used.


Query.prototype.near()

Parameters
  • [path] -«String»
  • val -«Object»
Returns:
  • «Query» this

Specifies a $near or $nearSphere condition

- -

These operators return documents sorted by distance.

- -

Example

- -
query.where('loc').near({ center: [10, 10] });
-query.where('loc').near({ center: [10, 10], maxDistance: 5 });
-query.where('loc').near({ center: [10, 10], maxDistance: 5, spherical: true });
-query.near('loc', { center: [10, 10], maxDistance: 5 });

Query.prototype.nearSphere()

DEPRECATED Specifies a $nearSphere condition

- -

Example

- -
query.where('loc').nearSphere({ center: [10, 10], maxDistance: 5 });
- -

Deprecated. Use query.near() instead with the spherical option set to true.

- -

Example

- -
query.where('loc').near({ center: [10, 10], spherical: true });

Query.prototype.nin()

Parameters
  • [path] -«String»
  • val -«Number»

Specifies an $nin query condition.

- -

When called with one argument, the most recent path passed to where() is used.


Query.prototype.nor()

Parameters
  • array -«Array» array of conditions
Returns:
  • «Query» this

Specifies arguments for a $nor condition.

- -

Example

- -
query.nor([{ color: 'green' }, { status: 'ok' }])

Query.prototype.or()

Parameters
  • array -«Array» array of conditions
Returns:
  • «Query» this

Specifies arguments for an $or condition.

- -

Example

- -
query.or([{ color: 'red' }, { status: 'emergency' }])

Query.prototype.orFail()

Parameters
  • [err] -«Function|Error» optional error to throw if no docs match filter. If not specified, orFail() will throw a DocumentNotFoundError
Returns:
  • «Query» this

Make this query throw an error if no documents match the given filter. This is handy for integrating with async/await, because orFail() saves you an extra if statement to check if no document was found.

- -

Example:

- -
// Throws if no doc returned
-await Model.findOne({ foo: 'bar' }).orFail();
-
-// Throws if no document was updated
-await Model.updateOne({ foo: 'bar' }, { name: 'test' }).orFail();
-
-// Throws "No docs found!" error if no docs match `{ foo: 'bar' }`
-await Model.find({ foo: 'bar' }).orFail(new Error('No docs found!'));
-
-// Throws "Not found" error if no document was found
-await Model.findOneAndUpdate({ foo: 'bar' }, { name: 'test' }).
-  orFail(() => Error('Not found'));

Query.prototype.polygon()

Parameters
  • [path] -«String|Array»
    • [coordinatePairs...] -«Array|Object»
Returns:
  • «Query» this

Specifies a $polygon condition

- -

Example

- -
query.where('loc').within().polygon([10,20], [13, 25], [7,15])
-query.polygon('loc', [10,20], [13, 25], [7,15])

Query.prototype.populate()

Parameters
  • path -«Object|String» either the path to populate or an object specifying all parameters
  • [select] -«Object|String» Field selection for the population query
  • [model] -«Model» The model you wish to use for population. If not specified, populate will look up the model by the name in the Schema's ref field.
  • [match] -«Object» Conditions for the population query
  • [options] -«Object» Options for the population query (sort, etc)
    • [options.path=null] -«String» The path to populate.
    • [options.retainNullValues=false] -«boolean» by default, Mongoose removes null and undefined values from populated arrays. Use this option to make populate() retain null and undefined array entries.
    • [options.getters=false] -«boolean» 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.
    • [options.clone=false] -«boolean» 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.
    • [options.match=null] -«Object|Function» Add an additional filter to the populate query. Can be a filter object containing MongoDB query syntax, or a function that returns a filter object.
Returns:
  • «Query» this

Specifies paths which should be populated with other documents.

- -

Example:

- -
Kitten.findOne().populate('owner').exec(function (err, kitten) {
-  console.log(kitten.owner.name) // Max
-})
-
-Kitten.find().populate({
-  path: 'owner',
-  select: 'name',
-  match: { color: 'black' },
-  options: { sort: { name: -1 } }
-}).exec(function (err, kittens) {
-  console.log(kittens[0].owner.name) // Zoopa
-})
-
-// alternatively
-Kitten.find().populate('owner', 'name', null, {sort: { name: -1 }}).exec(function (err, kittens) {
-  console.log(kittens[0].owner.name) // Zoopa
-})
- -

Paths are populated after the query executes and a response is received. A separate query is then executed for each path specified for population. After a response for each query has also been returned, the results are passed to the callback.


Query.prototype.projection()

Parameters
  • arg -«Object|null»
Returns:
  • «Object» the current projection

Get/set the current projection (AKA fields). Pass null to remove the current projection.

- -

Unlike projection(), the select() function modifies the current projection in place. This function overwrites the existing projection.

- -

Example:

- -
const q = Model.find();
-q.projection(); // null
-
-q.select('a b');
-q.projection(); // { a: 1, b: 1 }
-
-q.projection({ c: 1 });
-q.projection(); // { c: 1 }
-
-q.projection(null);
-q.projection(); // null

Query.prototype.read()

Parameters
  • pref -«String» one of the listed preference options or aliases
  • [tags] -«Array» optional tags for this query
Returns:
  • «Query» this

Determines the MongoDB nodes from which to read.

- -

Preferences:

- -
primary - (default) Read from primary only. Operations will produce an error if primary is unavailable. Cannot be combined with tags.
-secondary            Read from secondary if available, otherwise error.
-primaryPreferred     Read from primary if available, otherwise a secondary.
-secondaryPreferred   Read from a secondary if available, otherwise read from the primary.
-nearest              All operations read from among the nearest candidates, but unlike other modes, this option will include both the primary and all secondaries in the random selection.
- -

Aliases

- -
p   primary
-pp  primaryPreferred
-s   secondary
-sp  secondaryPreferred
-n   nearest
- -

Example:

- -
new Query().read('primary')
-new Query().read('p')  // same as primary
-
-new Query().read('primaryPreferred')
-new Query().read('pp') // same as primaryPreferred
-
-new Query().read('secondary')
-new Query().read('s')  // same as secondary
-
-new Query().read('secondaryPreferred')
-new Query().read('sp') // same as secondaryPreferred
-
-new Query().read('nearest')
-new Query().read('n')  // same as nearest
-
-// read from secondaries with matching tags
-new Query().read('s', [{ dc:'sf', s: 1 },{ dc:'ma', s: 2 }])
- -

Read more about how to use read preferrences here and here.


Query.prototype.readConcern()

Parameters
  • level -«String» one of the listed read concern level or their aliases
Returns:
  • «Query» this

Sets the readConcern option for the query.

- -

Example:

- -
new Query().readConcern('local')
-new Query().readConcern('l')  // same as local
-
-new Query().readConcern('available')
-new Query().readConcern('a')  // same as available
-
-new Query().readConcern('majority')
-new Query().readConcern('m')  // same as majority
-
-new Query().readConcern('linearizable')
-new Query().readConcern('lz') // same as linearizable
-
-new Query().readConcern('snapshot')
-new Query().readConcern('s')  // same as snapshot
- -

Read Concern Level:

- -
local         MongoDB 3.2+ The query returns from the instance with no guarantee guarantee that the data has been written to a majority of the replica set members (i.e. may be rolled back).
-available     MongoDB 3.6+ The query returns from the instance with no guarantee guarantee that the data has been written to a majority of the replica set members (i.e. may be rolled back).
-majority      MongoDB 3.2+ The query returns the data that has been acknowledged by a majority of the replica set members. The documents returned by the read operation are durable, even in the event of failure.
-linearizable  MongoDB 3.4+ The query returns data that reflects all successful majority-acknowledged writes that completed prior to the start of the read operation. The query may wait for concurrently executing writes to propagate to a majority of replica set members before returning results.
-snapshot      MongoDB 4.0+ Only available for operations within multi-document transactions. Upon transaction commit with write concern "majority", the transaction operations are guaranteed to have read from a snapshot of majority-committed data.
- -

Aliases

- -
l   local
-a   available
-m   majority
-lz  linearizable
-s   snapshot
- -

Read more about how to use read concern here.


Query.prototype.regex()

Parameters
  • [path] -«String»
  • val -«String|RegExp»

Specifies a $regex query condition.

- -

When called with one argument, the most recent path passed to where() is used.


Query.prototype.remove()

Parameters
  • [filter] -«Object|Query» mongodb selector
  • [callback] -«Function» optional params are (error, mongooseDeleteResult)
Returns:
  • «Query» this

Declare and/or execute this query as a remove() operation. remove() is deprecated, you should use deleteOne() or deleteMany() instead.

- -

This function does not trigger any middleware

- -

Example

- -
Character.remove({ name: /Stark/ }, callback);
- -

This function calls the MongoDB driver's Collection#remove() function. The returned promise resolves to an object that contains 3 properties:

- -
    -
  • ok: 1 if no errors occurred
  • -
  • deletedCount: the number of documents deleted
  • -
  • n: the number of documents deleted. Equal to deletedCount.
  • -
- -

Example

- -
const res = await Character.remove({ name: /Stark/ });
-// Number of docs deleted
-res.deletedCount;
- -

Note

- -

Calling remove() creates a Mongoose query, and a query does not execute until you either pass a callback, call Query#then(), or call Query#exec().

- -
// not executed
-const query = Character.remove({ name: /Stark/ });
-
-// executed
-Character.remove({ name: /Stark/ }, callback);
-Character.remove({ name: /Stark/ }).remove(callback);
-
-// executed without a callback
-Character.exec();

Query.prototype.replaceOne()

Parameters
  • [filter] -«Object»
  • [doc] -«Object» the update command
  • [options] -«Object»
    • [options.multipleCastError] -«Boolean» by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [options.upsert=false] -«Boolean» if true, and no documents found, insert a new document
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [options.timestamps=null] -«Boolean» If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
  • [callback] -«Function» params are (error, writeOpResult)
Returns:
  • «Query» this

Declare and/or execute this query as a replaceOne() operation. Same as update(), except MongoDB will replace the existing document and will not accept any atomic operators ($set, etc.)

- -

Note replaceOne will not fire update middleware. Use pre('replaceOne') and post('replaceOne') instead.

- -

Example:

- -
const res = await Person.replaceOne({ _id: 24601 }, { name: 'Jean Valjean' });
-res.n; // Number of documents matched
-res.nModified; // Number of documents modified
- -

This function triggers the following middleware.

- -
    -
  • replaceOne()
  • -

Query.prototype.select()

Parameters
  • arg -«Object|String»
Returns:
  • «Query» this

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.

- -

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 to exclude (which implies all other fields are included). The _id field is the only exception because MongoDB includes it by default.

- -

Example

- -
// include a and b, exclude other fields
-query.select('a b');
-
-// exclude c and d, include other fields
-query.select('-c -d');
-
-// Use `+` to override schema-level `select: false` without making the
-// projection inclusive.
-const schema = new Schema({
-  foo: { type: String, select: false },
-  bar: String
-});
-// ...
-query.select('+foo'); // Override foo's `select: false` without excluding `bar`
-
-// or you may use object notation, useful when
-// you have keys already prefixed with a "-"
-query.select({ a: 1, b: 1 });
-query.select({ c: 0, d: 0 });

Query.prototype.selected()

Returns:
  • «Boolean»

Determines if field selection has been made.


Query.prototype.selectedExclusively()

Returns:
  • «Boolean»

Determines if exclusive field selection has been made.

- -
query.selectedExclusively() // false
-query.select('-name')
-query.selectedExclusively() // true
-query.selectedInclusively() // false

Query.prototype.selectedInclusively()

Returns:
  • «Boolean»

Determines if inclusive field selection has been made.

- -
query.selectedInclusively() // false
-query.select('name')
-query.selectedInclusively() // true

Query.prototype.session()

Parameters
  • [session] -«ClientSession» from await conn.startSession()
Returns:
  • «Query» this

Sets the MongoDB session associated with this query. Sessions are how you mark a query as part of a transaction.

- -

Calling session(null) removes the session from this query.

- -

Example:

- -
const s = await mongoose.startSession();
-await mongoose.model('Person').findOne({ name: 'Axl Rose' }).session(s);

Query.prototype.set()

Parameters
  • path -«String|Object» path or object of key/value pairs to set
  • [val] -«Any» the value to set
Returns:
  • «Query» this

Adds a $set to this query's update without changing the operation. This is useful for query middleware so you can add an update regardless of whether you use updateOne(), updateMany(), findOneAndUpdate(), etc.

- -

Example:

- -
// Updates `{ $set: { updatedAt: new Date() } }`
-new Query().updateOne({}, {}).set('updatedAt', new Date());
-new Query().updateMany({}, {}).set({ updatedAt: new Date() });

Query.prototype.setOptions()

Parameters
  • options -«Object»
Returns:
  • «Query» this

Sets query options. Some options only make sense for certain operations.

- -

Options:

- -

The following options are only for find():

- - - -

The following options are only for write operations: update(), updateOne(), updateMany(), replaceOne(), findOneAndUpdate(), and findByIdAndUpdate():

- -
    -
  • upsert
  • -
  • writeConcern
  • -
  • timestamps: If timestamps is set in the schema, set this option to false to skip timestamps for that particular update. Has no effect if timestamps is not enabled in the schema options.
  • -
  • omitUndefined: delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
  • -
- -

The following options are only for find(), findOne(), findById(), findOneAndUpdate(), and findByIdAndUpdate():

- - - -

The following options are only for all operations except update(), updateOne(), updateMany(), remove(), deleteOne(), and deleteMany():

- - - -

The following options are for all operations

- -

Query.prototype.setQuery()

Parameters
  • new -«Object» query conditions
Returns:
  • «undefined»

Sets the query conditions to the provided JSON object.

- -

Example:

- -
var query = new Query();
-query.find({ a: 1 })
-query.setQuery({ a: 2 });
-query.getQuery(); // { a: 2 }

Query.prototype.setUpdate()

Parameters
  • new -«Object» update operation
Returns:
  • «undefined»

Sets the current update operation to new value.

- -

Example:

- -
var query = new Query();
-query.update({}, { $set: { a: 5 } });
-query.setUpdate({ $set: { b: 6 } });
-query.getUpdate(); // { $set: { b: 6 } }

Query.prototype.size()

Parameters
  • [path] -«String»
  • val -«Number»

Specifies a $size query condition.

- -

When called with one argument, the most recent path passed to where() is used.

- -

Example

- -
MyModel.where('tags').size(0).exec(function (err, docs) {
-  if (err) return handleError(err);
-
-  assert(Array.isArray(docs));
-  console.log('documents with 0 tags', docs);
-})

Query.prototype.skip()

Parameters
  • val -«Number»

Specifies the number of documents to skip.

- -

Example

- -
query.skip(100).limit(20)
- -

Note

- -

Cannot be used with distinct()


Query.prototype.slaveOk()

Parameters
  • v -«Boolean» defaults to true
Returns:
  • «Query» this

DEPRECATED Sets the slaveOk option.

- -

Deprecated in MongoDB 2.2 in favor of read preferences.

- -

Example:

- -
query.slaveOk() // true
-query.slaveOk(true)
-query.slaveOk(false)

Query.prototype.slice()

Parameters
  • [path] -«String»
  • val -«Number» number/range of elements to slice
Returns:
  • «Query» this

Specifies a $slice projection for an array.

- -

Example

- -
query.slice('comments', 5)
-query.slice('comments', -5)
-query.slice('comments', [10, 5])
-query.where('comments').slice(5)
-query.where('comments').slice([-10, 5])

Query.prototype.snapshot()

Returns:
  • «Query» this

Specifies this query as a snapshot query.

- -

Example

- -
query.snapshot() // true
-query.snapshot(true)
-query.snapshot(false)
- -

Note

- -

Cannot be used with distinct()


Query.prototype.sort()

Parameters
  • arg -«Object|String»
Returns:
  • «Query» this

Sets the sort order

- -

If an object is passed, values allowed are asc, desc, ascending, descending, 1, and -1.

- -

If a string is passed, it must be a space delimited list of path names. The sort order of each path is ascending unless the path name is prefixed with - which will be treated as descending.

- -

Example

- -
// sort by "field" ascending and "test" descending
-query.sort({ field: 'asc', test: -1 });
-
-// equivalent
-query.sort('field -test');
- -

Note

- -

Cannot be used with distinct()


Query.prototype.tailable()

Parameters
  • bool -«Boolean» defaults to true
  • [opts] -«Object» options to set
    • [opts.numberOfRetries] -«Number» if cursor is exhausted, retry this many times before giving up
    • [opts.tailableRetryInterval] -«Number» if cursor is exhausted, wait this many milliseconds before retrying

Sets the tailable option (for use with capped collections).

- -

Example

- -
query.tailable() // true
-query.tailable(true)
-query.tailable(false)
- -

Note

- -

Cannot be used with distinct()


Query.prototype.then()

Parameters
  • [resolve] -«Function»
  • [reject] -«Function»
Returns:
  • «Promise»

Executes the query returning a Promise which will be resolved with either the doc(s) or rejected with the error.


Query.prototype.toConstructor()

Returns:
  • «Query» subclass-of-Query

Converts this query to a customized, reusable query constructor with all arguments and options retained.

- -

Example

- -
// Create a query for adventure movies and read from the primary
-// node in the replica-set unless it is down, in which case we'll
-// read from a secondary node.
-var query = Movie.find({ tags: 'adventure' }).read('primaryPreferred');
-
-// create a custom Query constructor based off these settings
-var Adventure = query.toConstructor();
-
-// Adventure is now a subclass of mongoose.Query and works the same way but with the
-// default query parameters and options set.
-Adventure().exec(callback)
-
-// further narrow down our query results while still using the previous settings
-Adventure().where({ name: /^Life/ }).exec(callback);
-
-// since Adventure is a stand-alone constructor we can also add our own
-// helper methods and getters without impacting global queries
-Adventure.prototype.startsWith = function (prefix) {
-  this.where({ name: new RegExp('^' + prefix) })
-  return this;
-}
-Object.defineProperty(Adventure.prototype, 'highlyRated', {
-  get: function () {
-    this.where({ rating: { $gt: 4.5 }});
-    return this;
-  }
-})
-Adventure().highlyRated.startsWith('Life').exec(callback)

Query.prototype.update()

Parameters
  • [filter] -«Object»
  • [doc] -«Object» the update command
  • [options] -«Object»
    • [options.multipleCastError] -«Boolean» by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [options.upsert=false] -«Boolean» if true, and no documents found, insert a new document
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [options.timestamps=null] -«Boolean» If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
  • [callback] -«Function» params are (error, writeOpResult)
Returns:
  • «Query» this

Declare and/or execute this query as an update() operation.

- -

All paths passed that are not atomic operations will become $set ops.

- -

This function triggers the following middleware.

- -
    -
  • update()
  • -
- -

Example

- -
Model.where({ _id: id }).update({ title: 'words' })
-
-// becomes
-
-Model.where({ _id: id }).update({ $set: { title: 'words' }})
- -

Valid options:

- -
    -
  • upsert (boolean) whether to create the doc if it doesn't match (false)
  • -
  • multi (boolean) whether multiple documents should be updated (false)
  • -
  • runValidators: if true, runs update validators on this command. Update validators validate the update operation against the model's schema.
  • -
  • setDefaultsOnInsert: if this and upsert are true, mongoose will apply the defaults specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on MongoDB's $setOnInsert operator.
  • -
  • strict (boolean) overrides the strict option for this update
  • -
  • overwrite (boolean) disables update-only mode, allowing you to overwrite the doc (false)
  • -
  • context (string) if set to 'query' and runValidators is on, this will refer to the query in custom validator functions that update validation runs. Does nothing if runValidators is false.
  • -
  • read
  • -
  • writeConcern
  • -
- -

Note

- -

Passing an empty object {} as the doc will result in a no-op unless the overwrite option is passed. Without the overwrite option set, the update operation will be ignored and the callback executed without sending the command to MongoDB so as to prevent accidently overwritting documents in the collection.

- -

Note

- -

The operation is only executed when a callback is passed. To force execution without a callback, we must first call update() and then execute it by using the exec() method.

- -
var q = Model.where({ _id: id });
-q.update({ $set: { name: 'bob' }}).update(); // not executed
-
-q.update({ $set: { name: 'bob' }}).exec(); // executed
-
-// keys that are not [atomic](https://docs.mongodb.com/manual/tutorial/model-data-for-atomic-operations/#pattern) ops become `$set`.
-// this executes the same command as the previous example.
-q.update({ name: 'bob' }).exec();
-
-// overwriting with empty docs
-var q = Model.where({ _id: id }).setOptions({ overwrite: true })
-q.update({ }, callback); // executes
-
-// multi update with overwrite to empty doc
-var q = Model.where({ _id: id });
-q.setOptions({ multi: true, overwrite: true })
-q.update({ });
-q.update(callback); // executed
-
-// multi updates
-Model.where()
-     .update({ name: /^match/ }, { $set: { arr: [] }}, { multi: true }, callback)
-
-// more multi updates
-Model.where()
-     .setOptions({ multi: true })
-     .update({ $set: { arr: [] }}, callback)
-
-// single update by default
-Model.where({ email: 'address@example.com' })
-     .update({ $inc: { counter: 1 }}, callback)
-
- -

API summary

- -
update(filter, doc, options, cb) // executes
-update(filter, doc, options)
-update(filter, doc, cb) // executes
-update(filter, doc)
-update(doc, cb) // executes
-update(doc)
-update(cb) // executes
-update(true) // executes
-update()

Query.prototype.updateMany()

Parameters
  • [filter] -«Object»
  • [doc] -«Object» the update command
  • [options] -«Object»
    • [options.multipleCastError] -«Boolean» by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [options.upsert=false] -«Boolean» if true, and no documents found, insert a new document
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [options.timestamps=null] -«Boolean» If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
  • [callback] -«Function» params are (error, writeOpResult)
Returns:
  • «Query» this

Declare and/or execute this query as an updateMany() operation. Same as update(), except MongoDB will update all documents that match filter (as opposed to just the first one) regardless of the value of the multi option.

- -

Note updateMany will not fire update middleware. Use pre('updateMany') and post('updateMany') instead.

- -

Example:

- -
const res = await Person.updateMany({ name: /Stark$/ }, { isDeleted: true });
-res.n; // Number of documents matched
-res.nModified; // Number of documents modified
- -

This function triggers the following middleware.

- -
    -
  • updateMany()
  • -

Query.prototype.updateOne()

Parameters
  • [filter] -«Object»
  • [doc] -«Object» the update command
  • [options] -«Object»
    • [options.multipleCastError] -«Boolean» by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [options.upsert=false] -«Boolean» if true, and no documents found, insert a new document
    • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [options.timestamps=null] -«Boolean» If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
  • [callback] -«Function» params are (error, writeOpResult)
Returns:
  • «Query» this

Declare and/or execute this query as an updateOne() operation. Same as update(), except it does not support the multi or overwrite options.

- -
    -
  • MongoDB will update only the first document that matches filter regardless of the value of the multi option.
  • -
  • Use replaceOne() if you want to overwrite an entire document rather than using atomic operators like $set.
  • -
- -

Note updateOne will not fire update middleware. Use pre('updateOne') and post('updateOne') instead.

- -

Example:

- -
const res = await Person.updateOne({ name: 'Jean-Luc Picard' }, { ship: 'USS Enterprise' });
-res.n; // Number of documents matched
-res.nModified; // Number of documents modified
- -

This function triggers the following middleware.

- -
    -
  • updateOne()
  • -

Query.prototype.use$geoWithin

Type:
  • «property»

Flag to opt out of using $geoWithin.

- -
mongoose.Query.use$geoWithin = false;
- -

MongoDB 2.4 deprecated the use of $within, replacing it with $geoWithin. Mongoose uses $geoWithin by default (which is 100% backward compatible with $within). If you are running an older version of MongoDB, set this flag to false so your within() queries continue to work.


Query.prototype.w()

Parameters
  • val -«String|number» 0 for fire-and-forget, 1 for acknowledged by one server, 'majority' for majority of the replica set, or any of the more advanced options.
Returns:
  • «Query» this

Sets the specified number of mongod servers, or tag set of mongod servers, that must acknowledge this write before this write is considered successful.

- -

This option is only valid for operations that write to the database

- -
    -
  • deleteOne()
  • -
  • deleteMany()
  • -
  • findOneAndDelete()
  • -
  • findOneAndReplace()
  • -
  • findOneAndUpdate()
  • -
  • remove()
  • -
  • update()
  • -
  • updateOne()
  • -
  • updateMany()
  • -
- -

Defaults to the schema's writeConcern.w option

- -

Example:

- -
// The 'majority' option means the `deleteOne()` promise won't resolve
-// until the `deleteOne()` has propagated to the majority of the replica set
-await mongoose.model('Person').
-  deleteOne({ name: 'Ned Stark' }).
-  w('majority');

Query.prototype.where()

Parameters
  • [path] -«String|Object»
  • [val] -«any»
Returns:
  • «Query» this

Specifies a path for use with chaining.

- -

Example

- -
// instead of writing:
-User.find({age: {$gte: 21, $lte: 65}}, callback);
-
-// we can instead write:
-User.where('age').gte(21).lte(65);
-
-// passing query conditions is permitted
-User.find().where({ name: 'vonderful' })
-
-// chaining
-User
-.where('age').gte(21).lte(65)
-.where('name', /^vonderful/i)
-.where('friends').slice(10)
-.exec(callback)

Query.prototype.within()

Returns:
  • «Query» this

Defines a $within or $geoWithin argument for geo-spatial queries.

- -

Example

- -
query.where(path).within().box()
-query.where(path).within().circle()
-query.where(path).within().geometry()
-
-query.where('loc').within({ center: [50,50], radius: 10, unique: true, spherical: true });
-query.where('loc').within({ box: [[40.73, -73.9], [40.7, -73.988]] });
-query.where('loc').within({ polygon: [[],[],[],[]] });
-
-query.where('loc').within([], [], []) // polygon
-query.where('loc').within([], []) // box
-query.where('loc').within({ type: 'LineString', coordinates: [...] }); // geometry
- -

MUST be used after where().

- -

NOTE:

- -

As of Mongoose 3.7, $geoWithin is always used for queries. To change this behavior, see Query.use$geoWithin.

- -

NOTE:

- -

In Mongoose 3.7, within changed from a getter to a function. If you need the old syntax, use this.


Query.prototype.wtimeout()

Parameters
  • ms -«number» number of milliseconds to wait
Returns:
  • «Query» this

If w > 1, 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

- -
    -
  • deleteOne()
  • -
  • deleteMany()
  • -
  • findOneAndDelete()
  • -
  • findOneAndReplace()
  • -
  • findOneAndUpdate()
  • -
  • remove()
  • -
  • update()
  • -
  • updateOne()
  • -
  • updateMany()
  • -
- -

Defaults to the schema's writeConcern.wtimeout option

- -

Example:

- -
// The `deleteOne()` promise won't resolve until this `deleteOne()` has
-// propagated to at least `w = 2` members of the replica set. If it takes
-// longer than 1 second, this `deleteOne()` will fail.
-await mongoose.model('Person').
-  deleteOne({ name: 'Ned Stark' }).
-  w(2).
-  wtimeout(1000);

QueryCursor


QueryCursor()

Parameters
  • query -«Query»
  • options -«Object» query options passed to .find()

A QueryCursor is a concurrency primitive for processing query results one document at a time. A QueryCursor fulfills the Node.js streams3 API, in addition to several other mechanisms for loading documents from MongoDB one at a time.

- -

QueryCursors execute the model's pre find hooks, but not the model's post find hooks.

- -

Unless you're an advanced user, do not instantiate this class directly. Use Query#cursor() instead.


QueryCursor.prototype.addCursorFlag()

Parameters
  • flag -«String»
  • value -«Boolean»
Returns:
  • «AggregationCursor» this

Adds a cursor flag. Useful for setting the noCursorTimeout and tailable flags.


QueryCursor.prototype.close()

Parameters
  • callback -«Function»
Returns:
  • «Promise»

Marks this cursor as closed. Will stop streaming and subsequent calls to next() will error.


QueryCursor.prototype.eachAsync()

Parameters
  • fn -«Function»
  • [options] -«Object»
    • [options.parallel] -«Number» the number of promises to execute in parallel. Defaults to 1.
  • [callback] -«Function» executed when all docs have been processed
Returns:
  • «Promise»

Execute fn for every document in the cursor. If fn returns a promise, will wait for the promise to resolve before iterating on to the next one. Returns a promise that resolves when done.


QueryCursor.prototype.map()

Parameters
  • fn -«Function»
Returns:
  • «QueryCursor»

Registers a transform function which subsequently maps documents retrieved via the streams interface or .next()

- -

Example

- -
// Map documents returned by `data` events
-Thing.
-  find({ name: /^hello/ }).
-  cursor().
-  map(function (doc) {
-   doc.foo = "bar";
-   return doc;
-  })
-  on('data', function(doc) { console.log(doc.foo); });
-
-// Or map documents returned by `.next()`
-var cursor = Thing.find({ name: /^hello/ }).
-  cursor().
-  map(function (doc) {
-    doc.foo = "bar";
-    return doc;
-  });
-cursor.next(function(error, doc) {
-  console.log(doc.foo);
-});

QueryCursor.prototype.next()

Parameters
  • callback -«Function»
Returns:
  • «Promise»

Get the next document from this cursor. Will return null when there are no documents left.


Aggregate


Aggregate()

Parameters
  • [pipeline] -«Array» aggregation pipeline as an array of objects

Aggregate constructor used for building aggregation pipelines. Do not instantiate this class directly, use Model.aggregate() instead.

- -

Example:

- -
const aggregate = Model.aggregate([
-  { $project: { a: 1, b: 1 } },
-  { $skip: 5 }
-]);
-
-Model.
-  aggregate([{ $match: { age: { $gte: 21 }}}]).
-  unwind('tags').
-  exec(callback);
- -

Note:

- -
    -
  • The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned).
  • -
  • Mongoose does not cast pipeline stages. The below will not work unless _id is a string in the database
  • -
- -
  new Aggregate([{ $match: { _id: '00000000000000000000000a' } }]);
-  // Do this instead to cast to an ObjectId
-  new Aggregate([{ $match: { _id: mongoose.Types.ObjectId('00000000000000000000000a') } }]);
-

Aggregate.prototype.Symbol.asyncIterator()

Returns an asyncIterator for use with for/await/of loops This function only works for find() queries. You do not need to call this function explicitly, the JavaScript runtime will call it for you.

- -

Example

- -
for await (const doc of Model.find().sort({ name: 1 })) {
-  console.log(doc.name);
-}
- -

Node.js 10.x supports async iterators natively without any flags. You can enable async iterators in Node.js 8.x using the --harmony_async_iteration flag.

- -

Note: This function is not if Symbol.asyncIterator is undefined. If Symbol.asyncIterator is undefined, that means your Node.js version does not support async iterators.


Aggregate.prototype.addCursorFlag

Parameters
  • flag -«String»
  • value -«Boolean»
Returns:
  • «Aggregate» this
Type:
  • «property»

Sets an option on this aggregation. This function will be deprecated in a future release. Use the cursor(), collation(), etc. helpers to set individual options, or access agg.options directly.

- -

Note that MongoDB aggregations do not support the noCursorTimeout flag, if you try setting that flag with this function you will get a "unrecognized field 'noCursorTimeout'" error.


Aggregate.prototype.addFields()

Parameters
  • arg -«Object» field specification
Returns:
  • «Aggregate»

Appends a new $addFields operator to this aggregate pipeline. Requires MongoDB v3.4+ to work

- -

Examples:

- -
// adding new fields based on existing fields
-aggregate.addFields({
-    newField: '$b.nested'
-  , plusTen: { $add: ['$val', 10]}
-  , sub: {
-       name: '$a'
-    }
-})
-
-// etc
-aggregate.addFields({ salary_k: { $divide: [ "$salary", 1000 ] } });

Aggregate.prototype.allowDiskUse()

Parameters
  • value -«Boolean» Should tell server it can use hard drive to store data during aggregation.
  • [tags] -«Array» optional tags for this query

Sets the allowDiskUse option for the aggregation query (ignored for < 2.6.0)

- -

Example:

- -
await Model.aggregate([{ $match: { foo: 'bar' } }]).allowDiskUse(true);

Aggregate.prototype.append()

Parameters
  • ops -«Object» operator(s) to append
Returns:
  • «Aggregate»

Appends new operators to this aggregate pipeline

- -

Examples:

- -
aggregate.append({ $project: { field: 1 }}, { $limit: 2 });
-
-// or pass an array
-var pipeline = [{ $match: { daw: 'Logic Audio X' }} ];
-aggregate.append(pipeline);

Aggregate.prototype.catch()

Parameters
  • [reject] -«Function»
Returns:
  • «Promise»

Executes the query returning a Promise which will be resolved with either the doc(s) or rejected with the error. Like .then(), but only takes a rejection handler.


Aggregate.prototype.collation()

Parameters
  • collation -«Object» options
Returns:
  • «Aggregate» this

Adds a collation

- -

Example:

- -
Model.aggregate(..).collation({ locale: 'en_US', strength: 1 }).exec();

Aggregate.prototype.count()

Parameters
  • the -«String» name of the count field
Returns:
  • «Aggregate»

Appends a new $count operator to this aggregate pipeline.

- -

Examples:

- -
aggregate.count("userCount");

Aggregate.prototype.cursor()

Parameters
  • options -«Object»
  • options.batchSize -«Number» set the cursor batch size
    • [options.useMongooseAggCursor] -«Boolean» use experimental mongoose-specific aggregation cursor (for eachAsync() and other query cursor semantics)
Returns:
  • «Aggregate» this

Sets the cursor option option for the aggregation query (ignored for < 2.6.0). Note the different syntax below: .exec() returns a cursor object, and no callback is necessary.

- -

Example:

- -
var cursor = Model.aggregate(..).cursor({ batchSize: 1000 }).exec();
-cursor.eachAsync(function(error, doc) {
-  // use doc
-});

Aggregate.prototype.exec()

Parameters
  • [callback] -«Function»
Returns:
  • «Promise»

Executes the aggregate pipeline on the currently bound Model.

- -

Example:

- -
aggregate.exec(callback);
-
-// Because a promise is returned, the `callback` is optional.
-var promise = aggregate.exec();
-promise.then(..);

Aggregate.prototype.explain()

Parameters
  • callback -«Function»
Returns:
  • «Promise»

Execute the aggregation with explain

- -

Example:

- -
Model.aggregate(..).explain(callback)

Aggregate.prototype.facet()

Parameters
  • facet -«Object» options
Returns:
  • «Aggregate» this

Combines multiple aggregation pipelines.

- -

Example:

- -
Model.aggregate(...)
- .facet({
-   books: [{ groupBy: '$author' }],
-   price: [{ $bucketAuto: { groupBy: '$price', buckets: 2 } }]
- })
- .exec();
-
-// Output: { books: [...], price: [{...}, {...}] }

Aggregate.prototype.graphLookup()

Parameters
  • options -«Object» to $graphLookup as described in the above link
Returns:
  • «Aggregate»

Appends new custom $graphLookup operator(s) to this aggregate pipeline, performing a recursive search on a collection.

- -

Note that graphLookup can only consume at most 100MB of memory, and does not allow disk use even if { allowDiskUse: true } is specified.

- -

Examples:

- -
// Suppose we have a collection of courses, where a document might look like `{ _id: 0, name: 'Calculus', prerequisite: 'Trigonometry'}` and `{ _id: 0, name: 'Trigonometry', prerequisite: 'Algebra' }`
- aggregate.graphLookup({ from: 'courses', startWith: '$prerequisite', connectFromField: 'prerequisite', connectToField: 'name', as: 'prerequisites', maxDepth: 3 }) // this will recursively search the 'courses' collection up to 3 prerequisites

Aggregate.prototype.group()

Parameters
  • arg -«Object» $group operator contents
Returns:
  • «Aggregate»

Appends a new custom $group operator to this aggregate pipeline.

- -

Examples:

- -
aggregate.group({ _id: "$department" });

Aggregate.prototype.hint()

Parameters
  • value -«Object|String» a hint object or the index name

Sets the hint option for the aggregation query (ignored for < 3.6.0)

- -

Example:

- -
Model.aggregate(..).hint({ qty: 1, category: 1 }).exec(callback)

Aggregate.prototype.limit()

Parameters
  • num -«Number» maximum number of records to pass to the next stage
Returns:
  • «Aggregate»

Appends a new $limit operator to this aggregate pipeline.

- -

Examples:

- -
aggregate.limit(10);

Aggregate.prototype.lookup()

Parameters
  • options -«Object» to $lookup as described in the above link
Returns:
  • «Aggregate»

Appends new custom $lookup operator(s) to this aggregate pipeline.

- -

Examples:

- -
aggregate.lookup({ from: 'users', localField: 'userId', foreignField: '_id', as: 'users' });

Aggregate.prototype.match()

Parameters
  • arg -«Object» $match operator contents
Returns:
  • «Aggregate»

Appends a new custom $match operator to this aggregate pipeline.

- -

Examples:

- -
aggregate.match({ department: { $in: [ "sales", "engineering" ] } });

Aggregate.prototype.model()

Parameters
  • [model] -«Model» the model to which the aggregate is to be bound
Returns:
  • «Aggregate,Model» if model is passed, will return this, otherwise will return the model

Get/set the model that this aggregation will execute on.

- -

Example:

- -
const aggregate = MyModel.aggregate([{ $match: { answer: 42 } }]);
-aggregate.model() === MyModel; // true
-
-// Change the model. There's rarely any reason to do this.
-aggregate.model(SomeOtherModel);
-aggregate.model() === SomeOtherModel; // true

Aggregate.prototype.near()

Parameters
  • arg -«Object»
Returns:
  • «Aggregate»

Appends a new $geoNear operator to this aggregate pipeline.

- -

NOTE:

- -

MUST be used as the first operator in the pipeline.

- -

Examples:

- -
aggregate.near({
-  near: [40.724, -73.997],
-  distanceField: "dist.calculated", // required
-  maxDistance: 0.008,
-  query: { type: "public" },
-  includeLocs: "dist.location",
-  uniqueDocs: true,
-  num: 5
-});

Aggregate.prototype.option()

Parameters
Returns:
  • «Aggregate» this

Lets you set arbitrary options, for middleware or plugins.

- -

Example:

- -
var agg = Model.aggregate(..).option({ allowDiskUse: true }); // Set the `allowDiskUse` option
-agg.options; // `{ allowDiskUse: true }`

Aggregate.prototype.options

Type:
  • «property»

Contains options passed down to the aggregate command.

- -

Supported options are

- -

Aggregate.prototype.pipeline()

Returns:
  • «Array»

Returns the current pipeline

- -

Example:

- -
MyModel.aggregate().match({ test: 1 }).pipeline(); // [{ $match: { test: 1 } }]

Aggregate.prototype.project()

Parameters
  • arg -«Object|String» field specification
Returns:
  • «Aggregate»

Appends a new $project operator to this aggregate pipeline.

- -

Mongoose query selection syntax is also supported.

- -

Examples:

- -
// include a, include b, exclude _id
-aggregate.project("a b -_id");
-
-// or you may use object notation, useful when
-// you have keys already prefixed with a "-"
-aggregate.project({a: 1, b: 1, _id: 0});
-
-// reshaping documents
-aggregate.project({
-    newField: '$b.nested'
-  , plusTen: { $add: ['$val', 10]}
-  , sub: {
-       name: '$a'
-    }
-})
-
-// etc
-aggregate.project({ salary_k: { $divide: [ "$salary", 1000 ] } });

Aggregate.prototype.read()

Parameters
  • pref -«String» one of the listed preference options or their aliases
  • [tags] -«Array» optional tags for this query
Returns:
  • «Aggregate» this

Sets the readPreference option for the aggregation query.

- -

Example:

- -
Model.aggregate(..).read('primaryPreferred').exec(callback)

Aggregate.prototype.readConcern()

Parameters
  • level -«String» one of the listed read concern level or their aliases
Returns:
  • «Aggregate» this

Sets the readConcern level for the aggregation query.

- -

Example:

- -
Model.aggregate(..).readConcern('majority').exec(callback)

Aggregate.prototype.redact()

Parameters
  • expression -«Object» redact options or conditional expression
  • [thenExpr] -«String|Object» true case for the condition
  • [elseExpr] -«String|Object» false case for the condition
Returns:
  • «Aggregate» this

Appends a new $redact operator to this aggregate pipeline.

- -

If 3 arguments are supplied, Mongoose will wrap them with if-then-else of $cond operator respectively If thenExpr or elseExpr is string, make sure it starts with $$, like $$DESCEND, $$PRUNE or $$KEEP.

- -

Example:

- -
Model.aggregate(...)
- .redact({
-   $cond: {
-     if: { $eq: [ '$level', 5 ] },
-     then: '$$PRUNE',
-     else: '$$DESCEND'
-   }
- })
- .exec();
-
-// $redact often comes with $cond operator, you can also use the following syntax provided by mongoose
-Model.aggregate(...)
- .redact({ $eq: [ '$level', 5 ] }, '$$PRUNE', '$$DESCEND')
- .exec();

Aggregate.prototype.replaceRoot()

Parameters
  • the -«String|Object» field or document which will become the new root document
Returns:
  • «Aggregate»

Appends a new $replaceRoot operator to this aggregate pipeline.

- -

Note that the $replaceRoot operator requires field strings to start with '$'. If you are passing in a string Mongoose will prepend '$' if the specified field doesn't start '$'. If you are passing in an object the strings in your expression will not be altered.

- -

Examples:

- -
aggregate.replaceRoot("user");
-
-aggregate.replaceRoot({ x: { $concat: ['$this', '$that'] } });

Aggregate.prototype.sample()

Parameters
  • size -«Number» number of random documents to pick
Returns:
  • «Aggregate»

Appends new custom $sample operator(s) to this aggregate pipeline.

- -

Examples:

- -
aggregate.sample(3); // Add a pipeline that picks 3 random documents

Aggregate.prototype.session()

Parameters
  • session -«ClientSession»

Sets the session for this aggregation. Useful for transactions.

- -

Example:

- -
const session = await Model.startSession();
-await Model.aggregate(..).session(session);

Aggregate.prototype.skip()

Parameters
  • num -«Number» number of records to skip before next stage
Returns:
  • «Aggregate»

Appends a new $skip operator to this aggregate pipeline.

- -

Examples:

- -
aggregate.skip(10);

Aggregate.prototype.sort()

Parameters
  • arg -«Object|String»
Returns:
  • «Aggregate» this

Appends a new $sort operator to this aggregate pipeline.

- -

If an object is passed, values allowed are asc, desc, ascending, descending, 1, and -1.

- -

If a string is passed, it must be a space delimited list of path names. The sort order of each path is ascending unless the path name is prefixed with - which will be treated as descending.

- -

Examples:

- -
// these are equivalent
-aggregate.sort({ field: 'asc', test: -1 });
-aggregate.sort('field -test');

Aggregate.prototype.sortByCount()

Parameters
  • arg -«Object|String»
Returns:
  • «Aggregate» this

Appends a new $sortByCount operator to this aggregate pipeline. Accepts either a string field name or a pipeline object.

- -

Note that the $sortByCount operator requires the new root to start with '$'. Mongoose will prepend '$' if the specified field name doesn't start with '$'.

- -

Examples:

- -
aggregate.sortByCount('users');
-aggregate.sortByCount({ $mergeObjects: [ "$employee", "$business" ] })

Aggregate.prototype.then()

Parameters
  • [resolve] -«Function» successCallback
  • [reject] -«Function» errorCallback
Returns:
  • «Promise»

Provides promise for aggregate.

- -

Example:

- -
Model.aggregate(..).then(successCallback, errorCallback);

Aggregate.prototype.unwind()

Parameters
  • fields -«String» the field(s) to unwind
Returns:
  • «Aggregate»

Appends new custom $unwind operator(s) to this aggregate pipeline.

- -

Note that the $unwind operator requires the path name to start with '$'. Mongoose will prepend '$' if the specified field doesn't start '$'.

- -

Examples:

- -
aggregate.unwind("tags");
-aggregate.unwind("a", "b", "c");

AggregationCursor


AggregationCursor()

Parameters
  • agg -«Aggregate»
  • options -«Object»

An AggregationCursor is a concurrency primitive for processing aggregation results one document at a time. It is analogous to QueryCursor.

- -

An AggregationCursor fulfills the Node.js streams3 API, in addition to several other mechanisms for loading documents from MongoDB one at a time.

- -

Creating an AggregationCursor executes the model's pre aggregate hooks, but not the model's post aggregate hooks.

- -

Unless you're an advanced user, do not instantiate this class directly. Use Aggregate#cursor() instead.


AggregationCursor.prototype.addCursorFlag()

Parameters
  • flag -«String»
  • value -«Boolean»
Returns:
  • «AggregationCursor» this

Adds a cursor flag. Useful for setting the noCursorTimeout and tailable flags.


AggregationCursor.prototype.close()

Parameters
  • callback -«Function»
Returns:
  • «Promise»

Marks this cursor as closed. Will stop streaming and subsequent calls to next() will error.


AggregationCursor.prototype.eachAsync()

Parameters
  • fn -«Function»
  • [options] -«Object»
    • [options.parallel] -«Number» the number of promises to execute in parallel. Defaults to 1.
  • [callback] -«Function» executed when all docs have been processed
Returns:
  • «Promise»

Execute fn for every document in the cursor. If fn returns a promise, will wait for the promise to resolve before iterating on to the next one. Returns a promise that resolves when done.


AggregationCursor.prototype.map()

Parameters
  • fn -«Function»
Returns:
  • «AggregationCursor»

Registers a transform function which subsequently maps documents retrieved via the streams interface or .next()

- -

Example

- -
// Map documents returned by `data` events
-Thing.
-  find({ name: /^hello/ }).
-  cursor().
-  map(function (doc) {
-   doc.foo = "bar";
-   return doc;
-  })
-  on('data', function(doc) { console.log(doc.foo); });
-
-// Or map documents returned by `.next()`
-var cursor = Thing.find({ name: /^hello/ }).
-  cursor().
-  map(function (doc) {
-    doc.foo = "bar";
-    return doc;
-  });
-cursor.next(function(error, doc) {
-  console.log(doc.foo);
-});

AggregationCursor.prototype.next()

Parameters
  • callback -«Function»
Returns:
  • «Promise»

Get the next document from this cursor. Will return null when there are no documents left.


Schematype


SchemaType()

Parameters
  • path -«String»
  • [options] -«Object»
  • [instance] -«String»

SchemaType constructor. Do not instantiate SchemaType directly. Mongoose converts your schema paths into SchemaTypes automatically.

- -

Example:

- -
const schema = new Schema({ name: String });
-schema.path('name') instanceof SchemaType; // true

SchemaType.cast()

Parameters
  • caster -«Function|false» Function that casts arbitrary values to this type, or throws an error if casting failed
Returns:
  • «Function»

Get/set the function used to cast arbitrary values to this type.

- -

Example:

- -
// Disallow `null` for numbers, and don't try to cast any values to
-// numbers, so even strings like '123' will cause a CastError.
-mongoose.Number.cast(function(v) {
-  assert.ok(v === undefined || typeof v === 'number');
-  return v;
-});

SchemaType.checkRequired()

Parameters
  • fn -«Function»
Returns:
  • «Function»

Override the function the required validator uses to check whether a value passes the required check. Override this on the individual SchemaType.

- -

Example:

- -
// Use this to allow empty strings to pass the `required` validator
-mongoose.Schema.Types.String.checkRequired(v => typeof v === 'string');

SchemaType.get()

Parameters
  • getter -«Function»
Returns:
  • «this»

Attaches a getter for all instances of this schema type.

- -

Example:

- -
// Make all numbers round down
-mongoose.Number.get(function(v) { return Math.floor(v); });

SchemaType.prototype.default()

Parameters
  • val -«Function|any» the default value
Returns:
  • «defaultValue»

Sets a default value for this SchemaType.

- -

Example:

- -
var schema = new Schema({ n: { type: Number, default: 10 })
-var M = db.model('M', schema)
-var m = new M;
-console.log(m.n) // 10
- -

Defaults can be either functions which return the value to use as the default or the literal value itself. Either way, the value will be cast based on its schema type before being set during document creation.

- -

Example:

- -
// values are cast:
-var schema = new Schema({ aNumber: { type: Number, default: 4.815162342 }})
-var M = db.model('M', schema)
-var m = new M;
-console.log(m.aNumber) // 4.815162342
-
-// default unique objects for Mixed types:
-var schema = new Schema({ mixed: Schema.Types.Mixed });
-schema.path('mixed').default(function () {
-  return {};
-});
-
-// if we don't use a function to return object literals for Mixed defaults,
-// each document will receive a reference to the same object literal creating
-// a "shared" object instance:
-var schema = new Schema({ mixed: Schema.Types.Mixed });
-schema.path('mixed').default({});
-var M = db.model('M', schema);
-var m1 = new M;
-m1.mixed.added = 1;
-console.log(m1.mixed); // { added: 1 }
-var m2 = new M;
-console.log(m2.mixed); // { added: 1 }

SchemaType.prototype.get()

Parameters
  • fn -«Function»
Returns:
  • «SchemaType» this

Adds a getter to this schematype.

- -

Example:

- -
function dob (val) {
-  if (!val) return val;
-  return (val.getMonth() + 1) + "/" + val.getDate() + "/" + val.getFullYear();
-}
-
-// defining within the schema
-var s = new Schema({ born: { type: Date, get: dob })
-
-// or by retreiving its SchemaType
-var s = new Schema({ born: Date })
-s.path('born').get(dob)
- -

Getters allow you to transform the representation of the data as it travels from the raw mongodb document to the value that you see.

- -

Suppose you are storing credit card numbers and you want to hide everything except the last 4 digits to the mongoose user. You can do so by defining a getter in the following way:

- -
function obfuscate (cc) {
-  return '****-****-****-' + cc.slice(cc.length-4, cc.length);
-}
-
-var AccountSchema = new Schema({
-  creditCardNumber: { type: String, get: obfuscate }
-});
-
-var Account = db.model('Account', AccountSchema);
-
-Account.findById(id, function (err, found) {
-  console.log(found.creditCardNumber); // '****-****-****-1234'
-});
- -

Getters are also passed a second argument, the schematype on which the getter was defined. This allows for tailored behavior based on options passed in the schema.

- -
function inspector (val, schematype) {
-  if (schematype.options.required) {
-    return schematype.path + ' is required';
-  } else {
-    return schematype.path + ' is not';
-  }
-}
-
-var VirusSchema = new Schema({
-  name: { type: String, required: true, get: inspector },
-  taxonomy: { type: String, get: inspector }
-})
-
-var Virus = db.model('Virus', VirusSchema);
-
-Virus.findById(id, function (err, virus) {
-  console.log(virus.name);     // name is required
-  console.log(virus.taxonomy); // taxonomy is not
-})

SchemaType.prototype.immutable()

Parameters
  • bool -«Boolean»
Returns:
  • «SchemaType» this

Defines this path as immutable. Mongoose prevents you from changing immutable paths unless the parent document has isNew: true.

- -

Example:

- -
const schema = new Schema({
-  name: { type: String, immutable: true },
-  age: Number
-});
-const Model = mongoose.model('Test', schema);
-
-await Model.create({ name: 'test' });
-const doc = await Model.findOne();
-
-doc.isNew; // false
-doc.name = 'new name';
-doc.name; // 'test', because `name` is immutable
- -

Mongoose also prevents changing immutable properties using updateOne() and updateMany() based on strict mode.

- -

Example:

- -
// Mongoose will strip out the `name` update, because `name` is immutable
-Model.updateOne({}, { $set: { name: 'test2' }, $inc: { age: 1 } });
-
-// If `strict` is set to 'throw', Mongoose will throw an error if you
-// update `name`
-const err = await Model.updateOne({}, { name: 'test2' }, { strict: 'throw' }).
-  then(() => null, err =&gt; err);
-err.name; // StrictModeError
-
-// If `strict` is `false`, Mongoose allows updating `name` even though
-// the property is immutable.
-Model.updateOne({}, { name: 'test2' }, { strict: false });

SchemaType.prototype.index()

Parameters
  • options -«Object|Boolean|String»
Returns:
  • «SchemaType» this

Declares the index options for this schematype.

- -

Example:

- -
var s = new Schema({ name: { type: String, index: true })
-var s = new Schema({ loc: { type: [Number], index: 'hashed' })
-var s = new Schema({ loc: { type: [Number], index: '2d', sparse: true })
-var s = new Schema({ loc: { type: [Number], index: { type: '2dsphere', sparse: true }})
-var s = new Schema({ date: { type: Date, index: { unique: true, expires: '1d' }})
-Schema.path('my.path').index(true);
-Schema.path('my.date').index({ expires: 60 });
-Schema.path('my.path').index({ unique: true, sparse: true });
- -

NOTE:

- -

Indexes are created in the background by default. If background is set to false, MongoDB will not execute any read/write operations you send until the index build. Specify background: false to override Mongoose's default.


SchemaType.prototype.ref()

Parameters
  • ref -«String|Model|Function» either a model name, a Model, or a function that returns a model name or model.
Returns:
  • «SchemaType» this

Set the model that this path refers to. This is the option that populate looks at to determine the foreign collection it should query.

- -

Example:

- -
const userSchema = new Schema({ name: String });
-const User = mongoose.model('User', userSchema);
-
-const postSchema = new Schema({ user: mongoose.ObjectId });
-postSchema.path('user').ref('User'); // By model name
-postSchema.path('user').ref(User); // Can pass the model as well
-
-// Or you can just declare the `ref` inline in your schema
-const postSchema2 = new Schema({
-  user: { type: mongoose.ObjectId, ref: User }
-});

SchemaType.prototype.required()

Parameters
  • required -«Boolean|Function|Object» enable/disable the validator, or function that returns required boolean, or options object
    • [options.isRequired] -«Boolean|Function» enable/disable the validator, or function that returns required boolean
    • [options.ErrorConstructor] -«Function» custom error constructor. The constructor receives 1 parameter, an object containing the validator properties.
  • [message] -«String» optional custom error message
Returns:
  • «SchemaType» this

Adds a required validator to this SchemaType. The validator gets added to the front of this SchemaType's validators array using unshift().

- -

Example:

- -
var s = new Schema({ born: { type: Date, required: true })
-
-// or with custom error message
-
-var s = new Schema({ born: { type: Date, required: '{PATH} is required!' })
-
-// or with a function
-
-var s = new Schema({
-  userId: ObjectId,
-  username: {
-    type: String,
-    required: function() { return this.userId != null; }
-  }
-})
-
-// or with a function and a custom message
-var s = new Schema({
-  userId: ObjectId,
-  username: {
-    type: String,
-    required: [
-      function() { return this.userId != null; },
-      'username is required if id is specified'
-    ]
-  }
-})
-
-// or through the path API
-
-Schema.path('name').required(true);
-
-// with custom error messaging
-
-Schema.path('name').required(true, 'grrr :( ');
-
-// or make a path conditionally required based on a function
-var isOver18 = function() { return this.age >= 18; };
-Schema.path('voterRegistrationId').required(isOver18);
- -

The required validator uses the SchemaType's checkRequired function to determine whether a given value satisfies the required validator. By default, a value satisfies the required validator if val != null (that is, if the value is not null nor undefined). However, most built-in mongoose schema types override the default checkRequired function:


SchemaType.prototype.select()

Parameters
  • val -«Boolean»
Returns:
  • «SchemaType» this

Sets default select() behavior for this path.

- -

Set to true if this path should always be included in the results, false if it should be excluded by default. This setting can be overridden at the query level.

- -

Example:

- -
T = db.model('T', new Schema({ x: { type: String, select: true }}));
-T.find(..); // field x will always be selected ..
-// .. unless overridden;
-T.find().select('-x').exec(callback);

SchemaType.prototype.set()

Parameters
  • fn -«Function»
Returns:
  • «SchemaType» this

Adds a setter to this schematype.

- -

Example:

- -
function capitalize (val) {
-  if (typeof val !== 'string') val = '';
-  return val.charAt(0).toUpperCase() + val.substring(1);
-}
-
-// defining within the schema
-var s = new Schema({ name: { type: String, set: capitalize }});
-
-// or with the SchemaType
-var s = new Schema({ name: String })
-s.path('name').set(capitalize);
- -

Setters allow you to transform the data before it gets to the raw mongodb document or query.

- -

Suppose you are implementing user registration for a website. Users provide an email and password, which gets saved to mongodb. The email is a string that you will want to normalize to lower case, in order to avoid one email having more than one account -- e.g., otherwise, avenue@q.com can be registered for 2 accounts via avenue@q.com and AvEnUe@Q.CoM.

- -

You can set up email lower case normalization easily via a Mongoose setter.

- -
function toLower(v) {
-  return v.toLowerCase();
-}
-
-var UserSchema = new Schema({
-  email: { type: String, set: toLower }
-});
-
-var User = db.model('User', UserSchema);
-
-var user = new User({email: 'AVENUE@Q.COM'});
-console.log(user.email); // 'avenue@q.com'
-
-// or
-var user = new User();
-user.email = 'Avenue@Q.com';
-console.log(user.email); // 'avenue@q.com'
-User.updateOne({ _id: _id }, { $set: { email: 'AVENUE@Q.COM' } }); // update to 'avenue@q.com'
-
- -

As you can see above, setters allow you to transform the data before it stored in MongoDB, or before executing a query.

- -

NOTE: we could have also just used the built-in lowercase: true SchemaType option instead of defining our own function.

- -
new Schema({ email: { type: String, lowercase: true }})
- -

Setters are also passed a second argument, the schematype on which the setter was defined. This allows for tailored behavior based on options passed in the schema.

- -
function inspector (val, schematype) {
-  if (schematype.options.required) {
-    return schematype.path + ' is required';
-  } else {
-    return val;
-  }
-}
-
-var VirusSchema = new Schema({
-  name: { type: String, required: true, set: inspector },
-  taxonomy: { type: String, set: inspector }
-})
-
-var Virus = db.model('Virus', VirusSchema);
-var v = new Virus({ name: 'Parvoviridae', taxonomy: 'Parvovirinae' });
-
-console.log(v.name);     // name is required
-console.log(v.taxonomy); // Parvovirinae
- -

You can also use setters to modify other properties on the document. If you're setting a property name on a document, the setter will run with this as the document. Be careful, in mongoose 5 setters will also run when querying by name with this as the query.

- -
const nameSchema = new Schema({ name: String, keywords: [String] });
-nameSchema.path('name').set(function(v) {
-  // Need to check if `this` is a document, because in mongoose 5
-  // setters will also run on queries, in which case `this` will be a
-  // mongoose query object.
-  if (this instanceof Document && v != null) {
-    this.keywords = v.split(' ');
-  }
-  return v;
-});
-

SchemaType.prototype.sparse()

Parameters
  • bool -«Boolean»
Returns:
  • «SchemaType» this

Declares a sparse index.

- -

Example:

- -
var s = new Schema({ name: { type: String, sparse: true } });
-Schema.path('name').index({ sparse: true });

SchemaType.prototype.text()

Parameters
  • bool -«Boolean»
Returns:
  • «SchemaType» this

Declares a full text index.

- -

Example:

- -
var s = new Schema({name : {type: String, text : true })
- Schema.path('name').index({text : true});

SchemaType.prototype.unique()

Parameters
  • bool -«Boolean»
Returns:
  • «SchemaType» this

Declares an unique index.

- -

Example:

- -
var s = new Schema({ name: { type: String, unique: true }});
-Schema.path('name').index({ unique: true });
- -

NOTE: violating the constraint returns an E11000 error from MongoDB when saving, not a Mongoose validation error.


SchemaType.prototype.validate()

Parameters
  • obj -«RegExp|Function|Object» validator function, or hash describing options
    • [obj.validator] -«Function» validator function. If the validator function returns undefined or a truthy value, validation succeeds. If it returns falsy (except undefined) or throws an error, validation fails.
    • [obj.message] -«String|Function» optional error message. If function, should return the error message as a string
    • [obj.propsParameter=false] -«Boolean» If true, Mongoose will pass the validator properties object (with the validator function, message, etc.) as the 2nd arg to the validator function. This is disabled by default because many validators rely on positional args, so turning this on may cause unpredictable behavior in external validators.
  • [errorMsg] -«String|Function» optional error message. If function, should return the error message as a string
  • [type] -«String» optional validator type
Returns:
  • «SchemaType» this

Adds validator(s) for this document path.

- -

Validators always receive the value to validate as their first argument and 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 will be used.

- -

Examples:

- -
// make sure every value is equal to "something"
-function validator (val) {
-  return val == 'something';
-}
-new Schema({ name: { type: String, validate: validator }});
-
-// with a custom error message
-
-var custom = [validator, 'Uh oh, {PATH} does not equal "something".']
-new Schema({ name: { type: String, validate: custom }});
-
-// adding many validators at a time
-
-var many = [
-    { validator: validator, msg: 'uh oh' }
-  , { validator: anotherValidator, msg: 'failed' }
-]
-new Schema({ name: { type: String, validate: many }});
-
-// or utilizing SchemaType methods directly:
-
-var schema = new Schema({ name: 'string' });
-schema.path('name').validate(validator, 'validation of `{PATH}` failed with value `{VALUE}`');
- -

Error message templates:

- -

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.

- -

If Mongoose's built-in error message templating isn't enough, Mongoose supports setting the message property to a function.

- -
schema.path('name').validate({
-  validator: function() { return v.length > 5; },
-  // `errors['name']` will be "name must have length 5, got 'foo'"
-  message: function(props) {
-    return `${props.path} must have length 5, got '${props.value}'`;
-  }
-});
- -

To bypass Mongoose's error messages and just copy the error message that the validator throws, do this:

- -
schema.path('name').validate({
-  validator: function() { throw new Error('Oops!'); },
-  // `errors['name']` will be "Oops!"
-  message: function(props) { return props.reason.message; }
-});
- -

Asynchronous validation:

- -

Mongoose supports validators that return a promise. A validator that returns a promise is called an async validator. Async validators run in parallel, and validate() will wait until all async validators have settled.

- -
schema.path('name').validate({
-  validator: function (value) {
-    return new Promise(function (resolve, reject) {
-      resolve(false); // validation failed
-    });
-  }
-});
- -

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.

- -

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, passing the validation error object along.

- -
var conn = mongoose.createConnection(..);
-conn.on('error', handleError);
-
-var Product = conn.model('Product', yourSchema);
-var dvd = new Product(..);
-dvd.save(); // emits error on the `conn` above
- -

If you want to handle these errors at the Model level, add an error listener to your Model as shown below.

- -
// registering an error listener on the Model lets us handle errors more locally
-Product.on('error', handleError);

Virtualtype


VirtualType()

Parameters
  • options -«Object»
    • [options.ref] -«string|function» if ref is not nullish, this becomes a populated virtual
    • [options.localField] -«string|function» the local field to populate on if this is a populated virtual.
    • [options.foreignField] -«string|function» the foreign field to populate on if this is a populated virtual.
    • [options.justOne=false] -«boolean» by default, a populated virtual is an array. If you set justOne, the populated virtual will be a single doc or null.
    • [options.getters=false] -«boolean» if you set this to true, Mongoose will call any custom getters you defined on this virtual
    • [options.count=false] -«boolean» 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()

VirtualType constructor

- -

This is what mongoose uses to define virtual attributes via Schema.prototype.virtual.

- -

Example:

- -
const fullname = schema.virtual('fullname');
-fullname instanceof mongoose.VirtualType // true

VirtualType.prototype.applyGetters()

Parameters
  • value -«Object»
  • doc -«Document» The document this virtual is attached to
Returns:
  • «any» the value after applying all getters

Applies getters to value.


VirtualType.prototype.applySetters()

Parameters
  • value -«Object»
  • doc -«Document»
Returns:
  • «any» the value after applying all setters

Applies setters to value.


VirtualType.prototype.get()

Parameters
  • VirtualType, -«Function(Any|» Document)} fn
Returns:
  • «VirtualType» this

Adds a custom getter to this virtual.

- -

Mongoose calls the getter function with 3 parameters

- -
    -
  • value: the value returned by the previous getter. If there is only one getter, value will be undefined.
  • -
  • virtual: the virtual object you called .get() on
  • -
  • doc: the document this virtual is attached to. Equivalent to this.
  • -
- -

Example:

- -
var virtual = schema.virtual('fullname');
-virtual.get(function(value, virtual, doc) {
-  return this.name.first + ' ' + this.name.last;
-});

VirtualType.prototype.set()

Parameters
  • VirtualType, -«Function(Any|» Document)} fn
Returns:
  • «VirtualType» this

Adds a custom setter to this virtual.

- -

Mongoose calls the setter function with 3 parameters

- -
    -
  • value: the value being set
  • -
  • virtual: the virtual object you're calling .set() on
  • -
  • doc: the document this virtual is attached to. Equivalent to this.
  • -
- -

Example:

- -
const virtual = schema.virtual('fullname');
-virtual.set(function(value, virtual, doc) {
-  var parts = value.split(' ');
-  this.name.first = parts[0];
-  this.name.last = parts[1];
-});
-
-const Model = mongoose.model('Test', schema);
-const doc = new Model();
-// Calls the setter with `value = 'Jean-Luc Picard'`
-doc.fullname = 'Jean-Luc Picard';
-doc.name.first; // 'Jean-Luc'
-doc.name.last; // 'Picard'

Error


Error()

Parameters
  • msg -«String» Error message

MongooseError constructor. MongooseError is the base class for all Mongoose-specific errors.

- -

Example:

- -
const Model = mongoose.model('Test', new Schema({ answer: Number }));
-const doc = new Model({ answer: 'not a number' });
-const err = doc.validateSync();
-
-err instanceof mongoose.Error; // true

Error.CastError

Type:
  • «property»

An instance of this error class will be returned when mongoose failed to cast a value.


Error.DivergentArrayError

Type:
  • «property»

An instance of this error will be returned if you used an array projection and then modified the array in an unsafe way.


Error.DocumentNotFoundError

Type:
  • «property»

An instance of this error class will be returned when save() fails because the underlying document was not found. The constructor takes one parameter, the conditions that mongoose passed to update() when trying to update the document.


Error.MissingSchemaError

Type:
  • «property»

Thrown when you try to access a model that has not been registered yet


Error.OverwriteModelError

Type:
  • «property»

Thrown when a model with the given name was already registered on the connection. See the FAQ about OverwriteModelError.


Error.ParallelSaveError

Type:
  • «property»

An instance of this error class will be returned when you call save() multiple times on the same document in parallel. See the FAQ for more information.


Error.ValidationError

Type:
  • «property»

An instance of this error class will be returned when validation failed. The errors property contains an object whose keys are the paths that failed and whose values are instances of CastError or ValidationError.


Error.ValidatorError

Type:
  • «property»

A ValidationError has a hash of errors that contain individual ValidatorError instances


Error.VersionError

Type:
  • «property»

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 for more information.


Error.messages

Type:
  • «property»

The default built-in validator error messages.


Error.prototype.name

Type:
  • «String»

The name of the error. The name uniquely identifies this Mongoose error. The possible values are:

- -
    -
  • MongooseError: general Mongoose error
  • -
  • CastError: Mongoose could not convert a value to the type defined in the schema path. May be in a ValidationError class' errors property.
  • -
  • DisconnectedError: This connection timed out in trying to reconnect to MongoDB and will not successfully reconnect to MongoDB unless you explicitly reconnect.
  • -
  • DivergentArrayError: You attempted to save() an array that was modified after you loaded it with a $elemMatch or similar projection
  • -
  • MissingSchemaError: You tried to access a model with mongoose.model() that was not defined
  • -
  • DocumentNotFoundError: The document you tried to save() was not found
  • -
  • ValidatorError: error from an individual schema path's validator
  • -
  • ValidationError: error returned from validate() or 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.
  • -
  • 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() to re-define a model that was already defined.
  • -
  • ParallelSaveError: Thrown when you call 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 is set to throw.
  • -
  • VersionError: Thrown when the document is out of sync
  • -

Array

    \ No newline at end of file diff --git a/docs/api/aggregate.html b/docs/api/aggregate.html deleted file mode 100644 index b53ec2c853f..00000000000 --- a/docs/api/aggregate.html +++ /dev/null @@ -1,388 +0,0 @@ -Mongoose v5.6.0:

    Aggregate


    Aggregate()

    Parameters
    • [pipeline] -«Array» aggregation pipeline as an array of objects

    Aggregate constructor used for building aggregation pipelines. Do not instantiate this class directly, use Model.aggregate() instead.

    - -

    Example:

    - -
    const aggregate = Model.aggregate([
    -  { $project: { a: 1, b: 1 } },
    -  { $skip: 5 }
    -]);
    -
    -Model.
    -  aggregate([{ $match: { age: { $gte: 21 }}}]).
    -  unwind('tags').
    -  exec(callback);
    - -

    Note:

    - -
      -
    • The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned).
    • -
    • Mongoose does not cast pipeline stages. The below will not work unless _id is a string in the database
    • -
    - -
      new Aggregate([{ $match: { _id: '00000000000000000000000a' } }]);
    -  // Do this instead to cast to an ObjectId
    -  new Aggregate([{ $match: { _id: mongoose.Types.ObjectId('00000000000000000000000a') } }]);
    -

    Aggregate.prototype.Symbol.asyncIterator()

    Returns an asyncIterator for use with for/await/of loops This function only works for find() queries. You do not need to call this function explicitly, the JavaScript runtime will call it for you.

    - -

    Example

    - -
    for await (const doc of Model.find().sort({ name: 1 })) {
    -  console.log(doc.name);
    -}
    - -

    Node.js 10.x supports async iterators natively without any flags. You can enable async iterators in Node.js 8.x using the --harmony_async_iteration flag.

    - -

    Note: This function is not if Symbol.asyncIterator is undefined. If Symbol.asyncIterator is undefined, that means your Node.js version does not support async iterators.


    Aggregate.prototype.addCursorFlag

    Parameters
    • flag -«String»
    • value -«Boolean»
    Returns:
    • «Aggregate» this
    Type:
    • «property»

    Sets an option on this aggregation. This function will be deprecated in a future release. Use the cursor(), collation(), etc. helpers to set individual options, or access agg.options directly.

    - -

    Note that MongoDB aggregations do not support the noCursorTimeout flag, if you try setting that flag with this function you will get a "unrecognized field 'noCursorTimeout'" error.


    Aggregate.prototype.addFields()

    Parameters
    • arg -«Object» field specification
    Returns:
    • «Aggregate»

    Appends a new $addFields operator to this aggregate pipeline. Requires MongoDB v3.4+ to work

    - -

    Examples:

    - -
    // adding new fields based on existing fields
    -aggregate.addFields({
    -    newField: '$b.nested'
    -  , plusTen: { $add: ['$val', 10]}
    -  , sub: {
    -       name: '$a'
    -    }
    -})
    -
    -// etc
    -aggregate.addFields({ salary_k: { $divide: [ "$salary", 1000 ] } });

    Aggregate.prototype.allowDiskUse()

    Parameters
    • value -«Boolean» Should tell server it can use hard drive to store data during aggregation.
    • [tags] -«Array» optional tags for this query

    Sets the allowDiskUse option for the aggregation query (ignored for < 2.6.0)

    - -

    Example:

    - -
    await Model.aggregate([{ $match: { foo: 'bar' } }]).allowDiskUse(true);

    Aggregate.prototype.append()

    Parameters
    • ops -«Object» operator(s) to append
    Returns:
    • «Aggregate»

    Appends new operators to this aggregate pipeline

    - -

    Examples:

    - -
    aggregate.append({ $project: { field: 1 }}, { $limit: 2 });
    -
    -// or pass an array
    -var pipeline = [{ $match: { daw: 'Logic Audio X' }} ];
    -aggregate.append(pipeline);

    Aggregate.prototype.catch()

    Parameters
    • [reject] -«Function»
    Returns:
    • «Promise»

    Executes the query returning a Promise which will be resolved with either the doc(s) or rejected with the error. Like .then(), but only takes a rejection handler.


    Aggregate.prototype.collation()

    Parameters
    • collation -«Object» options
    Returns:
    • «Aggregate» this

    Adds a collation

    - -

    Example:

    - -
    Model.aggregate(..).collation({ locale: 'en_US', strength: 1 }).exec();

    Aggregate.prototype.count()

    Parameters
    • the -«String» name of the count field
    Returns:
    • «Aggregate»

    Appends a new $count operator to this aggregate pipeline.

    - -

    Examples:

    - -
    aggregate.count("userCount");

    Aggregate.prototype.cursor()

    Parameters
    • options -«Object»
    • options.batchSize -«Number» set the cursor batch size
      • [options.useMongooseAggCursor] -«Boolean» use experimental mongoose-specific aggregation cursor (for eachAsync() and other query cursor semantics)
    Returns:
    • «Aggregate» this

    Sets the cursor option option for the aggregation query (ignored for < 2.6.0). Note the different syntax below: .exec() returns a cursor object, and no callback is necessary.

    - -

    Example:

    - -
    var cursor = Model.aggregate(..).cursor({ batchSize: 1000 }).exec();
    -cursor.eachAsync(function(error, doc) {
    -  // use doc
    -});

    Aggregate.prototype.exec()

    Parameters
    • [callback] -«Function»
    Returns:
    • «Promise»

    Executes the aggregate pipeline on the currently bound Model.

    - -

    Example:

    - -
    aggregate.exec(callback);
    -
    -// Because a promise is returned, the `callback` is optional.
    -var promise = aggregate.exec();
    -promise.then(..);

    Aggregate.prototype.explain()

    Parameters
    • callback -«Function»
    Returns:
    • «Promise»

    Execute the aggregation with explain

    - -

    Example:

    - -
    Model.aggregate(..).explain(callback)

    Aggregate.prototype.facet()

    Parameters
    • facet -«Object» options
    Returns:
    • «Aggregate» this

    Combines multiple aggregation pipelines.

    - -

    Example:

    - -
    Model.aggregate(...)
    - .facet({
    -   books: [{ groupBy: '$author' }],
    -   price: [{ $bucketAuto: { groupBy: '$price', buckets: 2 } }]
    - })
    - .exec();
    -
    -// Output: { books: [...], price: [{...}, {...}] }

    Aggregate.prototype.graphLookup()

    Parameters
    • options -«Object» to $graphLookup as described in the above link
    Returns:
    • «Aggregate»

    Appends new custom $graphLookup operator(s) to this aggregate pipeline, performing a recursive search on a collection.

    - -

    Note that graphLookup can only consume at most 100MB of memory, and does not allow disk use even if { allowDiskUse: true } is specified.

    - -

    Examples:

    - -
    // Suppose we have a collection of courses, where a document might look like `{ _id: 0, name: 'Calculus', prerequisite: 'Trigonometry'}` and `{ _id: 0, name: 'Trigonometry', prerequisite: 'Algebra' }`
    - aggregate.graphLookup({ from: 'courses', startWith: '$prerequisite', connectFromField: 'prerequisite', connectToField: 'name', as: 'prerequisites', maxDepth: 3 }) // this will recursively search the 'courses' collection up to 3 prerequisites

    Aggregate.prototype.group()

    Parameters
    • arg -«Object» $group operator contents
    Returns:
    • «Aggregate»

    Appends a new custom $group operator to this aggregate pipeline.

    - -

    Examples:

    - -
    aggregate.group({ _id: "$department" });

    Aggregate.prototype.hint()

    Parameters
    • value -«Object|String» a hint object or the index name

    Sets the hint option for the aggregation query (ignored for < 3.6.0)

    - -

    Example:

    - -
    Model.aggregate(..).hint({ qty: 1, category: 1 }).exec(callback)

    Aggregate.prototype.limit()

    Parameters
    • num -«Number» maximum number of records to pass to the next stage
    Returns:
    • «Aggregate»

    Appends a new $limit operator to this aggregate pipeline.

    - -

    Examples:

    - -
    aggregate.limit(10);

    Aggregate.prototype.lookup()

    Parameters
    • options -«Object» to $lookup as described in the above link
    Returns:
    • «Aggregate»

    Appends new custom $lookup operator(s) to this aggregate pipeline.

    - -

    Examples:

    - -
    aggregate.lookup({ from: 'users', localField: 'userId', foreignField: '_id', as: 'users' });

    Aggregate.prototype.match()

    Parameters
    • arg -«Object» $match operator contents
    Returns:
    • «Aggregate»

    Appends a new custom $match operator to this aggregate pipeline.

    - -

    Examples:

    - -
    aggregate.match({ department: { $in: [ "sales", "engineering" ] } });

    Aggregate.prototype.model()

    Parameters
    • [model] -«Model» the model to which the aggregate is to be bound
    Returns:
    • «Aggregate,Model» if model is passed, will return this, otherwise will return the model

    Get/set the model that this aggregation will execute on.

    - -

    Example:

    - -
    const aggregate = MyModel.aggregate([{ $match: { answer: 42 } }]);
    -aggregate.model() === MyModel; // true
    -
    -// Change the model. There's rarely any reason to do this.
    -aggregate.model(SomeOtherModel);
    -aggregate.model() === SomeOtherModel; // true

    Aggregate.prototype.near()

    Parameters
    • arg -«Object»
    Returns:
    • «Aggregate»

    Appends a new $geoNear operator to this aggregate pipeline.

    - -

    NOTE:

    - -

    MUST be used as the first operator in the pipeline.

    - -

    Examples:

    - -
    aggregate.near({
    -  near: [40.724, -73.997],
    -  distanceField: "dist.calculated", // required
    -  maxDistance: 0.008,
    -  query: { type: "public" },
    -  includeLocs: "dist.location",
    -  uniqueDocs: true,
    -  num: 5
    -});

    Aggregate.prototype.option()

    Parameters
    Returns:
    • «Aggregate» this

    Lets you set arbitrary options, for middleware or plugins.

    - -

    Example:

    - -
    var agg = Model.aggregate(..).option({ allowDiskUse: true }); // Set the `allowDiskUse` option
    -agg.options; // `{ allowDiskUse: true }`

    Aggregate.prototype.options

    Type:
    • «property»

    Contains options passed down to the aggregate command.

    - -

    Supported options are

    - -

    Aggregate.prototype.pipeline()

    Returns:
    • «Array»

    Returns the current pipeline

    - -

    Example:

    - -
    MyModel.aggregate().match({ test: 1 }).pipeline(); // [{ $match: { test: 1 } }]

    Aggregate.prototype.project()

    Parameters
    • arg -«Object|String» field specification
    Returns:
    • «Aggregate»

    Appends a new $project operator to this aggregate pipeline.

    - -

    Mongoose query selection syntax is also supported.

    - -

    Examples:

    - -
    // include a, include b, exclude _id
    -aggregate.project("a b -_id");
    -
    -// or you may use object notation, useful when
    -// you have keys already prefixed with a "-"
    -aggregate.project({a: 1, b: 1, _id: 0});
    -
    -// reshaping documents
    -aggregate.project({
    -    newField: '$b.nested'
    -  , plusTen: { $add: ['$val', 10]}
    -  , sub: {
    -       name: '$a'
    -    }
    -})
    -
    -// etc
    -aggregate.project({ salary_k: { $divide: [ "$salary", 1000 ] } });

    Aggregate.prototype.read()

    Parameters
    • pref -«String» one of the listed preference options or their aliases
    • [tags] -«Array» optional tags for this query
    Returns:
    • «Aggregate» this

    Sets the readPreference option for the aggregation query.

    - -

    Example:

    - -
    Model.aggregate(..).read('primaryPreferred').exec(callback)

    Aggregate.prototype.readConcern()

    Parameters
    • level -«String» one of the listed read concern level or their aliases
    Returns:
    • «Aggregate» this

    Sets the readConcern level for the aggregation query.

    - -

    Example:

    - -
    Model.aggregate(..).readConcern('majority').exec(callback)

    Aggregate.prototype.redact()

    Parameters
    • expression -«Object» redact options or conditional expression
    • [thenExpr] -«String|Object» true case for the condition
    • [elseExpr] -«String|Object» false case for the condition
    Returns:
    • «Aggregate» this

    Appends a new $redact operator to this aggregate pipeline.

    - -

    If 3 arguments are supplied, Mongoose will wrap them with if-then-else of $cond operator respectively If thenExpr or elseExpr is string, make sure it starts with $$, like $$DESCEND, $$PRUNE or $$KEEP.

    - -

    Example:

    - -
    Model.aggregate(...)
    - .redact({
    -   $cond: {
    -     if: { $eq: [ '$level', 5 ] },
    -     then: '$$PRUNE',
    -     else: '$$DESCEND'
    -   }
    - })
    - .exec();
    -
    -// $redact often comes with $cond operator, you can also use the following syntax provided by mongoose
    -Model.aggregate(...)
    - .redact({ $eq: [ '$level', 5 ] }, '$$PRUNE', '$$DESCEND')
    - .exec();

    Aggregate.prototype.replaceRoot()

    Parameters
    • the -«String|Object» field or document which will become the new root document
    Returns:
    • «Aggregate»

    Appends a new $replaceRoot operator to this aggregate pipeline.

    - -

    Note that the $replaceRoot operator requires field strings to start with '$'. If you are passing in a string Mongoose will prepend '$' if the specified field doesn't start '$'. If you are passing in an object the strings in your expression will not be altered.

    - -

    Examples:

    - -
    aggregate.replaceRoot("user");
    -
    -aggregate.replaceRoot({ x: { $concat: ['$this', '$that'] } });

    Aggregate.prototype.sample()

    Parameters
    • size -«Number» number of random documents to pick
    Returns:
    • «Aggregate»

    Appends new custom $sample operator(s) to this aggregate pipeline.

    - -

    Examples:

    - -
    aggregate.sample(3); // Add a pipeline that picks 3 random documents

    Aggregate.prototype.session()

    Parameters
    • session -«ClientSession»

    Sets the session for this aggregation. Useful for transactions.

    - -

    Example:

    - -
    const session = await Model.startSession();
    -await Model.aggregate(..).session(session);

    Aggregate.prototype.skip()

    Parameters
    • num -«Number» number of records to skip before next stage
    Returns:
    • «Aggregate»

    Appends a new $skip operator to this aggregate pipeline.

    - -

    Examples:

    - -
    aggregate.skip(10);

    Aggregate.prototype.sort()

    Parameters
    • arg -«Object|String»
    Returns:
    • «Aggregate» this

    Appends a new $sort operator to this aggregate pipeline.

    - -

    If an object is passed, values allowed are asc, desc, ascending, descending, 1, and -1.

    - -

    If a string is passed, it must be a space delimited list of path names. The sort order of each path is ascending unless the path name is prefixed with - which will be treated as descending.

    - -

    Examples:

    - -
    // these are equivalent
    -aggregate.sort({ field: 'asc', test: -1 });
    -aggregate.sort('field -test');

    Aggregate.prototype.sortByCount()

    Parameters
    • arg -«Object|String»
    Returns:
    • «Aggregate» this

    Appends a new $sortByCount operator to this aggregate pipeline. Accepts either a string field name or a pipeline object.

    - -

    Note that the $sortByCount operator requires the new root to start with '$'. Mongoose will prepend '$' if the specified field name doesn't start with '$'.

    - -

    Examples:

    - -
    aggregate.sortByCount('users');
    -aggregate.sortByCount({ $mergeObjects: [ "$employee", "$business" ] })

    Aggregate.prototype.then()

    Parameters
    • [resolve] -«Function» successCallback
    • [reject] -«Function» errorCallback
    Returns:
    • «Promise»

    Provides promise for aggregate.

    - -

    Example:

    - -
    Model.aggregate(..).then(successCallback, errorCallback);

    Aggregate.prototype.unwind()

    Parameters
    • fields -«String» the field(s) to unwind
    Returns:
    • «Aggregate»

    Appends new custom $unwind operator(s) to this aggregate pipeline.

    - -

    Note that the $unwind operator requires the path name to start with '$'. Mongoose will prepend '$' if the specified field doesn't start '$'.

    - -

    Examples:

    - -
    aggregate.unwind("tags");
    -aggregate.unwind("a", "b", "c");
    \ No newline at end of file diff --git a/docs/api/aggregationcursor.html b/docs/api/aggregationcursor.html deleted file mode 100644 index 4f39cdc6beb..00000000000 --- a/docs/api/aggregationcursor.html +++ /dev/null @@ -1,105 +0,0 @@ -Mongoose v5.6.0:

    AggregationCursor


    AggregationCursor()

    Parameters
    • agg -«Aggregate»
    • options -«Object»

    An AggregationCursor is a concurrency primitive for processing aggregation results one document at a time. It is analogous to QueryCursor.

    - -

    An AggregationCursor fulfills the Node.js streams3 API, in addition to several other mechanisms for loading documents from MongoDB one at a time.

    - -

    Creating an AggregationCursor executes the model's pre aggregate hooks, but not the model's post aggregate hooks.

    - -

    Unless you're an advanced user, do not instantiate this class directly. Use Aggregate#cursor() instead.


    AggregationCursor.prototype.addCursorFlag()

    Parameters
    • flag -«String»
    • value -«Boolean»
    Returns:
    • «AggregationCursor» this

    Adds a cursor flag. Useful for setting the noCursorTimeout and tailable flags.


    AggregationCursor.prototype.close()

    Parameters
    • callback -«Function»
    Returns:
    • «Promise»

    Marks this cursor as closed. Will stop streaming and subsequent calls to next() will error.


    AggregationCursor.prototype.eachAsync()

    Parameters
    • fn -«Function»
    • [options] -«Object»
      • [options.parallel] -«Number» the number of promises to execute in parallel. Defaults to 1.
    • [callback] -«Function» executed when all docs have been processed
    Returns:
    • «Promise»

    Execute fn for every document in the cursor. If fn returns a promise, will wait for the promise to resolve before iterating on to the next one. Returns a promise that resolves when done.


    AggregationCursor.prototype.map()

    Parameters
    • fn -«Function»
    Returns:
    • «AggregationCursor»

    Registers a transform function which subsequently maps documents retrieved via the streams interface or .next()

    - -

    Example

    - -
    // Map documents returned by `data` events
    -Thing.
    -  find({ name: /^hello/ }).
    -  cursor().
    -  map(function (doc) {
    -   doc.foo = "bar";
    -   return doc;
    -  })
    -  on('data', function(doc) { console.log(doc.foo); });
    -
    -// Or map documents returned by `.next()`
    -var cursor = Thing.find({ name: /^hello/ }).
    -  cursor().
    -  map(function (doc) {
    -    doc.foo = "bar";
    -    return doc;
    -  });
    -cursor.next(function(error, doc) {
    -  console.log(doc.foo);
    -});

    AggregationCursor.prototype.next()

    Parameters
    • callback -«Function»
    Returns:
    • «Promise»

    Get the next document from this cursor. Will return null when there are no documents left.

    \ No newline at end of file diff --git a/docs/api/array.html b/docs/api/array.html deleted file mode 100644 index fef92ccc8f8..00000000000 --- a/docs/api/array.html +++ /dev/null @@ -1,65 +0,0 @@ -Mongoose v5.6.0:
    \ No newline at end of file diff --git a/docs/api/connection.html b/docs/api/connection.html deleted file mode 100644 index 1dfad522b6f..00000000000 --- a/docs/api/connection.html +++ /dev/null @@ -1,219 +0,0 @@ -Mongoose v5.6.0:

    Connection


    Connection()

    Parameters
    • base -«Mongoose» a mongoose instance

    Connection constructor

    - -

    For practical reasons, a Connection equals a Db.


    Connection.prototype.close()

    Parameters
    • [force] -«Boolean» optional
    • [callback] -«Function» optional
    Returns:
    • «Connection» self

    Closes the connection


    Connection.prototype.collection()

    Parameters
    • name -«String» of the collection
    • [options] -«Object» optional collection options
    Returns:
    • «Collection» collection instance

    Retrieves a collection, creating it if not cached.

    - -

    Not typically needed by applications. Just talk to your collection through your model.


    Connection.prototype.collections

    Type:
    • «property»

    A hash of the collections associated with this connection


    Connection.prototype.config

    Type:
    • «property»

    A hash of the global options that are associated with this connection


    Connection.prototype.createCollection()

    Parameters
    • collection -«string» The collection to create
    • [options] -«Object» see MongoDB driver docs
    • [callback] -«Function»
    Returns:
    • «Promise»

    Helper for createCollection(). Will explicitly create the given collection with specified options. Used to create capped collections and views from mongoose.

    - -

    Options are passed down without modification to the MongoDB driver's createCollection() function


    Connection.prototype.db

    Type:
    • «property»

    The mongodb.Db instance, set when the connection is opened


    Connection.prototype.deleteModel()

    Parameters
    • name -«String|RegExp» if string, the name of the model to remove. If regexp, removes all models whose name matches the regexp.
    Returns:
    • «Connection» this

    Removes the model named name from this connection, if it exists. You can use this function to clean up any models you created in your tests to prevent OverwriteModelErrors.

    - -

    Example:

    - -
    conn.model('User', new Schema({ name: String }));
    -console.log(conn.model('User')); // Model object
    -conn.deleteModel('User');
    -console.log(conn.model('User')); // undefined
    -
    -// Usually useful in a Mocha `afterEach()` hook
    -afterEach(function() {
    -  conn.deleteModel(/.+/); // Delete every model
    -});

    Connection.prototype.dropCollection()

    Parameters
    • collection -«string» The collection to delete
    • [callback] -«Function»
    Returns:
    • «Promise»

    Helper for dropCollection(). Will delete the given collection, including all documents and indexes.


    Connection.prototype.dropDatabase()

    Parameters
    • [callback] -«Function»
    Returns:
    • «Promise»

    Helper for dropDatabase(). Deletes the given database, including all collections, documents, and indexes.

    - -

    Example:

    - -
    const conn = mongoose.createConnection('mongodb://localhost:27017/mydb');
    -// Deletes the entire 'mydb' database
    -await conn.dropDatabase();

    Connection.prototype.get()

    Parameters
    • key -«String»

    Gets the value of the option key. Equivalent to conn.options[key]

    - -

    Example:

    - -
    conn.get('test'); // returns the 'test' value

    Connection.prototype.host

    Type:
    • «property»

    The host name portion of the URI. If multiple hosts, such as a replica set, this will contain the first host name in the URI

    - -

    Example

    - -
    mongoose.createConnection('mongodb://localhost:27017/mydb').host; // "localhost"

    Connection.prototype.model()

    Parameters
    • name -«String|Function» the model name or class extending Model
    • [schema] -«Schema» a schema. necessary when defining a model
    • [collection] -«String» name of mongodb collection (optional) if not given it will be induced from model name
    Returns:
    • «Model» The compiled model

    Defines or retrieves a model.

    - -
    var mongoose = require('mongoose');
    -var db = mongoose.createConnection(..);
    -db.model('Venue', new Schema(..));
    -var Ticket = db.model('Ticket', new Schema(..));
    -var Venue = db.model('Venue');
    - -

    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:

    - -
    var schema = new Schema({ name: String }, { collection: 'actor' });
    -
    -// or
    -
    -schema.set('collection', 'actor');
    -
    -// or
    -
    -var collectionName = 'actor'
    -var M = conn.model('Actor', schema, collectionName)

    Connection.prototype.modelNames()

    Returns:
    • «Array»

    Returns an array of model names created on this connection.


    Connection.prototype.name

    Type:
    • «property»

    The name of the database this connection points to.

    - -

    Example

    - -
    mongoose.createConnection('mongodb://localhost:27017/mydb').name; // "mydb"

    Connection.prototype.openUri()

    Parameters
    • uri -«String» The URI to connect with.
    • [options] -«Object» Passed on to http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html#connect
    • [callback] -«Function»

    Opens the connection with a URI using MongoClient.connect().


    Connection.prototype.pass

    Type:
    • «property»

    The password specified in the URI

    - -

    Example

    - -
    mongoose.createConnection('mongodb://val:psw@localhost:27017/mydb').pass; // "psw"

    Connection.prototype.plugin()

    Parameters
    • fn -«Function» plugin callback
    • [opts] -«Object» optional options
    Returns:
    • «Connection» this

    Declares a plugin executed on all schemas you pass to conn.model()

    - -

    Equivalent to calling .plugin(fn) on each schema you create.

    - -

    Example:

    - -
    const db = mongoose.createConnection('mongodb://localhost:27017/mydb');
    -db.plugin(() => console.log('Applied'));
    -db.plugins.length; // 1
    -
    -db.model('Test', new Schema({})); // Prints "Applied"

    Connection.prototype.plugins

    Type:
    • «property»

    The plugins that will be applied to all models created on this connection.

    - -

    Example:

    - -
    const db = mongoose.createConnection('mongodb://localhost:27017/mydb');
    -db.plugin(() => console.log('Applied'));
    -db.plugins.length; // 1
    -
    -db.model('Test', new Schema({})); // Prints "Applied"

    Connection.prototype.port

    Type:
    • «property»

    The port portion of the URI. If multiple hosts, such as a replica set, this will contain the port from the first host name in the URI.

    - -

    Example

    - -
    mongoose.createConnection('mongodb://localhost:27017/mydb').port; // 27017

    Connection.prototype.readyState

    Type:
    • «property»

    Connection ready state

    - -
      -
    • 0 = disconnected
    • -
    • 1 = connected
    • -
    • 2 = connecting
    • -
    • 3 = disconnecting
    • -
    - -

    Each state change emits its associated event name.

    - -

    Example

    - -
    conn.on('connected', callback);
    -conn.on('disconnected', callback);

    Connection.prototype.set()

    Parameters
    • key -«String»
    • val -«Any»

    Sets the value of the option key. Equivalent to conn.options[key] = val

    - -

    Supported options include

    - - - -

    Example:

    - -
    conn.set('test', 'foo');
    -conn.get('test'); // 'foo'
    -conn.options.test; // 'foo'

    Connection.prototype.startSession()

    Parameters
    • [options] -«Object» see the mongodb driver options
      • [options.causalConsistency=true] -«Boolean» set to false to disable causal consistency
    • [callback] -«Function»
    Returns:
    • «Promise<ClientSession>» promise that resolves to a MongoDB driver ClientSession

    Requires MongoDB >= 3.6.0. Starts a MongoDB session for benefits like causal consistency, retryable writes, and transactions.

    - -

    Example:

    - -
    const session = await conn.startSession();
    -let doc = await Person.findOne({ name: 'Ned Stark' }, null, { session });
    -await doc.remove();
    -// `doc` will always be null, even if reading from a replica set
    -// secondary. Without causal consistency, it is possible to
    -// get a doc back from the below query if the query reads from a
    -// secondary that is experiencing replication lag.
    -doc = await Person.findOne({ name: 'Ned Stark' }, null, { session, readPreference: 'secondary' });

    Connection.prototype.useDb()

    Parameters
    • name -«String» The database name
    Returns:
    • «Connection» New Connection Object

    Switches to a different database using the same connection pool.

    - -

    Returns a new connection object, with the new db.


    Connection.prototype.user

    Type:
    • «property»

    The username specified in the URI

    - -

    Example

    - -
    mongoose.createConnection('mongodb://val:psw@localhost:27017/mydb').user; // "val"
    \ No newline at end of file diff --git a/docs/api/document.html b/docs/api/document.html deleted file mode 100644 index 41f45bd8b4e..00000000000 --- a/docs/api/document.html +++ /dev/null @@ -1,558 +0,0 @@ -Mongoose v5.6.0:

    Document


    Document.prototype.$ignore()

    Parameters
    • path -«String» the path to ignore

    Don't run validation on this path or persist changes to this path.

    - -

    Example:

    - -
    doc.foo = null;
    -doc.$ignore('foo');
    -doc.save(); // changes to foo will not be persisted and validators won't be run

    Document.prototype.$isDefault()

    Parameters
    • [path] -«String»
    Returns:
    • «Boolean»

    Checks if a path is set to its default.

    - -

    Example

    - -
    MyModel = mongoose.model('test', { name: { type: String, default: 'Val '} });
    -var m = new MyModel();
    -m.$isDefault('name'); // true

    Document.prototype.$isDeleted()

    Parameters
    • [val] -«Boolean» optional, overrides whether mongoose thinks the doc is deleted
    Returns:
    • «Boolean» whether mongoose thinks this doc is deleted.

    Getter/setter, determines whether the document was removed or not.

    - -

    Example:

    - -
    product.remove(function (err, product) {
    -  product.$isDeleted(); // true
    -  product.remove(); // no-op, doesn't send anything to the db
    -
    -  product.$isDeleted(false);
    -  product.$isDeleted(); // false
    -  product.remove(); // will execute a remove against the db
    -})

    Document.prototype.$isEmpty()

    Returns:
    • «Boolean»

    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.

    - -

    Example:

    - -
    const schema = new Schema({ nested: { foo: String } });
    -const Model = mongoose.model('Test', schema);
    -const doc = new Model({});
    -doc.$isEmpty('nested'); // true
    -doc.nested.$isEmpty(); // true
    -
    -doc.nested.foo = 'bar';
    -doc.$isEmpty('nested'); // false
    -doc.nested.$isEmpty(); // false

    Document.prototype.$locals

    Type:
    • «property»

    Empty object that you can use for storing properties on the document. This is handy for passing data to middleware without conflicting with Mongoose internals.

    - -

    Example:

    - -
    schema.pre('save', function() {
    -  // Mongoose will set `isNew` to `false` if `save()` succeeds
    -  this.$locals.wasNew = this.isNew;
    -});
    -
    -schema.post('save', function() {
    -  // Prints true if `isNew` was set before `save()`
    -  console.log(this.$locals.wasNew);
    -});

    Document.prototype.$markValid()

    Parameters
    • path -«String» the field to mark as valid

    Marks a path as valid, removing existing validation errors.


    Document.prototype.$session()

    Parameters
    • [session] -«ClientSession» overwrite the current session
    Returns:
    • «ClientSession»

    Getter/setter around the session associated with this document. Used to automatically set session if you save() a doc that you got from a query with an associated session.

    - -

    Example:

    - -
    const session = MyModel.startSession();
    -const doc = await MyModel.findOne().session(session);
    -doc.$session() === session; // true
    -doc.$session(null);
    -doc.$session() === null; // true
    - -

    If this is a top-level document, setting the session propagates to all child docs.


    Document.prototype.$set()

    Parameters
    • path -«String|Object» path or object of key/vals to set
    • val -«Any» the value to set
    • [type] -«Schema|String|Number|Buffer|*» optionally specify a type for "on-the-fly" attributes
    • [options] -«Object» optionally specify options that modify the behavior of the set

    Alias for set(), used internally to avoid conflicts


    Document.prototype.depopulate()

    Parameters
    • path -«String»
    Returns:
    • «Document» this

    Takes a populated field and returns it to its unpopulated state.

    - -

    Example:

    - -
    Model.findOne().populate('author').exec(function (err, doc) {
    -  console.log(doc.author.name); // Dr.Seuss
    -  console.log(doc.depopulate('author'));
    -  console.log(doc.author); // '5144cf8050f071d979c118a7'
    -})
    - -

    If the path was not populated, this is a no-op.


    Document.prototype.directModifiedPaths()

    Returns:
    • «Array»

    Returns the list of paths that have been directly modified. A direct modified path is a path that you explicitly set, whether via doc.foo = 'bar', Object.assign(doc, { foo: 'bar' }), or doc.set('foo', 'bar').

    - -

    A path a may be in modifiedPaths() but not in directModifiedPaths() because a child of a was directly modified.

    - -

    Example

    - -
    const schema = new Schema({ foo: String, nested: { bar: String } });
    -const Model = mongoose.model('Test', schema);
    -await Model.create({ foo: 'original', nested: { bar: 'original' } });
    -
    -const doc = await Model.findOne();
    -doc.nested.bar = 'modified';
    -doc.directModifiedPaths(); // ['nested.bar']
    -doc.modifiedPaths(); // ['nested', 'nested.bar']

    Document.prototype.equals()

    Parameters
    • doc -«Document» a document to compare
    Returns:
    • «Boolean»

    Returns true if the Document stores the same data as doc.

    - -

    Documents are considered equal when they have matching _ids, unless neither document has an _id, in which case this function falls back to using deepEqual().


    Document.prototype.errors

    Type:
    • «property»

    Hash containing current validation errors.


    Document.prototype.execPopulate()

    Parameters
    • [callback] -«Function» optional callback. If specified, a promise will not be returned
    Returns:
    • «Promise» promise that resolves to the document when population is done

    Explicitly executes population and returns a promise. Useful for ES2015 integration.

    - -

    Example:

    - -
    var promise = doc.
    -  populate('company').
    -  populate({
    -    path: 'notes',
    -    match: /airline/,
    -    select: 'text',
    -    model: 'modelName'
    -    options: opts
    -  }).
    -  execPopulate();
    -
    -// summary
    -doc.execPopulate().then(resolve, reject);

    Document.prototype.get()

    Parameters
    • path -«String»
    • [type] -«Schema|String|Number|Buffer|*» optionally specify a type for on-the-fly attributes
    • [options] -«Object»
      • [options.virtuals=false] -«Boolean» Apply virtuals before getting this path
      • [options.getters=true] -«Boolean» If false, skip applying getters and just get the raw value

    Returns the value of a path.

    - -

    Example

    - -
    // path
    -doc.get('age') // 47
    -
    -// dynamic casting to a string
    -doc.get('age', String) // "47"

    Document.prototype.id

    Type:
    • «property»

    The string version of this documents _id.

    - -

    Note:

    - -

    This getter exists on all documents by default. The getter can be disabled by setting the id option of its Schema to false at construction time.

    - -
    new Schema({ name: String }, { id: false });

    Document.prototype.init()

    Parameters
    • doc -«Object» document returned by mongo

    Initializes the document without setters or marking anything modified.

    - -

    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. Note that init hooks are synchronous.


    Document.prototype.inspect()

    Helper for console.log


    Document.prototype.invalidate()

    Parameters
    • path -«String» the field to invalidate
    • errorMsg -«String|Error» the error which states the reason path was invalid
    • value -«Object|String|Number|any» optional invalid value
    • [kind] -«String» optional kind property for the error
    Returns:
    • «ValidationError» the current ValidationError, with all currently invalidated paths

    Marks a path as invalid, causing validation to fail.

    - -

    The errorMsg argument will become the message of the ValidationError.

    - -

    The value argument (if passed) will be available through the ValidationError.value property.

    - -
    doc.invalidate('size', 'must be less than 20', 14);
    -
    -doc.validate(function (err) {
    -  console.log(err)
    -  // prints
    -  { message: 'Validation failed',
    -    name: 'ValidationError',
    -    errors:
    -     { size:
    -        { message: 'must be less than 20',
    -          name: 'ValidatorError',
    -          path: 'size',
    -          type: 'user defined',
    -          value: 14 } } }
    -})

    Document.prototype.isDirectModified()

    Parameters
    • path -«String»
    Returns:
    • «Boolean»

    Returns true if path was directly set and modified, else false.

    - -

    Example

    - -
    doc.set('documents.0.title', 'changed');
    -doc.isDirectModified('documents.0.title') // true
    -doc.isDirectModified('documents') // false

    Document.prototype.isDirectSelected()

    Parameters
    • path -«String»
    Returns:
    • «Boolean»

    Checks if path was explicitly selected. If no projection, always returns true.

    - -

    Example

    - -
    Thing.findOne().select('nested.name').exec(function (err, doc) {
    -   doc.isDirectSelected('nested.name') // true
    -   doc.isDirectSelected('nested.otherName') // false
    -   doc.isDirectSelected('nested')  // false
    -})

    Document.prototype.isInit()

    Parameters
    • path -«String»
    Returns:
    • «Boolean»

    Checks if path was initialized.


    Document.prototype.isModified()

    Parameters
    • [path] -«String» optional
    Returns:
    • «Boolean»

    Returns true if this document was modified, else false.

    - -

    If path is given, checks if a path or any full path containing path as part of its path chain has been modified.

    - -

    Example

    - -
    doc.set('documents.0.title', 'changed');
    -doc.isModified()                      // true
    -doc.isModified('documents')           // true
    -doc.isModified('documents.0.title')   // true
    -doc.isModified('documents otherProp') // true
    -doc.isDirectModified('documents')     // false

    Document.prototype.isNew

    Type:
    • «property»

    Boolean flag specifying if the document is new.


    Document.prototype.isSelected()

    Parameters
    • path -«String»
    Returns:
    • «Boolean»

    Checks if path was selected in the source query which initialized this document.

    - -

    Example

    - -
    Thing.findOne().select('name').exec(function (err, doc) {
    -   doc.isSelected('name') // true
    -   doc.isSelected('age')  // false
    -})

    Document.prototype.markModified()

    Parameters
    • path -«String» the path to mark modified
    • [scope] -«Document» the scope to run validators with

    Marks the path as having pending changes to write to the db.

    - -

    Very helpful when using Mixed types.

    - -

    Example:

    - -
    doc.mixed.type = 'changed';
    -doc.markModified('mixed.type');
    -doc.save() // changes to mixed.type are now persisted

    Document.prototype.modifiedPaths()

    Parameters
    • [options] -«Object»
      • [options.includeChildren=false] -«Boolean» if true, returns children of modified paths as well. For example, if false, the list of modified paths for doc.colors = { primary: 'blue' }; will not contain colors.primary. If true, modifiedPaths() will return an array that contains colors.primary.
    Returns:
    • «Array»

    Returns the list of paths that have been modified.


    Document.prototype.overwrite()

    Parameters
    • obj -«Object» the object to overwrite this document with

    Overwrite all values in this document with the values of obj, except for immutable properties. Behaves similarly to set(), except for it unsets all properties that aren't in obj.


    Document.prototype.populate()

    Parameters
    • [path] -«String|Object» The path to populate or an options object
    • [callback] -«Function» When passed, population is invoked
    Returns:
    • «Document» this

    Populates document references, executing the callback when complete. If you want to use promises instead, use this function with execPopulate()

    - -

    Example:

    - -
    doc
    -.populate('company')
    -.populate({
    -  path: 'notes',
    -  match: /airline/,
    -  select: 'text',
    -  model: 'modelName'
    -  options: opts
    -}, function (err, user) {
    -  assert(doc._id === user._id) // the document itself is passed
    -})
    -
    -// summary
    -doc.populate(path)                   // not executed
    -doc.populate(options);               // not executed
    -doc.populate(path, callback)         // executed
    -doc.populate(options, callback);     // executed
    -doc.populate(callback);              // executed
    -doc.populate(options).execPopulate() // executed, returns promise
    - -

    NOTE:

    - -

    Population does not occur unless a callback is passed or you explicitly call execPopulate(). Passing the same path a second time will overwrite the previous path options. See Model.populate() for explaination of options.


    Document.prototype.populated()

    Parameters
    • path -«String»
    Returns:
    • «Array,ObjectId,Number,Buffer,String,undefined»

    Gets _id(s) used during population of the given path.

    - -

    Example:

    - -
    Model.findOne().populate('author').exec(function (err, doc) {
    -  console.log(doc.author.name)         // Dr.Seuss
    -  console.log(doc.populated('author')) // '5144cf8050f071d979c118a7'
    -})
    - -

    If the path was not populated, undefined is returned.


    Document.prototype.replaceOne()

    Parameters
    • doc -«Object»
    • options -«Object»
    • callback -«Function»
    Returns:
    • «Query»

    Sends a replaceOne command with this document _id as the query selector.

    - -

    Valid options:

    - -

    Document.prototype.save()

    Parameters
    • [options] -«Object» options optional options
      • [options.validateBeforeSave] -«Boolean» set to false to save without validating.
    • [fn] -«Function» optional callback
    Returns:
    • «Promise,undefined» Returns undefined if used with callback or a Promise otherwise.

    Saves this document.

    - -

    Example:

    - -
    product.sold = Date.now();
    -product.save(function (err, product) {
    -  if (err) ..
    -})
    - -

    The callback will receive two parameters

    - -
      -
    1. err if an error occurred
    2. -
    3. product which is the saved product
    4. -
    - -

    As an extra measure of flow control, save will return a Promise.

    - -

    Example:

    - -
    product.save().then(function(product) {
    -   ...
    -});

    Document.prototype.schema

    Type:
    • «property»

    The documents schema.


    Document.prototype.set()

    Parameters
    • path -«String|Object» path or object of key/vals to set
    • val -«Any» the value to set
    • [type] -«Schema|String|Number|Buffer|*» optionally specify a type for "on-the-fly" attributes
    • [options] -«Object» optionally specify options that modify the behavior of the set

    Sets the value of a path, or many paths.

    - -

    Example:

    - -
    // path, value
    -doc.set(path, value)
    -
    -// object
    -doc.set({
    -    path  : value
    -  , path2 : {
    -       path  : value
    -    }
    -})
    -
    -// on-the-fly cast to number
    -doc.set(path, value, Number)
    -
    -// on-the-fly cast to string
    -doc.set(path, value, String)
    -
    -// changing strict mode behavior
    -doc.set(path, value, { strict: false });

    Document.prototype.toJSON()

    Parameters
    • options -«Object»
    Returns:
    • «Object»

    The return value of this method is used in calls to JSON.stringify(doc).

    - -

    This method accepts the same options as Document#toObject. To apply the options to every document of your schema by default, set your schemas toJSON option to the same argument.

    - -
    schema.set('toJSON', { virtuals: true })
    - -

    See schema options for details.


    Document.prototype.toObject()

    Parameters
    • [options] -«Object»
      • [options.getters=false] -«Boolean» if true, apply all getters, including virtuals
      • [options.virtuals=false] -«Boolean» if true, apply virtuals. Use { getters: true, virtuals: false } to just apply getters, not virtuals
      • [options.minimize=true] -«Boolean» if true, omit any empty objects from the output
      • [options.transform=null] -«Function|null» if set, mongoose will call this function to allow you to transform the returned object
      • [options.depopulate=false] -«Boolean» if true, replace any conventionally populated paths with the original id in the output. Has no affect on virtual populated paths.
      • [options.versionKey=true] -«Boolean» if false, exclude the version key (__v by default) from the output
      • [options.flattenMaps=false] -«Boolean» if true, convert Maps to POJOs. Useful if you want to JSON.stringify() the result of toObject().
    Returns:
    • «Object» js object

    Converts this document into a plain javascript object, ready for storage in MongoDB.

    - -

    Buffers are converted to instances of mongodb.Binary for proper storage.

    - -

    Options:

    - -
      -
    • getters apply all getters (path and virtual getters), defaults to false
    • -
    • virtuals apply virtual getters (can override getters option), defaults to false
    • -
    • minimize remove empty objects (defaults to true)
    • -
    • transform a transform function to apply to the resulting document before returning
    • -
    • depopulate depopulate any populated paths, replacing them with their original refs (defaults to false)
    • -
    • versionKey whether to include the version key (defaults to true)
    • -
    - -

    Getters/Virtuals

    - -

    Example of only applying path getters

    - -
    doc.toObject({ getters: true, virtuals: false })
    - -

    Example of only applying virtual getters

    - -
    doc.toObject({ virtuals: true })
    - -

    Example of applying both path and virtual getters

    - -
    doc.toObject({ getters: true })
    - -

    To apply these options to every document of your schema by default, set your schemas toObject option to the same argument.

    - -
    schema.set('toObject', { virtuals: true })
    - -

    Transform

    - -

    We may need to perform a transformation of the resulting object based on some criteria, say to remove some sensitive information or return a custom object. In this case we set the optional transform function.

    - -

    Transform functions receive three arguments

    - -
    function (doc, ret, options) {}
    - -
      -
    • doc The mongoose document which is being converted
    • -
    • ret The plain object representation which has been converted
    • -
    • options The options in use (either schema options or the options passed inline)
    • -
    - -

    Example

    - -
    // specify the transform schema option
    -if (!schema.options.toObject) schema.options.toObject = {};
    -schema.options.toObject.transform = function (doc, ret, options) {
    -  // remove the _id of every document before returning the result
    -  delete ret._id;
    -  return ret;
    -}
    -
    -// without the transformation in the schema
    -doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
    -
    -// with the transformation
    -doc.toObject(); // { name: 'Wreck-it Ralph' }
    - -

    With transformations we can do a lot more than remove properties. We can even return completely new customized objects:

    - -
    if (!schema.options.toObject) schema.options.toObject = {};
    -schema.options.toObject.transform = function (doc, ret, options) {
    -  return { movie: ret.name }
    -}
    -
    -// without the transformation in the schema
    -doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
    -
    -// with the transformation
    -doc.toObject(); // { movie: 'Wreck-it Ralph' }
    - -

    Note: if a transform function returns undefined, the return value will be ignored.

    - -

    Transformations may also be applied inline, overridding any transform set in the options:

    - -
    function xform (doc, ret, options) {
    -  return { inline: ret.name, custom: true }
    -}
    -
    -// pass the transform as an inline option
    -doc.toObject({ transform: xform }); // { inline: 'Wreck-it Ralph', custom: true }
    - -

    If you want to skip transformations, use transform: false:

    - -
    if (!schema.options.toObject) schema.options.toObject = {};
    -schema.options.toObject.hide = '_id';
    -schema.options.toObject.transform = function (doc, ret, options) {
    -  if (options.hide) {
    -    options.hide.split(' ').forEach(function (prop) {
    -      delete ret[prop];
    -    });
    -  }
    -  return ret;
    -}
    -
    -var doc = new Doc({ _id: 'anId', secret: 47, name: 'Wreck-it Ralph' });
    -doc.toObject();                                        // { secret: 47, name: 'Wreck-it Ralph' }
    -doc.toObject({ hide: 'secret _id', transform: false });// { _id: 'anId', secret: 47, name: 'Wreck-it Ralph' }
    -doc.toObject({ hide: 'secret _id', transform: true }); // { name: 'Wreck-it Ralph' }
    - -

    Transforms are applied only to the document and are not applied to sub-documents.

    - -

    Transforms, like all of these options, are also available for toJSON.

    - -

    See schema options for some more details.

    - -

    During save, no custom options are applied to the document before being sent to the database.


    Document.prototype.toString()

    Helper for console.log


    Document.prototype.unmarkModified()

    Parameters
    • path -«String» the path to unmark modified

    Clears the modified state on the specified path.

    - -

    Example:

    - -
    doc.foo = 'bar';
    -doc.unmarkModified('foo');
    -doc.save(); // changes to foo will not be persisted

    Document.prototype.update()

    Parameters
    • doc -«Object»
    • options -«Object»
    • callback -«Function»
    Returns:
    • «Query»

    Sends an update command with this document _id as the query selector.

    - -

    Example:

    - -
    weirdCar.update({$inc: {wheels:1}}, { w: 1 }, callback);
    - -

    Valid options:

    - -

    Document.prototype.updateOne()

    Parameters
    • doc -«Object»
    • options -«Object»
    • callback -«Function»
    Returns:
    • «Query»

    Sends an updateOne command with this document _id as the query selector.

    - -

    Example:

    - -
    weirdCar.updateOne({$inc: {wheels:1}}, { w: 1 }, callback);
    - -

    Valid options:

    - -

    Document.prototype.validate()

    Parameters
    • optional -«Object» options internal options
    • callback -«Function» optional callback called after validation completes, passing an error if one occurred
    Returns:
    • «Promise» Promise

    Executes registered validation rules for this document.

    - -

    Note:

    - -

    This method is called pre save and if a validation rule is violated, save is aborted and the error is returned to your callback.

    - -

    Example:

    - -
    doc.validate(function (err) {
    -  if (err) handleError(err);
    -  else // validation passed
    -});

    Document.prototype.validateSync()

    Parameters
    • pathsToValidate -«Array|string» only validate the given paths
    Returns:
    • «ValidationError,undefined» ValidationError if there are errors during validation, or undefined if there is no error.

    Executes registered validation rules (skipping asynchronous validators) for this document.

    - -

    Note:

    - -

    This method is useful if you need synchronous validation.

    - -

    Example:

    - -
    var err = doc.validateSync();
    -if ( err ){
    -  handleError( err );
    -} else {
    -  // validation passed
    -}
    \ No newline at end of file diff --git a/docs/api/error.html b/docs/api/error.html deleted file mode 100644 index 1a43df5f96d..00000000000 --- a/docs/api/error.html +++ /dev/null @@ -1,92 +0,0 @@ -Mongoose v5.6.0:

    Error


    Error()

    Parameters
    • msg -«String» Error message

    MongooseError constructor. MongooseError is the base class for all Mongoose-specific errors.

    - -

    Example:

    - -
    const Model = mongoose.model('Test', new Schema({ answer: Number }));
    -const doc = new Model({ answer: 'not a number' });
    -const err = doc.validateSync();
    -
    -err instanceof mongoose.Error; // true

    Error.CastError

    Type:
    • «property»

    An instance of this error class will be returned when mongoose failed to cast a value.


    Error.DivergentArrayError

    Type:
    • «property»

    An instance of this error will be returned if you used an array projection and then modified the array in an unsafe way.


    Error.DocumentNotFoundError

    Type:
    • «property»

    An instance of this error class will be returned when save() fails because the underlying document was not found. The constructor takes one parameter, the conditions that mongoose passed to update() when trying to update the document.


    Error.MissingSchemaError

    Type:
    • «property»

    Thrown when you try to access a model that has not been registered yet


    Error.OverwriteModelError

    Type:
    • «property»

    Thrown when a model with the given name was already registered on the connection. See the FAQ about OverwriteModelError.


    Error.ParallelSaveError

    Type:
    • «property»

    An instance of this error class will be returned when you call save() multiple times on the same document in parallel. See the FAQ for more information.


    Error.ValidationError

    Type:
    • «property»

    An instance of this error class will be returned when validation failed. The errors property contains an object whose keys are the paths that failed and whose values are instances of CastError or ValidationError.


    Error.ValidatorError

    Type:
    • «property»

    A ValidationError has a hash of errors that contain individual ValidatorError instances


    Error.VersionError

    Type:
    • «property»

    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 for more information.


    Error.messages

    Type:
    • «property»

    The default built-in validator error messages.


    Error.prototype.name

    Type:
    • «String»

    The name of the error. The name uniquely identifies this Mongoose error. The possible values are:

    - -
      -
    • MongooseError: general Mongoose error
    • -
    • CastError: Mongoose could not convert a value to the type defined in the schema path. May be in a ValidationError class' errors property.
    • -
    • DisconnectedError: This connection timed out in trying to reconnect to MongoDB and will not successfully reconnect to MongoDB unless you explicitly reconnect.
    • -
    • DivergentArrayError: You attempted to save() an array that was modified after you loaded it with a $elemMatch or similar projection
    • -
    • MissingSchemaError: You tried to access a model with mongoose.model() that was not defined
    • -
    • DocumentNotFoundError: The document you tried to save() was not found
    • -
    • ValidatorError: error from an individual schema path's validator
    • -
    • ValidationError: error returned from validate() or 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.
    • -
    • 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() to re-define a model that was already defined.
    • -
    • ParallelSaveError: Thrown when you call 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 is set to throw.
    • -
    • VersionError: Thrown when the document is out of sync
    • -
    \ No newline at end of file diff --git a/docs/api/model.html b/docs/api/model.html deleted file mode 100644 index 6919734362e..00000000000 --- a/docs/api/model.html +++ /dev/null @@ -1,1329 +0,0 @@ -Mongoose v5.6.0:

    Model


    Model()

    Parameters
    • doc -«Object» values for initial set
    • optional -«[fields]» 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.

    A Model is a class that's your primary tool for interacting with MongoDB. An instance of a Model is called a 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() and connection.model() functions create subclasses of mongoose.Model as shown below.

    - -

    Example:

    - -
    // `UserModel` is a "Model", a subclass of `mongoose.Model`.
    -const UserModel = mongoose.model('User', new Schema({ name: String }));
    -
    -// You can use a Model to create new documents using `new`:
    -const userDoc = new UserModel({ name: 'Foo' });
    -await userDoc.save();
    -
    -// You also use a model to create queries:
    -const userFromDb = await UserModel.findOne({ name: 'Foo' });

    Model.aggregate()

    Parameters
    • [pipeline] -«Array» aggregation pipeline as an array of objects
    • [callback] -«Function»
    Returns:
    • «Aggregate»

    Performs aggregations on the models collection.

    - -

    If a callback is passed, the aggregate is executed and a Promise is returned. If a callback is not passed, the aggregate itself is returned.

    - -

    This function triggers the following middleware.

    - -
      -
    • aggregate()
    • -
    - -

    Example:

    - -
    // Find the max balance of all accounts
    -Users.aggregate([
    -  { $group: { _id: null, maxBalance: { $max: '$balance' }}},
    -  { $project: { _id: 0, maxBalance: 1 }}
    -]).
    -then(function (res) {
    -  console.log(res); // [ { maxBalance: 98000 } ]
    -});
    -
    -// Or use the aggregation pipeline builder.
    -Users.aggregate().
    -  group({ _id: null, maxBalance: { $max: '$balance' } }).
    -  project('-id maxBalance').
    -  exec(function (err, res) {
    -    if (err) return handleError(err);
    -    console.log(res); // [ { maxBalance: 98 } ]
    -  });
    - -

    NOTE:

    - -
      -
    • Arguments are not cast to the model's schema because $project operators allow redefining the "shape" of the documents at any stage of the pipeline, which may leave documents in an incompatible format.
    • -
    • The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned).
    • -
    • Requires MongoDB >= 2.1
    • -

    Model.bulkWrite()

    Parameters
    • ops -«Array»
      • [ops.insertOne.document] -«Object» The document to insert
      • [opts.updateOne.filter] -«Object» Update the first document that matches this filter
      • [opts.updateOne.upsert=false] -«Boolean» If true, insert a doc if none match
      • [opts.updateOne.arrayFilters] -«Array» The array filters used in update
      • [opts.updateMany.filter] -«Object» Update all the documents that match this filter
      • [opts.updateMany.upsert=false] -«Boolean» If true, insert a doc if no documents match filter
      • [opts.updateMany.arrayFilters] -«Array» The array filters used in update
      • [opts.deleteOne.filter] -«Object» Delete the first document that matches this filter
      • [opts.deleteMany.filter] -«Object» Delete all documents that match this filter
      • [opts.replaceOne.filter] -«Object» Replace the first document that matches this filter
      • [opts.replaceOne.replacement] -«Object» The replacement document
      • [opts.replaceOne.upsert=false] -«Boolean» If true, insert a doc if no documents match filter
    • [options] -«Object»
      • [options.ordered=true] -«Boolean» 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.
      • [options.session=null] -«ClientSession» The session associated with this bulk write. See transactions docs.
    • [callback] -«Function» callback function(error, bulkWriteOpResult) {}
    Returns:

    Sends multiple insertOne, updateOne, updateMany, replaceOne, deleteOne, and/or deleteMany operations to the MongoDB server in one command. This is faster than sending multiple independent operations (like) if you use create()) because with bulkWrite() there is only one round trip to MongoDB.

    - -

    Mongoose will perform casting on all operations you provide.

    - -

    This function does not trigger any middleware, not save() nor update(). If you need to trigger save() middleware for every document use create() instead.

    - -

    Example:

    - -
    Character.bulkWrite([
    -  {
    -    insertOne: {
    -      document: {
    -        name: 'Eddard Stark',
    -        title: 'Warden of the North'
    -      }
    -    }
    -  },
    -  {
    -    updateOne: {
    -      filter: { name: 'Eddard Stark' },
    -      // If you were using the MongoDB driver directly, you'd need to do
    -      // `update: { $set: { title: ... } }` but mongoose adds $set for
    -      // you.
    -      update: { title: 'Hand of the King' }
    -    }
    -  },
    -  {
    -    deleteOne: {
    -      {
    -        filter: { name: 'Eddard Stark' }
    -      }
    -    }
    -  }
    -]).then(res => {
    - // Prints "1 1 1"
    - console.log(res.insertedCount, res.modifiedCount, res.deletedCount);
    -});
    - -

    The supported operations are:

    - -
      -
    • insertOne
    • -
    • updateOne
    • -
    • updateMany
    • -
    • deleteOne
    • -
    • deleteMany
    • -
    • replaceOne
    • -

    Model.count()

    Parameters
    • filter -«Object»
    • [callback] -«Function»
    Returns:
    • «Query»

    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 instead. Otherwise, use the countDocuments() function instead.

    - -

    Example:

    - -
    Adventure.count({ type: 'jungle' }, function (err, count) {
    -  if (err) ..
    -  console.log('there are %d jungle adventures', count);
    -});

    Model.countDocuments()

    Parameters
    • filter -«Object»
    • [callback] -«Function»
    Returns:
    • «Query»

    Counts number of documents matching filter in a database collection.

    - -

    Example:

    - -
    Adventure.countDocuments({ type: 'jungle' }, function (err, count) {
    -  console.log('there are %d jungle adventures', count);
    -});
    - -

    If you want to count all documents in a large collection, use the estimatedDocumentCount() function instead. If you call countDocuments({}), MongoDB will always execute a full collection scan and not use any indexes.

    - -

    The countDocuments() function is similar to count(), but there are a few operators that countDocuments() does not support. Below are the operators that count() supports but countDocuments() does not, and the suggested replacement:

    - -

    Model.create()

    Parameters
    • docs -«Array|Object» Documents to insert, as a spread or array
    • [options] -«Object» Options passed down to save(). To specify options, docs must be an array, not a spread.
    • [callback] -«Function» callback
    Returns:
    • «Promise»

    Shortcut for saving one or more documents to the database. MyModel.create(docs) does new MyModel(doc).save() for every doc in docs.

    - -

    This function triggers the following middleware.

    - -
      -
    • save()
    • -
    - -

    Example:

    - -
    // pass a spread of docs and a callback
    -Candy.create({ type: 'jelly bean' }, { type: 'snickers' }, function (err, jellybean, snickers) {
    -  if (err) // ...
    -});
    -
    -// pass an array of docs
    -var array = [{ type: 'jelly bean' }, { type: 'snickers' }];
    -Candy.create(array, function (err, candies) {
    -  if (err) // ...
    -
    -  var jellybean = candies[0];
    -  var snickers = candies[1];
    -  // ...
    -});
    -
    -// callback is optional; use the returned promise if you like:
    -var promise = Candy.create({ type: 'jawbreaker' });
    -promise.then(function (jawbreaker) {
    -  // ...
    -})

    Model.createCollection()

    Parameters

    Create the collection for this model. By default, if no indexes are specified, mongoose will not create the collection for the model until any documents are created. Use this method to create the collection explicitly.

    - -

    Note 1: You may need to call this before starting a transaction See https://docs.mongodb.com/manual/core/transactions/#transactions-and-operations

    - -

    Note 2: You don't have to call this if your schema contains index or unique field. In that case, just use Model.init()

    - -

    Example:

    - -
    var userSchema = new Schema({ name: String })
    -var User = mongoose.model('User', userSchema);
    -
    -User.createCollection().then(function(collection) {
    -  console.log('Collection is created!');
    -});

    Model.createIndexes()

    Parameters
    • [options] -«Object» internal options
    • [cb] -«Function» optional callback
    Returns:
    • «Promise»

    Similar to ensureIndexes(), except for it uses the createIndex function.


    Model.deleteMany()

    Parameters
    Returns:
    • «Query»

    Deletes all of the documents that match conditions from the collection. Behaves like remove(), but deletes all documents that match conditions regardless of the single option.

    - -

    Example:

    - -
    Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }, function (err) {});
    - -

    Note:

    - -

    Like Model.remove(), this function does not trigger pre('remove') or post('remove') hooks.


    Model.deleteOne()

    Parameters
    Returns:
    • «Query»

    Deletes the first document that matches conditions from the collection. Behaves like remove(), but deletes at most one document regardless of the single option.

    - -

    Example:

    - -
    Character.deleteOne({ name: 'Eddard Stark' }, function (err) {});
    - -

    Note:

    - -

    Like Model.remove(), this function does not trigger pre('remove') or post('remove') hooks.


    Model.discriminator()

    Parameters
    • name -«String» discriminator model name
    • schema -«Schema» discriminator model schema
    • value -«String» the string stored in the discriminatorKey property

    Adds a discriminator type.

    - -

    Example:

    - -
    function BaseSchema() {
    -  Schema.apply(this, arguments);
    -
    -  this.add({
    -    name: String,
    -    createdAt: Date
    -  });
    -}
    -util.inherits(BaseSchema, Schema);
    -
    -var PersonSchema = new BaseSchema();
    -var BossSchema = new BaseSchema({ department: String });
    -
    -var Person = mongoose.model('Person', PersonSchema);
    -var Boss = Person.discriminator('Boss', BossSchema);
    -new Boss().__t; // "Boss". `__t` is the default `discriminatorKey`
    -
    -var employeeSchema = new Schema({ boss: ObjectId });
    -var Employee = Person.discriminator('Employee', employeeSchema, 'staff');
    -new Employee().__t; // "staff" because of 3rd argument above

    Model.distinct()

    Parameters
    • field -«String»
    • [conditions] -«Object» optional
    • [callback] -«Function»
    Returns:
    • «Query»

    Creates a Query for a distinct operation.

    - -

    Passing a callback executes the query.

    - -

    Example

    - -
    Link.distinct('url', { clicks: {$gt: 100}}, function (err, result) {
    -  if (err) return handleError(err);
    -
    -  assert(Array.isArray(result));
    -  console.log('unique urls with more than 100 clicks', result);
    -})
    -
    -var query = Link.distinct('url');
    -query.exec(callback);

    Model.ensureIndexes()

    Parameters
    • [options] -«Object» internal options
    • [cb] -«Function» optional callback
    Returns:
    • «Promise»

    Sends createIndex commands to mongo for each index declared in the schema. The createIndex commands are sent in series.

    - -

    Example:

    - -
    Event.ensureIndexes(function (err) {
    -  if (err) return handleError(err);
    -});
    - -

    After completion, an index event is emitted on this Model passing an error if one occurred.

    - -

    Example:

    - -
    var eventSchema = new Schema({ thing: { type: 'string', unique: true }})
    -var Event = mongoose.model('Event', eventSchema);
    -
    -Event.on('index', function (err) {
    -  if (err) console.error(err); // error occurred during index creation
    -})
    - -

    NOTE: It is not recommended that you run this in production. Index creation may impact database performance depending on your load. Use with caution.


    Model.estimatedDocumentCount()

    Parameters
    • [options] -«Object»
    • [callback] -«Function»
    Returns:
    • «Query»

    Estimates the number of documents in the MongoDB collection. Faster than using countDocuments() for large collections because estimatedDocumentCount() uses collection metadata rather than scanning the entire collection.

    - -

    Example:

    - -
    const numAdventures = Adventure.estimatedDocumentCount();

    Model.events

    Type:
    • «property»

    Event emitter that reports any errors that occurred. Useful for global error handling.

    - -

    Example:

    - -
    MyModel.events.on('error', err => console.log(err.message));
    -
    -// Prints a 'CastError' because of the above handler
    -await MyModel.findOne({ _id: 'notanid' }).catch(noop);

    Model.exists()

    Parameters
    • filter -«Object»
    • [callback] -«Function» callback
    Returns:
    • «Promise»

    Returns true if at least one document exists in the database that matches the given filter, and false otherwise.

    - -

    Under the hood, MyModel.exists({ answer: 42 }) is equivalent to MyModel.findOne({ answer: 42 }).select({ _id: 1 }).lean().then(doc => !!doc)

    - -

    Example:

    - -
    await Character.deleteMany({});
    -await Character.create({ name: 'Jean-Luc Picard' });
    -
    -await Character.exists({ name: /picard/i }); // true
    -await Character.exists({ name: /riker/i }); // false
    - -

    This function triggers the following middleware.

    - -
      -
    • findOne()
    • -

    Model.find()

    Parameters
    Returns:
    • «Query»

    Finds documents

    - -

    The conditions are cast to their respective SchemaTypes before the command is sent.

    - -

    Examples:

    - -
    // named john and at least 18
    -MyModel.find({ name: 'john', age: { $gte: 18 }});
    -
    -// executes, passing results to callback
    -MyModel.find({ name: 'john', age: { $gte: 18 }}, function (err, docs) {});
    -
    -// executes, name LIKE john and only selecting the "name" and "friends" fields
    -MyModel.find({ name: /john/i }, 'name friends', function (err, docs) { })
    -
    -// passing options
    -MyModel.find({ name: /john/i }, null, { skip: 10 })
    -
    -// passing options and executes
    -MyModel.find({ name: /john/i }, null, { skip: 10 }, function (err, docs) {});
    -
    -// executing a query explicitly
    -var query = MyModel.find({ name: /john/i }, null, { skip: 10 })
    -query.exec(function (err, docs) {});
    -
    -// using the promise returned from executing a query
    -var query = MyModel.find({ name: /john/i }, null, { skip: 10 });
    -var promise = query.exec();
    -promise.addBack(function (err, docs) {});

    Model.findById()

    Parameters
    Returns:
    • «Query»

    Finds a single document by its _id field. findById(id) is almost* equivalent to findOne({ _id: id }). If you want to query by a document's _id, use findById() instead of findOne().

    - -

    The id is cast based on the Schema before sending the command.

    - -

    This function triggers the following middleware.

    - -
      -
    • findOne()
    • -
    - -

    * Except for how it treats undefined. If you use findOne(), you'll see that findOne(undefined) and findOne({ _id: undefined }) are equivalent to findOne({}) and return arbitrary documents. However, mongoose translates findById(undefined) into findOne({ _id: null }).

    - -

    Example:

    - -
    // find adventure by id and execute
    -Adventure.findById(id, function (err, adventure) {});
    -
    -// same as above
    -Adventure.findById(id).exec(callback);
    -
    -// select only the adventures name and length
    -Adventure.findById(id, 'name length', function (err, adventure) {});
    -
    -// same as above
    -Adventure.findById(id, 'name length').exec(callback);
    -
    -// include all properties except for `length`
    -Adventure.findById(id, '-length').exec(function (err, adventure) {});
    -
    -// passing options (in this case return the raw js objects, not mongoose documents by passing `lean`
    -Adventure.findById(id, 'name', { lean: true }, function (err, doc) {});
    -
    -// same as above
    -Adventure.findById(id, 'name').lean().exec(function (err, doc) {});

    Model.findByIdAndDelete()

    Parameters
    Returns:
    • «Query»

    Issue a MongoDB findOneAndDelete() command by a document's _id field. In other words, findByIdAndDelete(id) is a shorthand for findOneAndDelete({ _id: id }).

    - -

    This function triggers the following middleware.

    - -
      -
    • findOneAndDelete()
    • -

    Model.findByIdAndRemove()

    Parameters
    Returns:
    • «Query»

    Issue a mongodb findAndModify remove command by a document's _id field. findByIdAndRemove(id, ...) is equivalent to findOneAndRemove({ _id: id }, ...).

    - -

    Finds a matching document, removes it, passing the found document (if any) to the callback.

    - -

    Executes the query if callback is passed.

    - -

    This function triggers the following middleware.

    - -
      -
    • findOneAndRemove()
    • -
    - -

    Options:

    - -
      -
    • sort: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
    • -
    • select: sets the document fields to return
    • -
    • rawResult: if true, returns the raw result from the MongoDB driver
    • -
    • strict: overwrites the schema's strict mode option for this update
    • -
    - -

    Examples:

    - -
    A.findByIdAndRemove(id, options, callback) // executes
    -A.findByIdAndRemove(id, options)  // return Query
    -A.findByIdAndRemove(id, callback) // executes
    -A.findByIdAndRemove(id) // returns Query
    -A.findByIdAndRemove()           // returns Query

    Model.findByIdAndUpdate()

    Parameters
    • id -«Object|Number|String» value of _id to query by
    • [update] -«Object»
    • [options] -«Object» optional see Query.prototype.setOptions()
      • [options.lean] -«Object» if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See Query.lean() and the Mongoose lean tutorial.
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [callback] -«Function»
    Returns:
    • «Query»

    Issues a mongodb findAndModify update command by a document's _id field. findByIdAndUpdate(id, ...) is equivalent to findOneAndUpdate({ _id: id }, ...).

    - -

    Finds a matching document, updates it according to the update arg, passing any options, and returns the found document (if any) to the callback. The query executes if callback is passed.

    - -

    This function triggers the following middleware.

    - -
      -
    • findOneAndUpdate()
    • -
    - -

    Options:

    - -
      -
    • new: bool - true to return the modified document rather than the original. defaults to false
    • -
    • upsert: bool - creates the object if it doesn't exist. defaults to false.
    • -
    • runValidators: if true, runs update validators on this command. Update validators validate the update operation against the model's schema.
    • -
    • setDefaultsOnInsert: if this and upsert are true, mongoose will apply the defaults specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on MongoDB's $setOnInsert operator.
    • -
    • sort: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
    • -
    • select: sets the document fields to return
    • -
    • rawResult: if true, returns the raw result from the MongoDB driver
    • -
    • strict: overwrites the schema's strict mode option for this update
    • -
    - -

    Examples:

    - -
    A.findByIdAndUpdate(id, update, options, callback) // executes
    -A.findByIdAndUpdate(id, update, options)  // returns Query
    -A.findByIdAndUpdate(id, update, callback) // executes
    -A.findByIdAndUpdate(id, update)           // returns Query
    -A.findByIdAndUpdate()                     // returns Query
    - -

    Note:

    - -

    All top level update keys which are not atomic operation names are treated as set operations:

    - -

    Example:

    - -
    Model.findByIdAndUpdate(id, { name: 'jason bourne' }, options, callback)
    -
    -// is sent as
    -Model.findByIdAndUpdate(id, { $set: { name: 'jason bourne' }}, options, callback)
    - -

    This helps prevent accidentally overwriting your document with { name: 'jason bourne' }.

    - -

    Note:

    - -

    Values are cast to their appropriate types when using the findAndModify helpers. However, the below are not executed by default.

    - -
      -
    • defaults. Use the setDefaultsOnInsert option to override.
    • -
    - -

    findAndModify helpers support limited validation. You can enable these by setting the runValidators options, respectively.

    - -

    If you need full-fledged validation, use the traditional approach of first retrieving the document.

    - -
    Model.findById(id, function (err, doc) {
    -  if (err) ..
    -  doc.name = 'jason bourne';
    -  doc.save(callback);
    -});

    Model.findOne()

    Parameters
    Returns:
    • «Query»

    Finds one document.

    - -

    The conditions are cast to their respective SchemaTypes before the command is sent.

    - -

    Note: conditions is optional, and if conditions is null or undefined, mongoose will send an empty findOne command to MongoDB, which will return an arbitrary document. If you're querying by _id, use findById() instead.

    - -

    Example:

    - -
    // find one iphone adventures - iphone adventures??
    -Adventure.findOne({ type: 'iphone' }, function (err, adventure) {});
    -
    -// same as above
    -Adventure.findOne({ type: 'iphone' }).exec(function (err, adventure) {});
    -
    -// select only the adventures name
    -Adventure.findOne({ type: 'iphone' }, 'name', function (err, adventure) {});
    -
    -// same as above
    -Adventure.findOne({ type: 'iphone' }, 'name').exec(function (err, adventure) {});
    -
    -// specify options, in this case lean
    -Adventure.findOne({ type: 'iphone' }, 'name', { lean: true }, callback);
    -
    -// same as above
    -Adventure.findOne({ type: 'iphone' }, 'name', { lean: true }).exec(callback);
    -
    -// chaining findOne queries (same as above)
    -Adventure.findOne({ type: 'iphone' }).select('name').lean().exec(callback);

    Model.findOneAndDelete()

    Parameters
    Returns:
    • «Query»

    Issue a MongoDB findOneAndDelete() command.

    - -

    Finds a matching document, removes it, and passes the found document (if any) to the callback.

    - -

    Executes the query if callback is passed.

    - -

    This function triggers the following middleware.

    - -
      -
    • findOneAndDelete()
    • -
    - -

    This function differs slightly from Model.findOneAndRemove() in that findOneAndRemove() becomes a MongoDB findAndModify() command, as opposed to a findOneAndDelete() command. For most mongoose use cases, this distinction is purely pedantic. You should use findOneAndDelete() unless you have a good reason not to.

    - -

    Options:

    - -
      -
    • 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
    • -
    • select: sets the document fields to return
    • -
    • projection: like select, it determines which fields to return, ex. { projection: { _id: 0 } }
    • -
    • rawResult: if true, returns the raw result from the MongoDB driver
    • -
    • strict: overwrites the schema's strict mode option for this update
    • -
    - -

    Examples:

    - -
    A.findOneAndDelete(conditions, options, callback) // executes
    -A.findOneAndDelete(conditions, options)  // return Query
    -A.findOneAndDelete(conditions, callback) // executes
    -A.findOneAndDelete(conditions) // returns Query
    -A.findOneAndDelete()           // returns Query
    - -

    Values are cast to their appropriate types when using the findAndModify helpers. However, the below are not executed by default.

    - -
      -
    • defaults. Use the setDefaultsOnInsert option to override.
    • -
    - -

    findAndModify helpers support limited validation. You can enable these by setting the runValidators options, respectively.

    - -

    If you need full-fledged validation, use the traditional approach of first retrieving the document.

    - -
    Model.findById(id, function (err, doc) {
    -  if (err) ..
    -  doc.name = 'jason bourne';
    -  doc.save(callback);
    -});

    Model.findOneAndRemove()

    Parameters
    Returns:
    • «Query»

    Issue a mongodb findAndModify remove command.

    - -

    Finds a matching document, removes it, passing the found document (if any) to the callback.

    - -

    Executes the query if callback is passed.

    - -

    This function triggers the following middleware.

    - -
      -
    • findOneAndRemove()
    • -
    - -

    Options:

    - -
      -
    • 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
    • -
    • select: sets the document fields to return
    • -
    • projection: like select, it determines which fields to return, ex. { projection: { _id: 0 } }
    • -
    • rawResult: if true, returns the raw result from the MongoDB driver
    • -
    • strict: overwrites the schema's strict mode option for this update
    • -
    - -

    Examples:

    - -
    A.findOneAndRemove(conditions, options, callback) // executes
    -A.findOneAndRemove(conditions, options)  // return Query
    -A.findOneAndRemove(conditions, callback) // executes
    -A.findOneAndRemove(conditions) // returns Query
    -A.findOneAndRemove()           // returns Query
    - -

    Values are cast to their appropriate types when using the findAndModify helpers. However, the below are not executed by default.

    - -
      -
    • defaults. Use the setDefaultsOnInsert option to override.
    • -
    - -

    findAndModify helpers support limited validation. You can enable these by setting the runValidators options, respectively.

    - -

    If you need full-fledged validation, use the traditional approach of first retrieving the document.

    - -
    Model.findById(id, function (err, doc) {
    -  if (err) ..
    -  doc.name = 'jason bourne';
    -  doc.save(callback);
    -});

    Model.findOneAndReplace()

    Parameters
    • filter -«Object» Replace the first document that matches this filter
    • [replacement] -«Object» Replace with this document
    • [options] -«Object» optional see Query.prototype.setOptions()
      • [options.lean] -«Object» if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See Query.lean().
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [callback] -«Function»
    Returns:
    • «Query»

    Issue a MongoDB findOneAndReplace() command.

    - -

    Finds a matching document, replaces it with the provided doc, and passes the returned doc to the callback.

    - -

    Executes the query if callback is passed.

    - -

    This function triggers the following query middleware.

    - -
      -
    • findOneAndReplace()
    • -
    - -

    Options:

    - -
      -
    • 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
    • -
    • select: sets the document fields to return
    • -
    • projection: like select, it determines which fields to return, ex. { projection: { _id: 0 } }
    • -
    • rawResult: if true, returns the raw result from the MongoDB driver
    • -
    • strict: overwrites the schema's strict mode option for this update
    • -
    - -

    Examples:

    - -
    A.findOneAndReplace(conditions, options, callback) // executes
    -A.findOneAndReplace(conditions, options)  // return Query
    -A.findOneAndReplace(conditions, callback) // executes
    -A.findOneAndReplace(conditions) // returns Query
    -A.findOneAndReplace()           // returns Query
    - -

    Values are cast to their appropriate types when using the findAndModify helpers. However, the below are not executed by default.

    - -
      -
    • defaults. Use the setDefaultsOnInsert option to override.
    • -

    Model.findOneAndUpdate()

    Parameters
    • [conditions] -«Object»
    • [update] -«Object»
    • [options] -«Object» optional see Query.prototype.setOptions()
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [callback] -«Function»
    Returns:
    • «Query»

    Issues a mongodb findAndModify update command.

    - -

    Finds a matching document, updates it according to the update arg, passing any options, and returns the found document (if any) to the callback. The query executes if callback is passed else a Query object is returned.

    - -

    Options:

    - -
      -
    • new: bool - if true, return the modified document rather than the original. defaults to false (changed in 4.0)
    • -
    • upsert: bool - creates the object if it doesn't exist. defaults to false.
    • -
    • fields: {Object|String} - Field selection. Equivalent to .select(fields).findOneAndUpdate()
    • -
    • maxTimeMS: puts a time limit on the query - requires mongodb >= 2.6.0
    • -
    • sort: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
    • -
    • runValidators: if true, runs update validators on this command. Update validators validate the update operation against the model's schema.
    • -
    • setDefaultsOnInsert: if this and upsert are true, mongoose will apply the defaults specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on MongoDB's $setOnInsert operator.
    • -
    • rawResult: if true, returns the raw result from the MongoDB driver
    • -
    • strict: overwrites the schema's strict mode option for this update
    • -
    - -

    Examples:

    - -
    A.findOneAndUpdate(conditions, update, options, callback) // executes
    -A.findOneAndUpdate(conditions, update, options)  // returns Query
    -A.findOneAndUpdate(conditions, update, callback) // executes
    -A.findOneAndUpdate(conditions, update)           // returns Query
    -A.findOneAndUpdate()                             // returns Query
    - -

    Note:

    - -

    All top level update keys which are not atomic operation names are treated as set operations:

    - -

    Example:

    - -
    var query = { name: 'borne' };
    -Model.findOneAndUpdate(query, { name: 'jason bourne' }, options, callback)
    -
    -// is sent as
    -Model.findOneAndUpdate(query, { $set: { name: 'jason bourne' }}, options, callback)
    - -

    This helps prevent accidentally overwriting your document with { name: 'jason bourne' }.

    - -

    Note:

    - -

    Values are cast to their appropriate types when using the findAndModify helpers. However, the below are not executed by default.

    - -
      -
    • defaults. Use the setDefaultsOnInsert option to override.
    • -
    - -

    findAndModify helpers support limited validation. You can enable these by setting the runValidators options, respectively.

    - -

    If you need full-fledged validation, use the traditional approach of first retrieving the document.

    - -
    Model.findById(id, function (err, doc) {
    -  if (err) ..
    -  doc.name = 'jason bourne';
    -  doc.save(callback);
    -});

    Model.geoSearch()

    Parameters
    • conditions -«Object» an object that specifies the match condition (required)
    • options -«Object» for the geoSearch, some (near, maxDistance) are required
      • [options.lean] -«Object|Boolean» if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See Query.lean() and the Mongoose lean tutorial.
    • [callback] -«Function» optional callback
    Returns:
    • «Promise»

    Implements $geoSearch functionality for Mongoose

    - -

    This function does not trigger any middleware

    - -

    Example:

    - -
    var options = { near: [10, 10], maxDistance: 5 };
    -Locations.geoSearch({ type : "house" }, options, function(err, res) {
    -  console.log(res);
    -});
    - -

    Options:

    - -
      -
    • near {Array} x,y point to search for
    • -
    • maxDistance {Number} the maximum distance from the point near that a result can be
    • -
    • limit {Number} The maximum number of results to return
    • -
    • lean {Object|Boolean} return the raw object instead of the Mongoose Model
    • -

    Model.hydrate()

    Parameters
    • obj -«Object»
    Returns:
    • «Model» document instance

    Shortcut for creating a new Document from existing raw data, pre-saved in the DB. The document returned has no paths marked as modified initially.

    - -

    Example:

    - -
    // hydrate previous data into a Mongoose document
    -var mongooseCandy = Candy.hydrate({ _id: '54108337212ffb6d459f854c', type: 'jelly bean' });

    Model.init()

    Parameters
    • [callback] -«Function»

    This function is responsible for building indexes, unless autoIndex is turned off.

    - -

    Mongoose calls this function automatically when a model is created using mongoose.model() or * connection.model(), so you don't need to call it. This function is also idempotent, so you may call it to get back a promise that will resolve when your indexes are finished building as an alternative to MyModel.on('index')

    - -

    Example:

    - -
    var eventSchema = new Schema({ thing: { type: 'string', unique: true }})
    -// This calls `Event.init()` implicitly, so you don't need to call
    -// `Event.init()` on your own.
    -var Event = mongoose.model('Event', eventSchema);
    -
    -Event.init().then(function(Event) {
    -  // You can also use `Event.on('index')` if you prefer event emitters
    -  // over promises.
    -  console.log('Indexes are done building!');
    -});

    Model.insertMany()

    Parameters
    • doc(s) -«Array|Object|*»
    • [options] -«Object» see the mongodb driver options
    • [options.ordered -«Boolean» = true] if true, will fail fast on the first error encountered. If false, will insert all the documents it can and report errors later. An insertMany() with ordered = false is called an "unordered" insertMany().
    • [options.rawResult -«Boolean» = false] if false, the returned promise resolves to the documents that passed mongoose document validation. If true, will return the raw result from the MongoDB driver with a mongoose property that contains validationErrors if this is an unordered insertMany.
    • [callback] -«Function» callback
    Returns:
    • «Promise»

    Shortcut for validating an array of documents and inserting them into MongoDB if they're all valid. This function is faster than .create() because it only sends one operation to the server, rather than one for each document.

    - -

    Mongoose always validates each document before sending insertMany to MongoDB. So if one document has a validation error, no documents will be saved, unless you set the ordered option to false.

    - -

    This function does not trigger save middleware.

    - -

    This function triggers the following middleware.

    - -
      -
    • insertMany()
    • -
    - -

    Example:

    - -
    var arr = [{ name: 'Star Wars' }, { name: 'The Empire Strikes Back' }];
    -Movies.insertMany(arr, function(error, docs) {});

    Model.listIndexes()

    Parameters
    • [cb] -«Function» optional callback
    Returns:
    • «Promise,undefined» Returns undefined if callback is specified, returns a promise if no callback.

    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 and if you build indexes manually.


    Model.mapReduce()

    Parameters
    • o -«Object» an object specifying map-reduce options
    • [callback] -«Function» optional callback
    Returns:
    • «Promise»

    Executes a mapReduce command.

    - -

    o is an object specifying all mapReduce options as well as the map and reduce functions. All options are delegated to the driver implementation. See node-mongodb-native mapReduce() documentation for more detail about options.

    - -

    This function does not trigger any middleware.

    - -

    Example:

    - -
    var o = {};
    -// `map()` and `reduce()` are run on the MongoDB server, not Node.js,
    -// these functions are converted to strings
    -o.map = function () { emit(this.name, 1) };
    -o.reduce = function (k, vals) { return vals.length };
    -User.mapReduce(o, function (err, results) {
    -  console.log(results)
    -})
    - -

    Other options:

    - -
      -
    • query {Object} query filter object.
    • -
    • sort {Object} sort input objects using this key
    • -
    • limit {Number} max number of documents
    • -
    • keeptemp {Boolean, default:false} keep temporary data
    • -
    • finalize {Function} finalize function
    • -
    • scope {Object} scope variables exposed to map/reduce/finalize during execution
    • -
    • jsMode {Boolean, default:false} it is possible to make the execution stay in JS. Provided in MongoDB > 2.0.X
    • -
    • verbose {Boolean, default:false} provide statistics on job execution time.
    • -
    • readPreference {String}
    • -
    • out* {Object, default: {inline:1}} sets the output target for the map reduce job.
    • -
    - -

    * out options:

    - -
      -
    • {inline:1} the results are returned in an array
    • -
    • {replace: 'collectionName'} add the results to collectionName: the results replace the collection
    • -
    • {reduce: 'collectionName'} add the results to collectionName: if dups are detected, uses the reducer / finalize functions
    • -
    • {merge: 'collectionName'} add the results to collectionName: if dups exist the new docs overwrite the old
    • -
    - -

    If options.out is set to replace, merge, or reduce, a Model instance is returned that can be used for further querying. Queries run against this model are all executed with the lean option; meaning only the js object is returned and no Mongoose magic is applied (getters, setters, etc).

    - -

    Example:

    - -
    var o = {};
    -// You can also define `map()` and `reduce()` as strings if your
    -// linter complains about `emit()` not being defined
    -o.map = 'function () { emit(this.name, 1) }';
    -o.reduce = 'function (k, vals) { return vals.length }';
    -o.out = { replace: 'createdCollectionNameForResults' }
    -o.verbose = true;
    -
    -User.mapReduce(o, function (err, model, stats) {
    -  console.log('map reduce took %d ms', stats.processtime)
    -  model.find().where('value').gt(10).exec(function (err, docs) {
    -    console.log(docs);
    -  });
    -})
    -
    -// `mapReduce()` returns a promise. However, ES6 promises can only
    -// resolve to exactly one value,
    -o.resolveToObject = true;
    -var promise = User.mapReduce(o);
    -promise.then(function (res) {
    -  var model = res.model;
    -  var stats = res.stats;
    -  console.log('map reduce took %d ms', stats.processtime)
    -  return model.find().where('value').gt(10).exec();
    -}).then(function (docs) {
    -   console.log(docs);
    -}).then(null, handleError).end()

    Model.populate()

    Parameters
    • docs -«Document|Array» Either a single document or array of documents to populate.
    • options -«Object» A hash of key/val (path, options) used for population.
      • [options.retainNullValues=false] -«boolean» by default, Mongoose removes null and undefined values from populated arrays. Use this option to make populate() retain null and undefined array entries.
      • [options.getters=false] -«boolean» 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.
      • [options.clone=false] -«boolean» 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.
      • [options.match=null] -«Object|Function» Add an additional filter to the populate query. Can be a filter object containing MongoDB query syntax, or a function that returns a filter object.
      • [options.skipInvalidIds=false] -«Boolean» 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.
    • [callback(err,doc)] -«Function» Optional callback, executed upon completion. Receives err and the doc(s).
    Returns:
    • «Promise»

    Populates document references.

    - -

    Available top-level options:

    - -
      -
    • path: space delimited path(s) to populate
    • -
    • select: optional fields to select
    • -
    • match: optional query conditions to match
    • -
    • model: optional name of the model to use for population
    • -
    • options: optional query options like sort, limit, etc
    • -
    • justOne: optional boolean, if true Mongoose will always set path to an array. Inferred from schema by default.
    • -
    - -

    Examples:

    - -
    // populates a single object
    -User.findById(id, function (err, user) {
    -  var opts = [
    -    { path: 'company', match: { x: 1 }, select: 'name' },
    -    { path: 'notes', options: { limit: 10 }, model: 'override' }
    -  ];
    -
    -  User.populate(user, opts, function (err, user) {
    -    console.log(user);
    -  });
    -});
    -
    -// populates an array of objects
    -User.find(match, function (err, users) {
    -  var opts = [{ path: 'company', match: { x: 1 }, select: 'name' }];
    -
    -  var promise = User.populate(users, opts);
    -  promise.then(console.log).end();
    -})
    -
    -// imagine a Weapon model exists with two saved documents:
    -//   { _id: 389, name: 'whip' }
    -//   { _id: 8921, name: 'boomerang' }
    -// and this schema:
    -// new Schema({
    -//   name: String,
    -//   weapon: { type: ObjectId, ref: 'Weapon' }
    -// });
    -
    -var user = { name: 'Indiana Jones', weapon: 389 };
    -Weapon.populate(user, { path: 'weapon', model: 'Weapon' }, function (err, user) {
    -  console.log(user.weapon.name); // whip
    -})
    -
    -// populate many plain objects
    -var users = [{ name: 'Indiana Jones', weapon: 389 }]
    -users.push({ name: 'Batman', weapon: 8921 })
    -Weapon.populate(users, { path: 'weapon' }, function (err, users) {
    -  users.forEach(function (user) {
    -    console.log('%s uses a %s', users.name, user.weapon.name)
    -    // Indiana Jones uses a whip
    -    // Batman uses a boomerang
    -  });
    -});
    -// Note that we didn't need to specify the Weapon model because
    -// it is in the schema's ref

    Model.prototype.$where

    Type:
    • «property»

    Additional properties to attach to the query when calling save() and isNew is false.


    Model.prototype.$where()

    Parameters
    • argument -«String|Function» is a javascript string or anonymous function
    Returns:
    • «Query»

    Creates a Query and specifies a $where condition.

    - -

    Sometimes you need to query for things in mongodb using a JavaScript expression. You can do so via find({ $where: javascript }), or you can use the mongoose shortcut method $where via a Query chain or from your mongoose Model.

    - -
    Blog.$where('this.username.indexOf("val") !== -1').exec(function (err, docs) {});

    Model.prototype.base

    Type:
    • «property»

    Base Mongoose instance the model uses.


    Model.prototype.baseModelName

    Type:
    • «property»

    If this is a discriminator model, baseModelName is the name of the base model.


    Model.prototype.collection

    Type:
    • «property»

    Collection the model uses.

    - -

    This property is read-only. Modifying this property is a no-op.


    Model.prototype.db

    Type:
    • «property»

    Connection the model uses.


    Model.prototype.delete

    Type:
    • «property»

    Alias for remove


    Model.prototype.deleteOne()

    Parameters
    • [fn] -«function(err|product)» optional callback
    Returns:
    • «Promise» Promise

    Removes this document from the db. Equivalent to .remove().

    - -

    Example:

    - -
    product = await product.deleteOne();
    -await Product.findById(product._id); // null

    Model.prototype.discriminators

    Type:
    • «property»

    Registered discriminators for this model.


    Model.prototype.increment()

    Signal that we desire an increment of this documents version.

    - -

    Example:

    - -
    Model.findById(id, function (err, doc) {
    -  doc.increment();
    -  doc.save(function (err) { .. })
    -})

    Model.prototype.model()

    Parameters
    • name -«String» model name

    Returns another Model instance.

    - -

    Example:

    - -
    var doc = new Tank;
    -doc.model('User').findById(id, callback);

    Model.prototype.modelName

    Type:
    • «property»

    The name of the model


    Model.prototype.remove()

    Parameters
    • [fn] -«function(err|product)» optional callback
    Returns:
    • «Promise» Promise

    Removes this document from the db.

    - -

    Example:

    - -
    product.remove(function (err, product) {
    -  if (err) return handleError(err);
    -  Product.findById(product._id, function (err, product) {
    -    console.log(product) // null
    -  })
    -})
    - -

    As an extra measure of flow control, remove will return a Promise (bound to fn if passed) so it could be chained, or hooked to recieve errors

    - -

    Example:

    - -
    product.remove().then(function (product) {
    -   ...
    -}).catch(function (err) {
    -   assert.ok(err)
    -})

    Model.prototype.save()

    Parameters
    Returns:
    • «Promise,undefined» Returns undefined if used with callback or a Promise otherwise.

    Saves this document.

    - -

    Example:

    - -
    product.sold = Date.now();
    -product = await product.save();
    - -

    If save is successful, the returned promise will fulfill with the document saved.

    - -

    Example:

    - -
    const newProduct = await product.save();
    -newProduct === product; // true

    Model.prototype.schema

    Type:
    • «property»

    Schema the model uses.


    Model.remove()

    Parameters
    • conditions -«Object»
    • [callback] -«Function»
    Returns:
    • «Query»

    Removes all documents that match conditions from the collection. To remove just the first document that matches conditions, set the single option to true.

    - -

    Example:

    - -
    const res = await Character.remove({ name: 'Eddard Stark' });
    -res.deletedCount; // Number of documents removed
    - -

    Note:

    - -

    This method sends a remove command directly to MongoDB, no Mongoose documents are involved. Because no Mongoose documents are involved, Mongoose does not execute document middleware.


    Model.replaceOne()

    Parameters
    • filter -«Object»
    • doc -«Object»
    • [options] -«Object» optional see Query.prototype.setOptions()
      • [options.upsert=false] -«Boolean» if true, and no documents found, insert a new document
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
      • [options.timestamps=null] -«Boolean» If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
    • [callback] -«Function» function(error, res) {} where res has 3 properties: n, nModified, ok.
    Returns:
    • «Query»

    Same as update(), except MongoDB replace the existing document with the given document (no atomic operators like $set).

    - -

    Example:

    - -
    const res = await Person.replaceOne({ _id: 24601 }, { name: 'Jean Valjean' });
    -res.n; // Number of documents matched
    -res.nModified; // Number of documents modified
    - -

    This function triggers the following middleware.

    - -
      -
    • replaceOne()
    • -

    Model.startSession()

    Parameters
    • [options] -«Object» see the mongodb driver options
      • [options.causalConsistency=true] -«Boolean» set to false to disable causal consistency
    • [callback] -«Function»
    Returns:
    • «Promise<ClientSession>» promise that resolves to a MongoDB driver ClientSession

    Requires MongoDB >= 3.6.0. Starts a MongoDB session for benefits like causal consistency, retryable writes, and transactions.

    - -

    Calling MyModel.startSession() is equivalent to calling MyModel.db.startSession().

    - -

    This function does not trigger any middleware.

    - -

    Example:

    - -
    const session = await Person.startSession();
    -let doc = await Person.findOne({ name: 'Ned Stark' }, null, { session });
    -await doc.remove();
    -// `doc` will always be null, even if reading from a replica set
    -// secondary. Without causal consistency, it is possible to
    -// get a doc back from the below query if the query reads from a
    -// secondary that is experiencing replication lag.
    -doc = await Person.findOne({ name: 'Ned Stark' }, null, { session, readPreference: 'secondary' });

    Model.syncIndexes()

    Parameters
    • [options] -«Object» options to pass to ensureIndexes()
    • [callback] -«Function» optional callback
    Returns:
    • «Promise,undefined» Returns undefined if callback is specified, returns a promise if no callback.

    Makes the indexes in MongoDB match the indexes defined in this model's schema. This function will drop any indexes that are not defined in the model's schema except the _id index, and build any indexes that are in your schema but not in MongoDB.

    - -

    See the introductory blog post for more information.

    - -

    Example:

    - -
    const schema = new Schema({ name: { type: String, unique: true } });
    -const Customer = mongoose.model('Customer', schema);
    -await Customer.createIndex({ age: 1 }); // Index is not in schema
    -// Will drop the 'age' index and create an index on `name`
    -await Customer.syncIndexes();

    Model.translateAliases()

    Parameters
    • raw -«Object» fields/conditions that may contain aliased keys
    Returns:
    • «Object» the translated 'pure' fields/conditions

    Translate any aliases fields/conditions so the final query or document object is pure

    - -

    Example:

    - -
    Character
    -  .find(Character.translateAliases({
    -    '名': 'Eddard Stark' // Alias for 'name'
    -  })
    -  .exec(function(err, characters) {})
    - -

    Note:

    - -

    Only translate arguments of object type anything else is returned raw


    Model.update()

    Parameters
    • filter -«Object»
    • doc -«Object»
    • [options] -«Object» optional see Query.prototype.setOptions()
      • [options.upsert=false] -«Boolean» if true, and no documents found, insert a new document
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
      • [options.multi=false] -«Boolean» whether multiple documents should be updated or just the first one that matches filter.
      • [options.runValidators=false] -«Boolean» if true, runs update validators on this command. Update validators validate the update operation against the model's schema.
      • [options.setDefaultsOnInsert=false] -«Boolean» if this and upsert are true, mongoose will apply the defaults specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on MongoDB's $setOnInsert operator.
      • [options.timestamps=null] -«Boolean» If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
      • [options.overwrite=false] -«Boolean» By default, if you don't include any update operators in doc, Mongoose will wrap doc in $set for you. This prevents you from accidentally overwriting the document. This option tells Mongoose to skip adding $set.
    • [callback] -«Function» params are (error, writeOpResult)
    • [callback] -«Function»
    Returns:
    • «Query»

    Updates one document in the database without returning it.

    - -

    This function triggers the following middleware.

    - -
      -
    • update()
    • -
    - -

    Examples:

    - -
    MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn);
    -
    -const res = await MyModel.update({ name: 'Tobi' }, { ferret: true });
    -res.n; // Number of documents that matched `{ name: 'Tobi' }`
    -// Number of documents that were changed. If every doc matched already
    -// had `ferret` set to `true`, `nModified` will be 0.
    -res.nModified;
    - -

    Valid options:

    - -
      -
    • strict (boolean): overrides the schema-level strict option for this update
    • -
    • upsert (boolean): whether to create the doc if it doesn't match (false)
    • -
    • writeConcern (object): sets the write concern for replica sets. Overrides the schema-level write concern
    • -
    • omitUndefined (boolean): If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • -
    • multi (boolean): whether multiple documents should be updated (false)
    • -
    • runValidators: if true, runs update validators on this command. Update validators validate the update operation against the model's schema.
    • -
    • setDefaultsOnInsert (boolean): if this and upsert are true, mongoose will apply the defaults specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on MongoDB's $setOnInsert operator.
    • -
    • timestamps (boolean): If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
    • -
    • overwrite (boolean): disables update-only mode, allowing you to overwrite the doc (false)
    • -
    - -

    All update values are cast to their appropriate SchemaTypes before being sent.

    - -

    The callback function receives (err, rawResponse).

    - -
      -
    • err is the error if any occurred
    • -
    • rawResponse is the full response from Mongo
    • -
    - -

    Note:

    - -

    All top level keys which are not atomic operation names are treated as set operations:

    - -

    Example:

    - -
    var query = { name: 'borne' };
    -Model.update(query, { name: 'jason bourne' }, options, callback);
    -
    -// is sent as
    -Model.update(query, { $set: { name: 'jason bourne' }}, options, function(err, res));
    -// if overwrite option is false. If overwrite is true, sent without the $set wrapper.
    -
    - -

    This helps prevent accidentally overwriting all documents in your collection with { name: 'jason bourne' }.

    - -

    Note:

    - -

    Be careful to not use an existing model instance for the update clause (this won't work and can cause weird behavior like infinite loops). Also, ensure that the update clause does not have an _id property, which causes Mongo to return a "Mod on _id not allowed" error.

    - -

    Note:

    - -

    Mongoose casts values and runs setters when using update. The following features are not applied by default.

    - - - -

    If you need document middleware and fully-featured validation, load the document first and then use save().

    - -
    Model.findOne({ name: 'borne' }, function (err, doc) {
    -  if (err) ..
    -  doc.name = 'jason bourne';
    -  doc.save(callback);
    -})

    Model.updateMany()

    Parameters
    • filter -«Object»
    • doc -«Object»
    • [options] -«Object» optional see Query.prototype.setOptions()
      • [options.upsert=false] -«Boolean» if true, and no documents found, insert a new document
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
      • [options.timestamps=null] -«Boolean» If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
    • [callback] -«Function» function(error, res) {} where res has 3 properties: n, nModified, ok.
    Returns:
    • «Query»

    Same as update(), except MongoDB will update all documents that match filter (as opposed to just the first one) regardless of the value of the multi option.

    - -

    Note updateMany will not fire update middleware. Use pre('updateMany') and post('updateMany') instead.

    - -

    Example:

    - -
    const res = await Person.updateMany({ name: /Stark$/ }, { isDeleted: true });
    -res.n; // Number of documents matched
    -res.nModified; // Number of documents modified
    - -

    This function triggers the following middleware.

    - -
      -
    • updateMany()
    • -

    Model.updateOne()

    Parameters
    • filter -«Object»
    • doc -«Object»
    • [options] -«Object» optional see Query.prototype.setOptions()
      • [options.upsert=false] -«Boolean» if true, and no documents found, insert a new document
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
      • [options.timestamps=null] -«Boolean» If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
    • [callback] -«Function» params are (error, writeOpResult)
    Returns:
    • «Query»

    Same as update(), except it does not support the multi or overwrite options.

    - -
      -
    • MongoDB will update only the first document that matches filter regardless of the value of the multi option.
    • -
    • Use replaceOne() if you want to overwrite an entire document rather than using atomic operators like $set.
    • -
    - -

    Example:

    - -
    const res = await Person.updateOne({ name: 'Jean-Luc Picard' }, { ship: 'USS Enterprise' });
    -res.n; // Number of documents matched
    -res.nModified; // Number of documents modified
    - -

    This function triggers the following middleware.

    - -
      -
    • updateOne()
    • -

    Model.watch()

    Parameters
    Returns:
    • «ChangeStream» mongoose-specific change stream wrapper, inherits from EventEmitter

    Requires a replica set running MongoDB >= 3.6.0. Watches the underlying collection for changes using MongoDB change streams.

    - -

    This function does not trigger any middleware. In particular, it does not trigger aggregate middleware.

    - -

    The ChangeStream object is an event emitter that emits the following events

    - -
      -
    • 'change': A change occurred, see below example
    • -
    • 'error': An unrecoverable error occurred. In particular, change streams currently error out if they lose connection to the replica set primary. Follow this GitHub issue for updates.
    • -
    • 'end': Emitted if the underlying stream is closed
    • -
    • 'close': Emitted if the underlying stream is closed
    • -
    - -

    Example:

    - -
    const doc = await Person.create({ name: 'Ned Stark' });
    -const changeStream = Person.watch().on('change', change => console.log(change));
    -// Will print from the above `console.log()`:
    -// { _id: { _data: ... },
    -//   operationType: 'delete',
    -//   ns: { db: 'mydb', coll: 'Person' },
    -//   documentKey: { _id: 5a51b125c5500f5aa094c7bd } }
    -await doc.remove();

    Model.where()

    Parameters
    • path -«String»
    • [val] -«Object» optional value
    Returns:
    • «Query»

    Creates a Query, applies the passed conditions, and returns the Query.

    - -

    For example, instead of writing:

    - -
    User.find({age: {$gte: 21, $lte: 65}}, callback);
    - -

    we can instead write:

    - -
    User.where('age').gte(21).lte(65).exec(callback);
    - -

    Since the Query class also supports where you can continue chaining

    - -
    User
    -.where('age').gte(21).lte(65)
    -.where('name', /^b/i)
    -... etc

    function Object() { [native code] }.prototype.inspect()

    Helper for console.log

    \ No newline at end of file diff --git a/docs/api/mongoose.html b/docs/api/mongoose.html deleted file mode 100644 index f48284709bf..00000000000 --- a/docs/api/mongoose.html +++ /dev/null @@ -1,317 +0,0 @@ -Mongoose v5.6.0:

    Mongoose


    Mongoose()

    Mongoose constructor.

    - -

    The exports object of the mongoose module is an instance of this class. Most apps will only use this one instance.


    Mongoose.prototype.Aggregate()

    The Mongoose Aggregate constructor


    Mongoose.prototype.CastError()

    Parameters
    • type -«String» The name of the type
    • value -«Any» The value that failed to cast
    • path -«String» The path a.b.c in the doc where this cast error occurred
    • [reason] -«Error» The original error that was thrown

    The Mongoose CastError constructor


    Mongoose.prototype.Collection()

    The Mongoose Collection constructor


    Mongoose.prototype.Connection()

    The Mongoose Connection constructor


    Mongoose.prototype.Decimal128

    Type:
    • «property»

    The Mongoose Decimal128 SchemaType. Used for declaring paths in your schema that should be 128-bit decimal floating points. Do not use this to create a new Decimal128 instance, use mongoose.Types.Decimal128 instead.

    - -

    Example:

    - -
    const vehicleSchema = new Schema({ fuelLevel: mongoose.Decimal128 });

    Mongoose.prototype.Document()

    The Mongoose Document constructor.


    Mongoose.prototype.DocumentProvider()

    The Mongoose DocumentProvider constructor. Mongoose users should not have to use this directly


    Mongoose.prototype.Error()

    The MongooseError constructor.


    Mongoose.prototype.Mixed

    Type:
    • «property»

    The Mongoose Mixed SchemaType. Used for declaring paths in your schema that Mongoose's change tracking, casting, and validation should ignore.

    - -

    Example:

    - -
    const schema = new Schema({ arbitrary: mongoose.Mixed });

    Mongoose.prototype.Model()

    The Mongoose Model constructor.


    Mongoose.prototype.Mongoose()

    The Mongoose constructor

    - -

    The exports of the mongoose module is an instance of this class.

    - -

    Example:

    - -
    var mongoose = require('mongoose');
    -var mongoose2 = new mongoose.Mongoose();

    Mongoose.prototype.Number

    Type:
    • «property»

    The Mongoose Number SchemaType. Used for declaring paths in your schema that Mongoose should cast to numbers.

    - -

    Example:

    - -
    const schema = new Schema({ num: mongoose.Number });
    -// Equivalent to:
    -const schema = new Schema({ num: 'number' });

    Mongoose.prototype.ObjectId

    Type:
    • «property»

    The Mongoose ObjectId SchemaType. Used for declaring paths in your schema that should be MongoDB ObjectIds. Do not use this to create a new ObjectId instance, use mongoose.Types.ObjectId instead.

    - -

    Example:

    - -
    const childSchema = new Schema({ parentId: mongoose.ObjectId });

    Mongoose.prototype.Promise

    Type:
    • «property»

    The Mongoose Promise constructor.


    Mongoose.prototype.PromiseProvider()

    Storage layer for mongoose promises


    Mongoose.prototype.Query()

    The Mongoose Query constructor.


    Mongoose.prototype.STATES

    Type:
    • «property»

    Expose connection states for user-land


    Mongoose.prototype.Schema()

    The Mongoose Schema constructor

    - -

    Example:

    - -
    var mongoose = require('mongoose');
    -var Schema = mongoose.Schema;
    -var CatSchema = new Schema(..);

    Mongoose.prototype.SchemaType()

    The Mongoose SchemaType constructor


    Mongoose.prototype.SchemaTypes

    Type:
    • «property»

    The various Mongoose SchemaTypes.

    - -

    Note:

    - -

    Alias of mongoose.Schema.Types for backwards compatibility.


    Mongoose.prototype.Types

    Type:
    • «property»

    The various Mongoose Types.

    - -

    Example:

    - -
    var mongoose = require('mongoose');
    -var array = mongoose.Types.Array;
    - -

    Types:

    - - - -

    Using this exposed access to the ObjectId type, we can construct ids on demand.

    - -
    var ObjectId = mongoose.Types.ObjectId;
    -var id1 = new ObjectId;

    Mongoose.prototype.VirtualType()

    The Mongoose VirtualType constructor


    Mongoose.prototype.connect()

    Parameters
    • uri(s) -«String»
    • [options] -«Object» passed down to the MongoDB driver's connect() function, except for 4 mongoose-specific options explained below.
      • [options.dbName] -«String» The name of the database we want to use. If not provided, use database name from connection string.
      • [options.user] -«String» username for authentication, equivalent to options.auth.user. Maintained for backwards compatibility.
      • [options.pass] -«String» password for authentication, equivalent to options.auth.password. Maintained for backwards compatibility.
      • [options.autoIndex=true] -«Boolean» Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection.
      • [options.bufferCommands=true] -«Boolean» Mongoose specific option. Set to false to disable buffering on all models associated with this connection.
      • [options.useFindAndModify=true] -«Boolean» True by default. Set to false to make findOneAndUpdate() and findOneAndRemove() use native findOneAndUpdate() rather than findAndModify().
      • [options.useNewUrlParser=false] -«Boolean» False by default. Set to true to make all connections set the useNewUrlParser option by default.
    • [callback] -«Function»
    Returns:
    • «Promise» resolves to this if connection succeeded

    Opens the default mongoose connection.

    - -

    Example:

    - -
    mongoose.connect('mongodb://user:pass@localhost:port/database');
    -
    -// replica sets
    -var uri = 'mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/mydatabase';
    -mongoose.connect(uri);
    -
    -// with options
    -mongoose.connect(uri, options);
    -
    -// optional callback that gets fired when initial connection completed
    -var uri = 'mongodb://nonexistent.domain:27000';
    -mongoose.connect(uri, function(error) {
    -  // if error is truthy, the initial connection failed.
    -})

    Mongoose.prototype.connection

    Type:
    • «Connection»

    The Mongoose module's default connection. Equivalent to mongoose.connections][0], see connections.

    - -

    Example:

    - -
    var mongoose = require('mongoose');
    -mongoose.connect(...);
    -mongoose.connection.on('error', cb);
    - -

    This is the connection used by default for every model created using mongoose.model.

    - -

    To create a new connection, use createConnection().


    Mongoose.prototype.connections

    Type:
    • «Array»

    An array containing all connections associated with this Mongoose instance. By default, there is 1 connection. Calling createConnection() adds a connection to this array.

    - -

    Example:

    - -
    const mongoose = require('mongoose');
    -mongoose.connections.length; // 1, just the default connection
    -mongoose.connections[0] === mongoose.connection; // true
    -
    -mongoose.createConnection('mongodb://localhost:27017/test');
    -mongoose.connections.length; // 2

    Mongoose.prototype.createConnection()

    Parameters
    • [uri] -«String» a mongodb:// URI
    • [options] -«Object» passed down to the MongoDB driver's connect() function, except for 4 mongoose-specific options explained below.
      • [options.user] -«String» username for authentication, equivalent to options.auth.user. Maintained for backwards compatibility.
      • [options.pass] -«String» password for authentication, equivalent to options.auth.password. Maintained for backwards compatibility.
      • [options.autoIndex=true] -«Boolean» Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection.
      • [options.bufferCommands=true] -«Boolean» Mongoose specific option. Set to false to disable buffering on all models associated with this connection.
    Returns:
    • «Connection» the created Connection object. Connections are thenable, so you can do await mongoose.createConnection()

    Creates a Connection instance.

    - -

    Each connection instance maps to a single database. This method is helpful when mangaging multiple db connections.

    - -

    Options passed take precedence over options included in connection strings.

    - -

    Example:

    - -
    // with mongodb:// URI
    -db = mongoose.createConnection('mongodb://user:pass@localhost:port/database');
    -
    -// and options
    -var opts = { db: { native_parser: true }}
    -db = mongoose.createConnection('mongodb://user:pass@localhost:port/database', opts);
    -
    -// replica sets
    -db = mongoose.createConnection('mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/database');
    -
    -// and options
    -var opts = { replset: { strategy: 'ping', rs_name: 'testSet' }}
    -db = mongoose.createConnection('mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/database', opts);
    -
    -// and options
    -var opts = { server: { auto_reconnect: false }, user: 'username', pass: 'mypassword' }
    -db = mongoose.createConnection('localhost', 'database', port, opts)
    -
    -// initialize now, connect later
    -db = mongoose.createConnection();
    -db.openUri('localhost', 'database', port, [opts]);

    Mongoose.prototype.deleteModel()

    Parameters
    • name -«String|RegExp» if string, the name of the model to remove. If regexp, removes all models whose name matches the regexp.
    Returns:
    • «Mongoose» this

    Removes the model named name from the default connection, if it exists. You can use this function to clean up any models you created in your tests to prevent OverwriteModelErrors.

    - -

    Equivalent to mongoose.connection.deleteModel(name).

    - -

    Example:

    - -
    mongoose.model('User', new Schema({ name: String }));
    -console.log(mongoose.model('User')); // Model object
    -mongoose.deleteModel('User');
    -console.log(mongoose.model('User')); // undefined
    -
    -// Usually useful in a Mocha `afterEach()` hook
    -afterEach(function() {
    -  mongoose.deleteModel(/.+/); // Delete every model
    -});

    Mongoose.prototype.disconnect()

    Parameters
    • [callback] -«Function» called after all connection close, or when first error occurred.
    Returns:
    • «Promise» resolves when all connections are closed, or rejects with the first error that occurred.

    Runs .close() on all connections in parallel.


    Mongoose.prototype.driver

    Type:
    • «property»

    The underlying driver this Mongoose instance uses to communicate with the database. A driver is a Mongoose-specific interface that defines functions like find().


    Mongoose.prototype.get()

    Parameters
    • key -«String»

    Gets mongoose options

    - -

    Example:

    - -
    mongoose.get('test') // returns the 'test' value

    Mongoose.prototype.model()

    Parameters
    • name -«String|Function» model name or class extending Model
    • [schema] -«Schema» the schema to use.
    • [collection] -«String» name (optional, inferred from model name)
    • [skipInit] -«Boolean» whether to skip initialization (defaults to false)
    Returns:
    • «Model» The model associated with name. Mongoose will create the model if it doesn't already exist.

    Defines a model or retrieves it.

    - -

    Models defined on the mongoose instance are available to all connection created by the same mongoose instance.

    - -

    If you call mongoose.model() with twice the same name but a different schema, you will get an OverwriteModelError. If you call mongoose.model() with the same name and same schema, you'll get the same schema back.

    - -

    Example:

    - -
    var mongoose = require('mongoose');
    -
    -// define an Actor model with this mongoose instance
    -const Schema = new Schema({ name: String });
    -mongoose.model('Actor', schema);
    -
    -// create a new connection
    -var conn = mongoose.createConnection(..);
    -
    -// create Actor model
    -var Actor = conn.model('Actor', schema);
    -conn.model('Actor') === Actor; // true
    -conn.model('Actor', schema) === Actor; // true, same schema
    -conn.model('Actor', schema, 'actors') === Actor; // true, same schema and collection name
    -
    -// This throws an `OverwriteModelError` because the schema is different.
    -conn.model('Actor', new Schema({ name: String }));
    - -

    When no collection argument is passed, Mongoose uses the model name. If you don't like this behavior, either pass a collection name, use mongoose.pluralize(), or set your schemas collection name option.

    - -

    Example:

    - -
    var schema = new Schema({ name: String }, { collection: 'actor' });
    -
    -// or
    -
    -schema.set('collection', 'actor');
    -
    -// or
    -
    -var collectionName = 'actor'
    -var M = mongoose.model('Actor', schema, collectionName)

    Mongoose.prototype.modelNames()

    Returns:
    • «Array»

    Returns an array of model names created on this instance of Mongoose.

    - -

    Note:

    - -

    Does not include names of models created using connection.model().


    Mongoose.prototype.mongo

    Type:
    • «property»

    The node-mongodb-native driver Mongoose uses.


    Mongoose.prototype.mquery

    Type:
    • «property»

    The mquery query builder Mongoose uses.


    Mongoose.prototype.now()

    Mongoose uses this function to get the current time when setting timestamps. You may stub out this function using a tool like Sinon for testing.


    Mongoose.prototype.plugin()

    Parameters
    • fn -«Function» plugin callback
    • [opts] -«Object» optional options
    Returns:
    • «Mongoose» this

    Declares a global plugin executed on all Schemas.

    - -

    Equivalent to calling .plugin(fn) on each Schema you create.


    Mongoose.prototype.pluralize()

    Parameters
    • [fn] -«Function|null» overwrites the function used to pluralize collection names
    Returns:
    • «Function,null» the current function used to pluralize collection names, defaults to the legacy function from mongoose-legacy-pluralize.

    Getter/setter around function for pluralizing collection names.


    Mongoose.prototype.set()

    Parameters
    • key -«String»
    • value -«String|Function|Boolean»

    Sets mongoose options

    - -

    Example:

    - -
    mongoose.set('test', value) // sets the 'test' option to `value`
    -
    -mongoose.set('debug', true) // enable logging collection methods + arguments to the console
    -
    -mongoose.set('debug', function(collectionName, methodName, arg1, arg2...) {}); // use custom function to log collection methods + arguments
    - -

    Currently supported options are

    - -
      -
    • 'debug': prints the operations mongoose sends to MongoDB to the console
    • -
    • 'bufferCommands': enable/disable mongoose's buffering mechanism for all connections and models
    • -
    • 'useCreateIndex': false by default. Set to true to make Mongoose's default index build use createIndex() instead of ensureIndex() to avoid deprecation warnings from the MongoDB driver.
    • -
    • 'useFindAndModify': true by default. Set to false to make findOneAndUpdate() and findOneAndRemove() use native findOneAndUpdate() rather than findAndModify().
    • -
    • 'useNewUrlParser': false by default. Set to true to make all connections set the useNewUrlParser option by default
    • -
    • 'cloneSchemas': false by default. Set to true to clone() all schemas before compiling into a model.
    • -
    • '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.
    • -
    • '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.
    • -
    • 'runValidators': false by default. Set to true to enable update validators for all validators by default.
    • -
    • 'toObject': { transform: true, flattenDecimals: true } by default. Overwrites default objects to toObject()
    • -
    • 'toJSON': { transform: true, flattenDecimals: true } by default. Overwrites default objects to toJSON(), for determining how Mongoose documents get serialized by JSON.stringify()
    • -
    • 'strict': true by default, may be false, true, or 'throw'. Sets the default strict mode for schemas.
    • -
    • '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.
    • -

    Mongoose.prototype.startSession()

    Parameters
    • [options] -«Object» see the mongodb driver options
      • [options.causalConsistency=true] -«Boolean» set to false to disable causal consistency
    • [callback] -«Function»
    Returns:
    • «Promise<ClientSession>» promise that resolves to a MongoDB driver ClientSession

    Requires MongoDB >= 3.6.0. Starts a MongoDB session for benefits like causal consistency, retryable writes, and transactions.

    - -

    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.


    Mongoose.prototype.version

    Type:
    • «property»

    The Mongoose version

    - -

    Example

    - -
    console.log(mongoose.version); // '5.x.x'
    \ No newline at end of file diff --git a/docs/api/mongooseerror.html b/docs/api/mongooseerror.html deleted file mode 100644 index 6f31d704ec6..00000000000 --- a/docs/api/mongooseerror.html +++ /dev/null @@ -1,84 +0,0 @@ -Mongoose v5.6.0:

    MongooseError


    MongooseError()

    Parameters
    • msg -«String» Error message

    MongooseError constructor. MongooseError is the base class for all Mongoose-specific errors.


    MongooseError.prototype.name

    Type:
    • «String»

    The name of the error. The name uniquely identifies this Mongoose error. The possible values are:

    - -
      -
    • MongooseError: general Mongoose error
    • -
    • CastError: Mongoose could not convert a value to the type defined in the schema path. May be in a ValidationError class' errors property.
    • -
    • DisconnectedError: This connection timed out in trying to reconnect to MongoDB and will not successfully reconnect to MongoDB unless you explicitly reconnect.
    • -
    • DivergentArrayError: You attempted to save() an array that was modified after you loaded it with a $elemMatch or similar projection
    • -
    • MissingSchemaError: You tried to access a model with mongoose.model() that was not defined
    • -
    • DocumentNotFoundError: The document you tried to save() was not found
    • -
    • ValidatorError: error from an individual schema path's validator
    • -
    • ValidationError: error returned from validate() or 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.
    • -
    • 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() to re-define a model that was already defined.
    • -
    • ParallelSaveError: Thrown when you call 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 is set to throw.
    • -
    • VersionError: Thrown when the document is out of sync
    • -
    \ No newline at end of file diff --git a/docs/api/query.html b/docs/api/query.html deleted file mode 100644 index 00338894dff..00000000000 --- a/docs/api/query.html +++ /dev/null @@ -1,1600 +0,0 @@ -Mongoose v5.6.0:

    Query


    Query()

    Parameters
    • [options] -«Object»
    • [model] -«Object»
    • [conditions] -«Object»
    • [collection] -«Object» Mongoose collection

    Query constructor used for building queries. You do not need to instantiate a Query directly. Instead use Model functions like Model.find().

    - -

    Example:

    - -
    const query = MyModel.find(); // `query` is an instance of `Query`
    -query.setOptions({ lean : true });
    -query.collection(MyModel.collection);
    -query.where('age').gte(21).exec(callback);
    -
    -// You can instantiate a query directly. There is no need to do
    -// this unless you're an advanced user with a very good reason to.
    -const query = new mongoose.Query();

    Query.prototype.$where()

    Parameters
    • js -«String|Function» javascript string or function
    Returns:
    • «Query» this

    Specifies a javascript function or expression to pass to MongoDBs query system.

    - -

    Example

    - -
    query.$where('this.comments.length === 10 || this.name.length === 5')
    -
    -// or
    -
    -query.$where(function () {
    -  return this.comments.length === 10 || this.name.length === 5;
    -})
    - -

    NOTE:

    - -

    Only use $where when you have a condition that cannot be met using other MongoDB operators like $lt. Be sure to read about all of its caveats before using.


    Query.prototype.Symbol.asyncIterator()

    Returns an asyncIterator for use with for/await/of loops This function only works for find() queries. You do not need to call this function explicitly, the JavaScript runtime will call it for you.

    - -

    Example

    - -
    for await (const doc of Model.aggregate([{ $sort: { name: 1 } }])) {
    -  console.log(doc.name);
    -}
    - -

    Node.js 10.x supports async iterators natively without any flags. You can enable async iterators in Node.js 8.x using the --harmony_async_iteration flag.

    - -

    Note: This function is not if Symbol.asyncIterator is undefined. If Symbol.asyncIterator is undefined, that means your Node.js version does not support async iterators.


    Query.prototype.all()

    Parameters
    • [path] -«String»
    • val -«Array»

    Specifies an $all query condition.

    - -

    When called with one argument, the most recent path passed to where() is used.

    - -

    Example:

    - -
    MyModel.find().where('pets').all(['dog', 'cat', 'ferret']);
    -// Equivalent:
    -MyModel.find().all('pets', ['dog', 'cat', 'ferret']);

    Query.prototype.and()

    Parameters
    • array -«Array» array of conditions
    Returns:
    • «Query» this

    Specifies arguments for a $and condition.

    - -

    Example

    - -
    query.and([{ color: 'green' }, { status: 'ok' }])

    Query.prototype.batchSize()

    Parameters
    • val -«Number»

    Specifies the batchSize option.

    - -

    Example

    - -
    query.batchSize(100)
    - -

    Note

    - -

    Cannot be used with distinct()


    Query.prototype.box()

    Parameters
    • val -«Object»
    • Upper -«[Array]» Right Coords
    Returns:
    • «Query» this

    Specifies a $box condition

    - -

    Example

    - -
    var lowerLeft = [40.73083, -73.99756]
    -var upperRight= [40.741404,  -73.988135]
    -
    -query.where('loc').within().box(lowerLeft, upperRight)
    -query.box({ ll : lowerLeft, ur : upperRight })

    Query.prototype.cast()

    Parameters
    • [model] -«Model» the model to cast to. If not set, defaults to this.model
    • [obj] -«Object»
    Returns:
    • «Object»

    Casts this query to the schema of model

    - -

    Note

    - -

    If obj is present, it is cast instead of this query.


    Query.prototype.catch()

    Parameters
    • [reject] -«Function»
    Returns:
    • «Promise»

    Executes the query returning a Promise which will be resolved with either the doc(s) or rejected with the error. Like .then(), but only takes a rejection handler.


    Query.prototype.center()

    DEPRECATED Alias for circle

    - -

    Deprecated. Use circle instead.


    Query.prototype.centerSphere()

    Parameters
    • [path] -«String»
    • val -«Object»
    Returns:
    • «Query» this

    DEPRECATED Specifies a $centerSphere condition

    - -

    Deprecated. Use circle instead.

    - -

    Example

    - -
    var area = { center: [50, 50], radius: 10 };
    -query.where('loc').within().centerSphere(area);

    Query.prototype.circle()

    Parameters
    • [path] -«String»
    • area -«Object»
    Returns:
    • «Query» this

    Specifies a $center or $centerSphere condition.

    - -

    Example

    - -
    var area = { center: [50, 50], radius: 10, unique: true }
    -query.where('loc').within().circle(area)
    -// alternatively
    -query.circle('loc', area);
    -
    -// spherical calculations
    -var area = { center: [50, 50], radius: 10, unique: true, spherical: true }
    -query.where('loc').within().circle(area)
    -// alternatively
    -query.circle('loc', area);

    Query.prototype.collation()

    Parameters
    • value -«Object»
    Returns:
    • «Query» this

    Adds a collation to this op (MongoDB 3.4 and up)


    Query.prototype.comment()

    Parameters
    • val -«String»

    Specifies the comment option.

    - -

    Example

    - -
    query.comment('login query')
    - -

    Note

    - -

    Cannot be used with distinct()


    Query.prototype.count()

    Parameters
    • [filter] -«Object» count documents that match this object
    • [callback] -«Function» optional params are (error, count)
    Returns:
    • «Query» this

    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 instead. Otherwise, use the countDocuments() function instead.

    - -

    Passing a callback executes the query.

    - -

    This function triggers the following middleware.

    - -
      -
    • count()
    • -
    - -

    Example:

    - -
    var countQuery = model.where({ 'color': 'black' }).count();
    -
    -query.count({ color: 'black' }).count(callback)
    -
    -query.count({ color: 'black' }, callback)
    -
    -query.where('color', 'black').count(function (err, count) {
    -  if (err) return handleError(err);
    -  console.log('there are %d kittens', count);
    -})

    Query.prototype.countDocuments()

    Parameters
    • [filter] -«Object» mongodb selector
    • [callback] -«Function» optional params are (error, count)
    Returns:
    • «Query» this

    Specifies this query as a countDocuments() query. Behaves like count(), except it always does a full collection scan when passed an empty filter {}.

    - -

    There are also minor differences in how countDocuments() handles $where and a couple geospatial operators. versus count().

    - -

    Passing a callback executes the query.

    - -

    This function triggers the following middleware.

    - -
      -
    • countDocuments()
    • -
    - -

    Example:

    - -
    const countQuery = model.where({ 'color': 'black' }).countDocuments();
    -
    -query.countDocuments({ color: 'black' }).count(callback);
    -
    -query.countDocuments({ color: 'black' }, callback);
    -
    -query.where('color', 'black').countDocuments(function(err, count) {
    -  if (err) return handleError(err);
    -  console.log('there are %d kittens', count);
    -});
    - -

    The countDocuments() function is similar to count(), but there are a few operators that countDocuments() does not support. Below are the operators that count() supports but countDocuments() does not, and the suggested replacement:

    - -

    Query.prototype.cursor()

    Parameters
    • [options] -«Object»
    Returns:
    • «QueryCursor»

    Returns a wrapper around a mongodb driver cursor. A QueryCursor exposes a Streams3 interface, as well as a .next() function.

    - -

    The .cursor() function triggers pre find hooks, but not post find hooks.

    - -

    Example

    - -
    // There are 2 ways to use a cursor. First, as a stream:
    -Thing.
    -  find({ name: /^hello/ }).
    -  cursor().
    -  on('data', function(doc) { console.log(doc); }).
    -  on('end', function() { console.log('Done!'); });
    -
    -// Or you can use `.next()` to manually get the next doc in the stream.
    -// `.next()` returns a promise, so you can use promises or callbacks.
    -var cursor = Thing.find({ name: /^hello/ }).cursor();
    -cursor.next(function(error, doc) {
    -  console.log(doc);
    -});
    -
    -// Because `.next()` returns a promise, you can use co
    -// to easily iterate through all documents without loading them
    -// all into memory.
    -co(function*() {
    -  const cursor = Thing.find({ name: /^hello/ }).cursor();
    -  for (let doc = yield cursor.next(); doc != null; doc = yield cursor.next()) {
    -    console.log(doc);
    -  }
    -});
    - -

    Valid options

    - -
      -
    • transform: optional function which accepts a mongoose document. The return value of the function will be emitted on data and returned by .next().
    • -

    Query.prototype.deleteMany()

    Parameters
    • [filter] -«Object|Query» mongodb selector
    • [callback] -«Function» optional params are (error, mongooseDeleteResult)
    Returns:
    • «Query» this

    Declare and/or execute this query as a deleteMany() operation. Works like remove, except it deletes every document that matches filter in the collection, regardless of the value of single.

    - -

    This function does not trigger any middleware

    - -

    Example

    - -
    Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }, callback)
    -Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }).then(next)
    - -

    This function calls the MongoDB driver's Collection#deleteMany() function. The returned promise resolves to an object that contains 3 properties:

    - -
      -
    • ok: 1 if no errors occurred
    • -
    • deletedCount: the number of documents deleted
    • -
    • n: the number of documents deleted. Equal to deletedCount.
    • -
    - -

    Example

    - -
    const res = await Character.deleteMany({ name: /Stark/, age: { $gte: 18 } });
    -// `0` if no docs matched the filter, number of docs deleted otherwise
    -res.deletedCount;

    Query.prototype.deleteOne()

    Parameters
    • [filter] -«Object|Query» mongodb selector
    • [callback] -«Function» optional params are (error, mongooseDeleteResult)
    Returns:
    • «Query» this

    Declare and/or execute this query as a deleteOne() operation. Works like remove, except it deletes at most one document regardless of the single option.

    - -

    This function does not trigger any middleware.

    - -

    Example

    - -
    Character.deleteOne({ name: 'Eddard Stark' }, callback);
    -Character.deleteOne({ name: 'Eddard Stark' }).then(next);
    - -

    This function calls the MongoDB driver's Collection#deleteOne() function. The returned promise resolves to an object that contains 3 properties:

    - -
      -
    • ok: 1 if no errors occurred
    • -
    • deletedCount: the number of documents deleted
    • -
    • n: the number of documents deleted. Equal to deletedCount.
    • -
    - -

    Example

    - -
    const res = await Character.deleteOne({ name: 'Eddard Stark' });
    -// `1` if MongoDB deleted a doc, `0` if no docs matched the filter `{ name: ... }`
    -res.deletedCount;

    Query.prototype.distinct()

    Parameters
    • [field] -«String»
    • [filter] -«Object|Query»
    • [callback] -«Function» optional params are (error, arr)
    Returns:
    • «Query» this

    Declares or executes a distinct() operation.

    - -

    Passing a callback executes the query.

    - -

    This function does not trigger any middleware.

    - -

    Example

    - -
    distinct(field, conditions, callback)
    -distinct(field, conditions)
    -distinct(field, callback)
    -distinct(field)
    -distinct(callback)
    -distinct()

    Query.prototype.elemMatch()

    Parameters
    • path -«String|Object|Function»
    • filter -«Object|Function»
    Returns:
    • «Query» this

    Specifies an $elemMatch condition

    - -

    Example

    - -
    query.elemMatch('comment', { author: 'autobot', votes: {$gte: 5}})
    -
    -query.where('comment').elemMatch({ author: 'autobot', votes: {$gte: 5}})
    -
    -query.elemMatch('comment', function (elem) {
    -  elem.where('author').equals('autobot');
    -  elem.where('votes').gte(5);
    -})
    -
    -query.where('comment').elemMatch(function (elem) {
    -  elem.where({ author: 'autobot' });
    -  elem.where('votes').gte(5);
    -})

    Query.prototype.equals()

    Parameters
    • val -«Object»
    Returns:
    • «Query» this

    Specifies the complementary comparison value for paths specified with where()

    - -

    Example

    - -
    User.where('age').equals(49);
    -
    -// is the same as
    -
    -User.where('age', 49);

    Query.prototype.error()

    Parameters
    • err -«Error|null» if set, exec() will fail fast before sending the query to MongoDB
    Returns:
    • «Query» this

    Gets/sets the error flag on this query. If this flag is not null or undefined, the exec() promise will reject without executing.

    - -

    Example:

    - -
    Query().error(); // Get current error value
    -Query().error(null); // Unset the current error
    -Query().error(new Error('test')); // `exec()` will resolve with test
    -Schema.pre('find', function() {
    -  if (!this.getQuery().userId) {
    -    this.error(new Error('Not allowed to query without setting userId'));
    -  }
    -});
    - -

    Note that query casting runs after hooks, so cast errors will override custom errors.

    - -

    Example:

    - -
    var TestSchema = new Schema({ num: Number });
    -var TestModel = db.model('Test', TestSchema);
    -TestModel.find({ num: 'not a number' }).error(new Error('woops')).exec(function(error) {
    -  // `error` will be a cast error because `num` failed to cast
    -});

    Query.prototype.estimatedDocumentCount()

    Parameters
    • [options] -«Object» passed transparently to the MongoDB driver
    • [callback] -«Function» optional params are (error, count)
    Returns:
    • «Query» this

    Specifies this query as a estimatedDocumentCount() query. Faster than using countDocuments() for large collections because estimatedDocumentCount() uses collection metadata rather than scanning the entire collection.

    - -

    estimatedDocumentCount() does not accept a filter. Model.find({ foo: bar }).estimatedDocumentCount() is equivalent to Model.find().estimatedDocumentCount()

    - -

    This function triggers the following middleware.

    - -
      -
    • estimatedDocumentCount()
    • -
    - -

    Example:

    - -
    await Model.find().estimatedDocumentCount();

    Query.prototype.exec()

    Parameters
    • [operation] -«String|Function»
    • [callback] -«Function» optional params depend on the function being called
    Returns:
    • «Promise»

    Executes the query

    - -

    Examples:

    - -
    var promise = query.exec();
    -var promise = query.exec('update');
    -
    -query.exec(callback);
    -query.exec('find', callback);

    Query.prototype.exists()

    Parameters
    • [path] -«String»
    • val -«Number»
    Returns:
    • «Query» this

    Specifies an $exists condition

    - -

    Example

    - -
    // { name: { $exists: true }}
    -Thing.where('name').exists()
    -Thing.where('name').exists(true)
    -Thing.find().exists('name')
    -
    -// { name: { $exists: false }}
    -Thing.where('name').exists(false);
    -Thing.find().exists('name', false);

    Query.prototype.explain()

    Parameters
    • [verbose] -«String» The verbosity mode. Either 'queryPlanner', 'executionStats', or 'allPlansExecution'. The default is 'queryPlanner'
    Returns:
    • «Query» this

    Sets the explain option, which makes this query return detailed execution stats instead of the actual query result. This method is useful for determining what index your queries use.

    - -

    Calling query.explain(v) is equivalent to query.setOption({ explain: v })

    - -

    Example:

    - -
    const query = new Query();
    -const res = await query.find({ a: 1 }).explain('queryPlanner');
    -console.log(res);

    Query.prototype.find()

    Parameters
    • [filter] -«Object» mongodb selector. If not specified, returns all documents.
    • [callback] -«Function»
    Returns:
    • «Query» this

    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()

    - -

    Example

    - -
    // Using async/await
    -const arr = await Movie.find({ year: { $gte: 1980, $lte: 1989 } });
    -
    -// Using callbacks
    -Movie.find({ year: { $gte: 1980, $lte: 1989 } }, function(err, arr) {});

    Query.prototype.findOne()

    Parameters
    • [filter] -«Object» mongodb selector
    • [projection] -«Object» optional fields to return
    • [options] -«Object» see setOptions()
    • [callback] -«Function» optional params are (error, document)
    Returns:
    • «Query» this

    Declares the query a findOne operation. When executed, the first found document is passed to the callback.

    - -

    Passing a callback executes the query. The result of the query is a single document.

    - -
      -
    • Note: conditions is optional, and if conditions is null or undefined, -mongoose will send an empty findOne command to MongoDB, which will return -an arbitrary document. If you're querying by _id, use Model.findById() -instead.
    • -
    - -

    This function triggers the following middleware.

    - -
      -
    • findOne()
    • -
    - -

    Example

    - -
    var query  = Kitten.where({ color: 'white' });
    -query.findOne(function (err, kitten) {
    -  if (err) return handleError(err);
    -  if (kitten) {
    -    // doc may be null if no document matched
    -  }
    -});

    Query.prototype.findOneAndDelete()

    Parameters
    • [conditions] -«Object»
    • [options] -«Object»
    • [callback] -«Function» optional params are (error, document)
    Returns:
    • «Query» this

    Issues a MongoDB findOneAndDelete command.

    - -

    Finds a matching document, removes it, and passes the found document (if any) to the callback. Executes if callback is passed.

    - -

    This function triggers the following middleware.

    - -
      -
    • findOneAndDelete()
    • -
    - -

    This function differs slightly from Model.findOneAndRemove() in that findOneAndRemove() becomes a MongoDB findAndModify() command, as opposed to a findOneAndDelete() command. For most mongoose use cases, this distinction is purely pedantic. You should use findOneAndDelete() unless you have a good reason not to.

    - -

    Available options

    - -
      -
    • 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
    • -
    • rawResult: if true, resolves to the raw result from the MongoDB driver
    • -
    - -

    Callback Signature

    - -
    function(error, doc) {
    -  // error: any errors that occurred
    -  // doc: the document before updates are applied if `new: false`, or after updates if `new = true`
    -}
    - -

    Examples

    - -
    A.where().findOneAndDelete(conditions, options, callback) // executes
    -A.where().findOneAndDelete(conditions, options)  // return Query
    -A.where().findOneAndDelete(conditions, callback) // executes
    -A.where().findOneAndDelete(conditions) // returns Query
    -A.where().findOneAndDelete(callback)   // executes
    -A.where().findOneAndDelete()           // returns Query

    Query.prototype.findOneAndRemove()

    Parameters
    • [conditions] -«Object»
    • [options] -«Object»
    • [callback] -«Function» optional params are (error, document)
    Returns:
    • «Query» this

    Issues a mongodb findAndModify remove command.

    - -

    Finds a matching document, removes it, passing the found document (if any) to the callback. Executes if callback is passed.

    - -

    This function triggers the following middleware.

    - -
      -
    • findOneAndRemove()
    • -
    - -

    Available options

    - -
      -
    • 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
    • -
    • rawResult: if true, resolves to the raw result from the MongoDB driver
    • -
    - -

    Callback Signature

    - -
    function(error, doc) {
    -  // error: any errors that occurred
    -  // doc: the document before updates are applied if `new: false`, or after updates if `new = true`
    -}
    - -

    Examples

    - -
    A.where().findOneAndRemove(conditions, options, callback) // executes
    -A.where().findOneAndRemove(conditions, options)  // return Query
    -A.where().findOneAndRemove(conditions, callback) // executes
    -A.where().findOneAndRemove(conditions) // returns Query
    -A.where().findOneAndRemove(callback)   // executes
    -A.where().findOneAndRemove()           // returns Query

    Query.prototype.findOneAndReplace()

    Parameters
    • [filter] -«Object»
    • [replacement] -«Object»
    • [options] -«Object»
      • [options.lean] -«Object» if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See Query.lean().
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [callback] -«Function» optional params are (error, document)
    Returns:
    • «Query» this

    Issues a MongoDB findOneAndReplace command.

    - -

    Finds a matching document, removes it, and passes the found document (if any) to the callback. Executes if callback is passed.

    - -

    This function triggers the following middleware.

    - -
      -
    • findOneAndReplace()
    • -
    - -

    Available options

    - -
      -
    • 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
    • -
    • rawResult: if true, resolves to the raw result from the MongoDB driver
    • -
    - -

    Callback Signature

    - -
    function(error, doc) {
    -  // error: any errors that occurred
    -  // doc: the document before updates are applied if `new: false`, or after updates if `new = true`
    -}
    - -

    Examples

    - -
    A.where().findOneAndReplace(filter, replacement, options, callback); // executes
    -A.where().findOneAndReplace(filter, replacement, options); // return Query
    -A.where().findOneAndReplace(filter, replacement, callback); // executes
    -A.where().findOneAndReplace(filter); // returns Query
    -A.where().findOneAndReplace(callback); // executes
    -A.where().findOneAndReplace(); // returns Query

    Query.prototype.findOneAndUpdate()

    Parameters
    • [filter] -«Object|Query»
    • [doc] -«Object»
    • [options] -«Object»
      • [options.multipleCastError] -«Boolean» by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
      • [options.lean] -«Object» if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See Query.lean().
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • [callback] -«Function» optional params are (error, doc), unless rawResult is used, in which case params are (error, writeOpResult)
    Returns:
    • «Query» this

    Issues a mongodb findAndModify update command.

    - -

    Finds a matching document, updates it according to the update arg, passing any options, and returns the found document (if any) to the callback. The query executes if callback is passed.

    - -

    This function triggers the following middleware.

    - -
      -
    • findOneAndUpdate()
    • -
    - -

    Available options

    - -
      -
    • new: bool - if true, return the modified document rather than the original. defaults to false (changed in 4.0)
    • -
    • upsert: bool - creates the object if it doesn't exist. defaults to false.
    • -
    • 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 on this command. Update validators validate the update operation against the model's schema.
    • -
    • setDefaultsOnInsert: if this and upsert are true, mongoose will apply the defaults specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on MongoDB's $setOnInsert operator.
    • -
    • rawResult: if true, returns the raw result from the MongoDB driver
    • -
    • context (string) if set to 'query' and runValidators is on, this will refer to the query in custom validator functions that update validation runs. Does nothing if runValidators is false.
    • -
    - -

    Callback Signature

    - -
    function(error, doc) {
    -  // error: any errors that occurred
    -  // doc: the document before updates are applied if `new: false`, or after updates if `new = true`
    -}
    - -

    Examples

    - -
    query.findOneAndUpdate(conditions, update, options, callback) // executes
    -query.findOneAndUpdate(conditions, update, options)  // returns Query
    -query.findOneAndUpdate(conditions, update, callback) // executes
    -query.findOneAndUpdate(conditions, update)           // returns Query
    -query.findOneAndUpdate(update, callback)             // returns Query
    -query.findOneAndUpdate(update)                       // returns Query
    -query.findOneAndUpdate(callback)                     // executes
    -query.findOneAndUpdate()                             // returns Query

    Query.prototype.geometry()

    Parameters
    • object -«Object» Must contain a type property which is a String and a coordinates property which is an Array. See the examples.
    Returns:
    • «Query» this

    Specifies a $geometry condition

    - -

    Example

    - -
    var polyA = [[[ 10, 20 ], [ 10, 40 ], [ 30, 40 ], [ 30, 20 ]]]
    -query.where('loc').within().geometry({ type: 'Polygon', coordinates: polyA })
    -
    -// or
    -var polyB = [[ 0, 0 ], [ 1, 1 ]]
    -query.where('loc').within().geometry({ type: 'LineString', coordinates: polyB })
    -
    -// or
    -var polyC = [ 0, 0 ]
    -query.where('loc').within().geometry({ type: 'Point', coordinates: polyC })
    -
    -// or
    -query.where('loc').intersects().geometry({ type: 'Point', coordinates: polyC })
    - -

    The argument is assigned to the most recent path passed to where().

    - -

    NOTE:

    - -

    geometry() must come after either intersects() or within().

    - -

    The object argument must contain type and coordinates properties. - type {String} - coordinates {Array}


    Query.prototype.getFilter()

    Returns:
    • «Object» current query filter

    Returns the current query filter (also known as conditions) as a POJO.

    - -

    Example:

    - -
    const query = new Query();
    -query.find({ a: 1 }).where('b').gt(2);
    -query.getFilter(); // { a: 1, b: { $gt: 2 } }

    Query.prototype.getOptions()

    Returns:
    • «Object» the options

    Gets query options.

    - -

    Example:

    - -
    var query = new Query();
    -query.limit(10);
    -query.setOptions({ maxTimeMS: 1000 })
    -query.getOptions(); // { limit: 10, maxTimeMS: 1000 }

    Query.prototype.getPopulatedPaths()

    Returns:
    • «Array» an array of strings representing populated paths

    Gets a list of paths to be populated by this query

    - -

    Example:

    - -
    bookSchema.pre('findOne', function() {
    -   let keys = this.getPopulatedPaths(); // ['author']
    - });
    - ...
    - Book.findOne({}).populate('author');
    - -

    Example:

    - -
    // Deep populate
    - const q = L1.find().populate({
    -   path: 'level2',
    -   populate: { path: 'level3' }
    - });
    - q.getPopulatedPaths(); // ['level2', 'level2.level3']

    Query.prototype.getQuery()

    Returns:
    • «Object» current query filter

    Returns the current query filter. Equivalent to getFilter().

    - -

    You should use getFilter() instead of getQuery() where possible. getQuery() will likely be deprecated in a future release.

    - -

    Example:

    - -
    var query = new Query();
    -query.find({ a: 1 }).where('b').gt(2);
    -query.getQuery(); // { a: 1, b: { $gt: 2 } }

    Query.prototype.getUpdate()

    Returns:
    • «Object» current update operations

    Returns the current update operations as a JSON object.

    - -

    Example:

    - -
    var query = new Query();
    -query.update({}, { $set: { a: 5 } });
    -query.getUpdate(); // { $set: { a: 5 } }

    Query.prototype.gt()

    Parameters
    • [path] -«String»
    • val -«Number»

    Specifies a $gt query condition.

    - -

    When called with one argument, the most recent path passed to where() is used.

    - -

    Example

    - -
    Thing.find().where('age').gt(21)
    -
    -// or
    -Thing.find().gt('age', 21)

    Query.prototype.gte()

    Parameters
    • [path] -«String»
    • val -«Number»

    Specifies a $gte query condition.

    - -

    When called with one argument, the most recent path passed to where() is used.


    Query.prototype.hint()

    Parameters
    • val -«Object» a hint object
    Returns:
    • «Query» this

    Sets query hints.

    - -

    Example

    - -
    query.hint({ indexA: 1, indexB: -1})
    - -

    Note

    - -

    Cannot be used with distinct()


    Query.prototype.in()

    Parameters
    • [path] -«String»
    • val -«Number»

    Specifies an $in query condition.

    - -

    When called with one argument, the most recent path passed to where() is used.


    Query.prototype.intersects()

    Parameters
    • [arg] -«Object»
    Returns:
    • «Query» this

    Declares an intersects query for geometry().

    - -

    Example

    - -
    query.where('path').intersects().geometry({
    -    type: 'LineString'
    -  , coordinates: [[180.0, 11.0], [180, 9.0]]
    -})
    -
    -query.where('path').intersects({
    -    type: 'LineString'
    -  , coordinates: [[180.0, 11.0], [180, 9.0]]
    -})
    - -

    NOTE:

    - -

    MUST be used after where().

    - -

    NOTE:

    - -

    In Mongoose 3.7, intersects changed from a getter to a function. If you need the old syntax, use this.


    Query.prototype.j()

    Parameters
    • val -«boolean»
    Returns:
    • «Query» this

    Requests acknowledgement that this operation has been persisted to MongoDB's on-disk journal.

    - -

    This option is only valid for operations that write to the database

    - -
      -
    • deleteOne()
    • -
    • deleteMany()
    • -
    • findOneAndDelete()
    • -
    • findOneAndReplace()
    • -
    • findOneAndUpdate()
    • -
    • remove()
    • -
    • update()
    • -
    • updateOne()
    • -
    • updateMany()
    • -
    - -

    Defaults to the schema's writeConcern.j option

    - -

    Example:

    - -
    await mongoose.model('Person').deleteOne({ name: 'Ned Stark' }).j(true);

    Query.prototype.lean()

    Parameters
    • bool -«Boolean|Object» defaults to true
    Returns:
    • «Query» this

    Sets the lean option.

    - -

    Documents returned from queries with the lean option enabled are plain javascript objects, not Mongoose Documents. They have no save method, getters/setters, virtuals, or other Mongoose features.

    - -

    Example:

    - -
    new Query().lean() // true
    -new Query().lean(true)
    -new Query().lean(false)
    -
    -const docs = await Model.find().lean();
    -docs[0] instanceof mongoose.Document; // false
    - -

    Lean is great for high-performance, read-only cases, especially when combined with cursors.


    Query.prototype.limit()

    Parameters
    • val -«Number»

    Specifies the maximum number of documents the query will return.

    - -

    Example

    - -
    query.limit(20)
    - -

    Note

    - -

    Cannot be used with distinct()


    Query.prototype.lt()

    Parameters
    • [path] -«String»
    • val -«Number»

    Specifies a $lt query condition.

    - -

    When called with one argument, the most recent path passed to where() is used.


    Query.prototype.lte()

    Parameters
    • [path] -«String»
    • val -«Number»

    Specifies a $lte query condition.

    - -

    When called with one argument, the most recent path passed to where() is used.


    Query.prototype.map()

    Parameters
    • fn -«Function» function to run to transform the query result
    Returns:
    • «Query» this

    Runs a function fn and treats the return value of fn as the new value for the query to resolve to.

    - -

    Any functions you pass to map() will run after any post hooks.

    - -

    Example:

    - -
    const res = await MyModel.findOne().map(res => {
    -  // Sets a `loadedAt` property on the doc that tells you the time the
    -  // document was loaded.
    -  return res == null ?
    -    res :
    -    Object.assign(res, { loadedAt: new Date() });
    -});

    Query.prototype.maxDistance()

    Parameters
    • [path] -«String»
    • val -«Number»

    Specifies a maxDistance query condition.

    - -

    When called with one argument, the most recent path passed to where() is used.


    Query.prototype.maxScan()

    Parameters
    • val -«Number»

    Specifies the maxScan option.

    - -

    Example

    - -
    query.maxScan(100)
    - -

    Note

    - -

    Cannot be used with distinct()


    Query.prototype.maxTimeMS()

    Parameters
    • [ms] -«Number» The number of milliseconds
    Returns:
    • «Query» this

    Sets the maxTimeMS option. This will tell the MongoDB server to abort if the query or write op has been running for more than ms milliseconds.

    - -

    Calling query.maxTimeMS(v) is equivalent to query.setOption({ maxTimeMS: v })

    - -

    Example:

    - -
    const query = new Query();
    -// Throws an error 'operation exceeded time limit' as long as there's
    -// >= 1 doc in the queried collection
    -const res = await query.find({ $where: 'sleep(1000) || true' }).maxTimeMS(100);

    Query.prototype.maxscan()

    DEPRECATED Alias of maxScan


    Query.prototype.merge()

    Parameters
    • source -«Query|Object»
    Returns:
    • «Query» this

    Merges another Query or conditions object into this one.

    - -

    When a Query is passed, conditions, field selection and options are merged.


    Query.prototype.mod()

    Parameters
    • [path] -«String»
    • val -«Array» must be of length 2, first element is divisor, 2nd element is remainder.
    Returns:
    • «Query» this

    Specifies a $mod condition, filters documents for documents whose path property is a number that is equal to remainder modulo divisor.

    - -

    Example

    - -
    // All find products whose inventory is odd
    -Product.find().mod('inventory', [2, 1]);
    -Product.find().where('inventory').mod([2, 1]);
    -// This syntax is a little strange, but supported.
    -Product.find().where('inventory').mod(2, 1);

    Query.prototype.mongooseOptions()

    Parameters
    • options -«Object» if specified, overwrites the current options
    Returns:
    • «Object» the options

    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()
    • -
    • lean: if truthy, Mongoose will not hydrate any documents that are returned from this query. See 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 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 for backwards compatibility, which means Mongoose will allow Model.find({ foo: 'bar' }) even if foo is not in the schema. See the strictQuery docs for more information.
    • -
    • useFindAndModify: used to work around the findAndModify() deprecation warning
    • -
    • omitUndefined: delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • -
    • nearSphere: use $nearSphere instead of near(). See the Query.prototype.nearSphere() docs
    • -
    - -

    Mongoose maintains a separate object for internal options because Mongoose sends Query.prototype.options to the MongoDB server, and the above options are not relevant for the MongoDB server.


    Query.prototype.ne()

    Parameters
    • [path] -«String»
    • val -«Number»

    Specifies a $ne query condition.

    - -

    When called with one argument, the most recent path passed to where() is used.


    Query.prototype.near()

    Parameters
    • [path] -«String»
    • val -«Object»
    Returns:
    • «Query» this

    Specifies a $near or $nearSphere condition

    - -

    These operators return documents sorted by distance.

    - -

    Example

    - -
    query.where('loc').near({ center: [10, 10] });
    -query.where('loc').near({ center: [10, 10], maxDistance: 5 });
    -query.where('loc').near({ center: [10, 10], maxDistance: 5, spherical: true });
    -query.near('loc', { center: [10, 10], maxDistance: 5 });

    Query.prototype.nearSphere()

    DEPRECATED Specifies a $nearSphere condition

    - -

    Example

    - -
    query.where('loc').nearSphere({ center: [10, 10], maxDistance: 5 });
    - -

    Deprecated. Use query.near() instead with the spherical option set to true.

    - -

    Example

    - -
    query.where('loc').near({ center: [10, 10], spherical: true });

    Query.prototype.nin()

    Parameters
    • [path] -«String»
    • val -«Number»

    Specifies an $nin query condition.

    - -

    When called with one argument, the most recent path passed to where() is used.


    Query.prototype.nor()

    Parameters
    • array -«Array» array of conditions
    Returns:
    • «Query» this

    Specifies arguments for a $nor condition.

    - -

    Example

    - -
    query.nor([{ color: 'green' }, { status: 'ok' }])

    Query.prototype.or()

    Parameters
    • array -«Array» array of conditions
    Returns:
    • «Query» this

    Specifies arguments for an $or condition.

    - -

    Example

    - -
    query.or([{ color: 'red' }, { status: 'emergency' }])

    Query.prototype.orFail()

    Parameters
    • [err] -«Function|Error» optional error to throw if no docs match filter. If not specified, orFail() will throw a DocumentNotFoundError
    Returns:
    • «Query» this

    Make this query throw an error if no documents match the given filter. This is handy for integrating with async/await, because orFail() saves you an extra if statement to check if no document was found.

    - -

    Example:

    - -
    // Throws if no doc returned
    -await Model.findOne({ foo: 'bar' }).orFail();
    -
    -// Throws if no document was updated
    -await Model.updateOne({ foo: 'bar' }, { name: 'test' }).orFail();
    -
    -// Throws "No docs found!" error if no docs match `{ foo: 'bar' }`
    -await Model.find({ foo: 'bar' }).orFail(new Error('No docs found!'));
    -
    -// Throws "Not found" error if no document was found
    -await Model.findOneAndUpdate({ foo: 'bar' }, { name: 'test' }).
    -  orFail(() => Error('Not found'));

    Query.prototype.polygon()

    Parameters
    • [path] -«String|Array»
      • [coordinatePairs...] -«Array|Object»
    Returns:
    • «Query» this

    Specifies a $polygon condition

    - -

    Example

    - -
    query.where('loc').within().polygon([10,20], [13, 25], [7,15])
    -query.polygon('loc', [10,20], [13, 25], [7,15])

    Query.prototype.populate()

    Parameters
    • path -«Object|String» either the path to populate or an object specifying all parameters
    • [select] -«Object|String» Field selection for the population query
    • [model] -«Model» The model you wish to use for population. If not specified, populate will look up the model by the name in the Schema's ref field.
    • [match] -«Object» Conditions for the population query
    • [options] -«Object» Options for the population query (sort, etc)
      • [options.path=null] -«String» The path to populate.
      • [options.retainNullValues=false] -«boolean» by default, Mongoose removes null and undefined values from populated arrays. Use this option to make populate() retain null and undefined array entries.
      • [options.getters=false] -«boolean» 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.
      • [options.clone=false] -«boolean» 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.
      • [options.match=null] -«Object|Function» Add an additional filter to the populate query. Can be a filter object containing MongoDB query syntax, or a function that returns a filter object.
    Returns:
    • «Query» this

    Specifies paths which should be populated with other documents.

    - -

    Example:

    - -
    Kitten.findOne().populate('owner').exec(function (err, kitten) {
    -  console.log(kitten.owner.name) // Max
    -})
    -
    -Kitten.find().populate({
    -  path: 'owner',
    -  select: 'name',
    -  match: { color: 'black' },
    -  options: { sort: { name: -1 } }
    -}).exec(function (err, kittens) {
    -  console.log(kittens[0].owner.name) // Zoopa
    -})
    -
    -// alternatively
    -Kitten.find().populate('owner', 'name', null, {sort: { name: -1 }}).exec(function (err, kittens) {
    -  console.log(kittens[0].owner.name) // Zoopa
    -})
    - -

    Paths are populated after the query executes and a response is received. A separate query is then executed for each path specified for population. After a response for each query has also been returned, the results are passed to the callback.


    Query.prototype.projection()

    Parameters
    • arg -«Object|null»
    Returns:
    • «Object» the current projection

    Get/set the current projection (AKA fields). Pass null to remove the current projection.

    - -

    Unlike projection(), the select() function modifies the current projection in place. This function overwrites the existing projection.

    - -

    Example:

    - -
    const q = Model.find();
    -q.projection(); // null
    -
    -q.select('a b');
    -q.projection(); // { a: 1, b: 1 }
    -
    -q.projection({ c: 1 });
    -q.projection(); // { c: 1 }
    -
    -q.projection(null);
    -q.projection(); // null

    Query.prototype.read()

    Parameters
    • pref -«String» one of the listed preference options or aliases
    • [tags] -«Array» optional tags for this query
    Returns:
    • «Query» this

    Determines the MongoDB nodes from which to read.

    - -

    Preferences:

    - -
    primary - (default) Read from primary only. Operations will produce an error if primary is unavailable. Cannot be combined with tags.
    -secondary            Read from secondary if available, otherwise error.
    -primaryPreferred     Read from primary if available, otherwise a secondary.
    -secondaryPreferred   Read from a secondary if available, otherwise read from the primary.
    -nearest              All operations read from among the nearest candidates, but unlike other modes, this option will include both the primary and all secondaries in the random selection.
    - -

    Aliases

    - -
    p   primary
    -pp  primaryPreferred
    -s   secondary
    -sp  secondaryPreferred
    -n   nearest
    - -

    Example:

    - -
    new Query().read('primary')
    -new Query().read('p')  // same as primary
    -
    -new Query().read('primaryPreferred')
    -new Query().read('pp') // same as primaryPreferred
    -
    -new Query().read('secondary')
    -new Query().read('s')  // same as secondary
    -
    -new Query().read('secondaryPreferred')
    -new Query().read('sp') // same as secondaryPreferred
    -
    -new Query().read('nearest')
    -new Query().read('n')  // same as nearest
    -
    -// read from secondaries with matching tags
    -new Query().read('s', [{ dc:'sf', s: 1 },{ dc:'ma', s: 2 }])
    - -

    Read more about how to use read preferrences here and here.


    Query.prototype.readConcern()

    Parameters
    • level -«String» one of the listed read concern level or their aliases
    Returns:
    • «Query» this

    Sets the readConcern option for the query.

    - -

    Example:

    - -
    new Query().readConcern('local')
    -new Query().readConcern('l')  // same as local
    -
    -new Query().readConcern('available')
    -new Query().readConcern('a')  // same as available
    -
    -new Query().readConcern('majority')
    -new Query().readConcern('m')  // same as majority
    -
    -new Query().readConcern('linearizable')
    -new Query().readConcern('lz') // same as linearizable
    -
    -new Query().readConcern('snapshot')
    -new Query().readConcern('s')  // same as snapshot
    - -

    Read Concern Level:

    - -
    local         MongoDB 3.2+ The query returns from the instance with no guarantee guarantee that the data has been written to a majority of the replica set members (i.e. may be rolled back).
    -available     MongoDB 3.6+ The query returns from the instance with no guarantee guarantee that the data has been written to a majority of the replica set members (i.e. may be rolled back).
    -majority      MongoDB 3.2+ The query returns the data that has been acknowledged by a majority of the replica set members. The documents returned by the read operation are durable, even in the event of failure.
    -linearizable  MongoDB 3.4+ The query returns data that reflects all successful majority-acknowledged writes that completed prior to the start of the read operation. The query may wait for concurrently executing writes to propagate to a majority of replica set members before returning results.
    -snapshot      MongoDB 4.0+ Only available for operations within multi-document transactions. Upon transaction commit with write concern "majority", the transaction operations are guaranteed to have read from a snapshot of majority-committed data.
    - -

    Aliases

    - -
    l   local
    -a   available
    -m   majority
    -lz  linearizable
    -s   snapshot
    - -

    Read more about how to use read concern here.


    Query.prototype.regex()

    Parameters
    • [path] -«String»
    • val -«String|RegExp»

    Specifies a $regex query condition.

    - -

    When called with one argument, the most recent path passed to where() is used.


    Query.prototype.remove()

    Parameters
    • [filter] -«Object|Query» mongodb selector
    • [callback] -«Function» optional params are (error, mongooseDeleteResult)
    Returns:
    • «Query» this

    Declare and/or execute this query as a remove() operation. remove() is deprecated, you should use deleteOne() or deleteMany() instead.

    - -

    This function does not trigger any middleware

    - -

    Example

    - -
    Character.remove({ name: /Stark/ }, callback);
    - -

    This function calls the MongoDB driver's Collection#remove() function. The returned promise resolves to an object that contains 3 properties:

    - -
      -
    • ok: 1 if no errors occurred
    • -
    • deletedCount: the number of documents deleted
    • -
    • n: the number of documents deleted. Equal to deletedCount.
    • -
    - -

    Example

    - -
    const res = await Character.remove({ name: /Stark/ });
    -// Number of docs deleted
    -res.deletedCount;
    - -

    Note

    - -

    Calling remove() creates a Mongoose query, and a query does not execute until you either pass a callback, call Query#then(), or call Query#exec().

    - -
    // not executed
    -const query = Character.remove({ name: /Stark/ });
    -
    -// executed
    -Character.remove({ name: /Stark/ }, callback);
    -Character.remove({ name: /Stark/ }).remove(callback);
    -
    -// executed without a callback
    -Character.exec();

    Query.prototype.replaceOne()

    Parameters
    • [filter] -«Object»
    • [doc] -«Object» the update command
    • [options] -«Object»
      • [options.multipleCastError] -«Boolean» by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
      • [options.upsert=false] -«Boolean» if true, and no documents found, insert a new document
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
      • [options.timestamps=null] -«Boolean» If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
    • [callback] -«Function» params are (error, writeOpResult)
    Returns:
    • «Query» this

    Declare and/or execute this query as a replaceOne() operation. Same as update(), except MongoDB will replace the existing document and will not accept any atomic operators ($set, etc.)

    - -

    Note replaceOne will not fire update middleware. Use pre('replaceOne') and post('replaceOne') instead.

    - -

    Example:

    - -
    const res = await Person.replaceOne({ _id: 24601 }, { name: 'Jean Valjean' });
    -res.n; // Number of documents matched
    -res.nModified; // Number of documents modified
    - -

    This function triggers the following middleware.

    - -
      -
    • replaceOne()
    • -

    Query.prototype.select()

    Parameters
    • arg -«Object|String»
    Returns:
    • «Query» this

    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.

    - -

    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 to exclude (which implies all other fields are included). The _id field is the only exception because MongoDB includes it by default.

    - -

    Example

    - -
    // include a and b, exclude other fields
    -query.select('a b');
    -
    -// exclude c and d, include other fields
    -query.select('-c -d');
    -
    -// Use `+` to override schema-level `select: false` without making the
    -// projection inclusive.
    -const schema = new Schema({
    -  foo: { type: String, select: false },
    -  bar: String
    -});
    -// ...
    -query.select('+foo'); // Override foo's `select: false` without excluding `bar`
    -
    -// or you may use object notation, useful when
    -// you have keys already prefixed with a "-"
    -query.select({ a: 1, b: 1 });
    -query.select({ c: 0, d: 0 });

    Query.prototype.selected()

    Returns:
    • «Boolean»

    Determines if field selection has been made.


    Query.prototype.selectedExclusively()

    Returns:
    • «Boolean»

    Determines if exclusive field selection has been made.

    - -
    query.selectedExclusively() // false
    -query.select('-name')
    -query.selectedExclusively() // true
    -query.selectedInclusively() // false

    Query.prototype.selectedInclusively()

    Returns:
    • «Boolean»

    Determines if inclusive field selection has been made.

    - -
    query.selectedInclusively() // false
    -query.select('name')
    -query.selectedInclusively() // true

    Query.prototype.session()

    Parameters
    • [session] -«ClientSession» from await conn.startSession()
    Returns:
    • «Query» this

    Sets the MongoDB session associated with this query. Sessions are how you mark a query as part of a transaction.

    - -

    Calling session(null) removes the session from this query.

    - -

    Example:

    - -
    const s = await mongoose.startSession();
    -await mongoose.model('Person').findOne({ name: 'Axl Rose' }).session(s);

    Query.prototype.set()

    Parameters
    • path -«String|Object» path or object of key/value pairs to set
    • [val] -«Any» the value to set
    Returns:
    • «Query» this

    Adds a $set to this query's update without changing the operation. This is useful for query middleware so you can add an update regardless of whether you use updateOne(), updateMany(), findOneAndUpdate(), etc.

    - -

    Example:

    - -
    // Updates `{ $set: { updatedAt: new Date() } }`
    -new Query().updateOne({}, {}).set('updatedAt', new Date());
    -new Query().updateMany({}, {}).set({ updatedAt: new Date() });

    Query.prototype.setOptions()

    Parameters
    • options -«Object»
    Returns:
    • «Query» this

    Sets query options. Some options only make sense for certain operations.

    - -

    Options:

    - -

    The following options are only for find():

    - - - -

    The following options are only for write operations: update(), updateOne(), updateMany(), replaceOne(), findOneAndUpdate(), and findByIdAndUpdate():

    - -
      -
    • upsert
    • -
    • writeConcern
    • -
    • timestamps: If timestamps is set in the schema, set this option to false to skip timestamps for that particular update. Has no effect if timestamps is not enabled in the schema options.
    • -
    • omitUndefined: delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
    • -
    - -

    The following options are only for find(), findOne(), findById(), findOneAndUpdate(), and findByIdAndUpdate():

    - - - -

    The following options are only for all operations except update(), updateOne(), updateMany(), remove(), deleteOne(), and deleteMany():

    - - - -

    The following options are for all operations

    - -

    Query.prototype.setQuery()

    Parameters
    • new -«Object» query conditions
    Returns:
    • «undefined»

    Sets the query conditions to the provided JSON object.

    - -

    Example:

    - -
    var query = new Query();
    -query.find({ a: 1 })
    -query.setQuery({ a: 2 });
    -query.getQuery(); // { a: 2 }

    Query.prototype.setUpdate()

    Parameters
    • new -«Object» update operation
    Returns:
    • «undefined»

    Sets the current update operation to new value.

    - -

    Example:

    - -
    var query = new Query();
    -query.update({}, { $set: { a: 5 } });
    -query.setUpdate({ $set: { b: 6 } });
    -query.getUpdate(); // { $set: { b: 6 } }

    Query.prototype.size()

    Parameters
    • [path] -«String»
    • val -«Number»

    Specifies a $size query condition.

    - -

    When called with one argument, the most recent path passed to where() is used.

    - -

    Example

    - -
    MyModel.where('tags').size(0).exec(function (err, docs) {
    -  if (err) return handleError(err);
    -
    -  assert(Array.isArray(docs));
    -  console.log('documents with 0 tags', docs);
    -})

    Query.prototype.skip()

    Parameters
    • val -«Number»

    Specifies the number of documents to skip.

    - -

    Example

    - -
    query.skip(100).limit(20)
    - -

    Note

    - -

    Cannot be used with distinct()


    Query.prototype.slaveOk()

    Parameters
    • v -«Boolean» defaults to true
    Returns:
    • «Query» this

    DEPRECATED Sets the slaveOk option.

    - -

    Deprecated in MongoDB 2.2 in favor of read preferences.

    - -

    Example:

    - -
    query.slaveOk() // true
    -query.slaveOk(true)
    -query.slaveOk(false)

    Query.prototype.slice()

    Parameters
    • [path] -«String»
    • val -«Number» number/range of elements to slice
    Returns:
    • «Query» this

    Specifies a $slice projection for an array.

    - -

    Example

    - -
    query.slice('comments', 5)
    -query.slice('comments', -5)
    -query.slice('comments', [10, 5])
    -query.where('comments').slice(5)
    -query.where('comments').slice([-10, 5])

    Query.prototype.snapshot()

    Returns:
    • «Query» this

    Specifies this query as a snapshot query.

    - -

    Example

    - -
    query.snapshot() // true
    -query.snapshot(true)
    -query.snapshot(false)
    - -

    Note

    - -

    Cannot be used with distinct()


    Query.prototype.sort()

    Parameters
    • arg -«Object|String»
    Returns:
    • «Query» this

    Sets the sort order

    - -

    If an object is passed, values allowed are asc, desc, ascending, descending, 1, and -1.

    - -

    If a string is passed, it must be a space delimited list of path names. The sort order of each path is ascending unless the path name is prefixed with - which will be treated as descending.

    - -

    Example

    - -
    // sort by "field" ascending and "test" descending
    -query.sort({ field: 'asc', test: -1 });
    -
    -// equivalent
    -query.sort('field -test');
    - -

    Note

    - -

    Cannot be used with distinct()


    Query.prototype.tailable()

    Parameters
    • bool -«Boolean» defaults to true
    • [opts] -«Object» options to set
      • [opts.numberOfRetries] -«Number» if cursor is exhausted, retry this many times before giving up
      • [opts.tailableRetryInterval] -«Number» if cursor is exhausted, wait this many milliseconds before retrying

    Sets the tailable option (for use with capped collections).

    - -

    Example

    - -
    query.tailable() // true
    -query.tailable(true)
    -query.tailable(false)
    - -

    Note

    - -

    Cannot be used with distinct()


    Query.prototype.then()

    Parameters
    • [resolve] -«Function»
    • [reject] -«Function»
    Returns:
    • «Promise»

    Executes the query returning a Promise which will be resolved with either the doc(s) or rejected with the error.


    Query.prototype.toConstructor()

    Returns:
    • «Query» subclass-of-Query

    Converts this query to a customized, reusable query constructor with all arguments and options retained.

    - -

    Example

    - -
    // Create a query for adventure movies and read from the primary
    -// node in the replica-set unless it is down, in which case we'll
    -// read from a secondary node.
    -var query = Movie.find({ tags: 'adventure' }).read('primaryPreferred');
    -
    -// create a custom Query constructor based off these settings
    -var Adventure = query.toConstructor();
    -
    -// Adventure is now a subclass of mongoose.Query and works the same way but with the
    -// default query parameters and options set.
    -Adventure().exec(callback)
    -
    -// further narrow down our query results while still using the previous settings
    -Adventure().where({ name: /^Life/ }).exec(callback);
    -
    -// since Adventure is a stand-alone constructor we can also add our own
    -// helper methods and getters without impacting global queries
    -Adventure.prototype.startsWith = function (prefix) {
    -  this.where({ name: new RegExp('^' + prefix) })
    -  return this;
    -}
    -Object.defineProperty(Adventure.prototype, 'highlyRated', {
    -  get: function () {
    -    this.where({ rating: { $gt: 4.5 }});
    -    return this;
    -  }
    -})
    -Adventure().highlyRated.startsWith('Life').exec(callback)

    Query.prototype.update()

    Parameters
    • [filter] -«Object»
    • [doc] -«Object» the update command
    • [options] -«Object»
      • [options.multipleCastError] -«Boolean» by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
      • [options.upsert=false] -«Boolean» if true, and no documents found, insert a new document
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
      • [options.timestamps=null] -«Boolean» If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
    • [callback] -«Function» params are (error, writeOpResult)
    Returns:
    • «Query» this

    Declare and/or execute this query as an update() operation.

    - -

    All paths passed that are not atomic operations will become $set ops.

    - -

    This function triggers the following middleware.

    - -
      -
    • update()
    • -
    - -

    Example

    - -
    Model.where({ _id: id }).update({ title: 'words' })
    -
    -// becomes
    -
    -Model.where({ _id: id }).update({ $set: { title: 'words' }})
    - -

    Valid options:

    - -
      -
    • upsert (boolean) whether to create the doc if it doesn't match (false)
    • -
    • multi (boolean) whether multiple documents should be updated (false)
    • -
    • runValidators: if true, runs update validators on this command. Update validators validate the update operation against the model's schema.
    • -
    • setDefaultsOnInsert: if this and upsert are true, mongoose will apply the defaults specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on MongoDB's $setOnInsert operator.
    • -
    • strict (boolean) overrides the strict option for this update
    • -
    • overwrite (boolean) disables update-only mode, allowing you to overwrite the doc (false)
    • -
    • context (string) if set to 'query' and runValidators is on, this will refer to the query in custom validator functions that update validation runs. Does nothing if runValidators is false.
    • -
    • read
    • -
    • writeConcern
    • -
    - -

    Note

    - -

    Passing an empty object {} as the doc will result in a no-op unless the overwrite option is passed. Without the overwrite option set, the update operation will be ignored and the callback executed without sending the command to MongoDB so as to prevent accidently overwritting documents in the collection.

    - -

    Note

    - -

    The operation is only executed when a callback is passed. To force execution without a callback, we must first call update() and then execute it by using the exec() method.

    - -
    var q = Model.where({ _id: id });
    -q.update({ $set: { name: 'bob' }}).update(); // not executed
    -
    -q.update({ $set: { name: 'bob' }}).exec(); // executed
    -
    -// keys that are not [atomic](https://docs.mongodb.com/manual/tutorial/model-data-for-atomic-operations/#pattern) ops become `$set`.
    -// this executes the same command as the previous example.
    -q.update({ name: 'bob' }).exec();
    -
    -// overwriting with empty docs
    -var q = Model.where({ _id: id }).setOptions({ overwrite: true })
    -q.update({ }, callback); // executes
    -
    -// multi update with overwrite to empty doc
    -var q = Model.where({ _id: id });
    -q.setOptions({ multi: true, overwrite: true })
    -q.update({ });
    -q.update(callback); // executed
    -
    -// multi updates
    -Model.where()
    -     .update({ name: /^match/ }, { $set: { arr: [] }}, { multi: true }, callback)
    -
    -// more multi updates
    -Model.where()
    -     .setOptions({ multi: true })
    -     .update({ $set: { arr: [] }}, callback)
    -
    -// single update by default
    -Model.where({ email: 'address@example.com' })
    -     .update({ $inc: { counter: 1 }}, callback)
    -
    - -

    API summary

    - -
    update(filter, doc, options, cb) // executes
    -update(filter, doc, options)
    -update(filter, doc, cb) // executes
    -update(filter, doc)
    -update(doc, cb) // executes
    -update(doc)
    -update(cb) // executes
    -update(true) // executes
    -update()

    Query.prototype.updateMany()

    Parameters
    • [filter] -«Object»
    • [doc] -«Object» the update command
    • [options] -«Object»
      • [options.multipleCastError] -«Boolean» by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
      • [options.upsert=false] -«Boolean» if true, and no documents found, insert a new document
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
      • [options.timestamps=null] -«Boolean» If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
    • [callback] -«Function» params are (error, writeOpResult)
    Returns:
    • «Query» this

    Declare and/or execute this query as an updateMany() operation. Same as update(), except MongoDB will update all documents that match filter (as opposed to just the first one) regardless of the value of the multi option.

    - -

    Note updateMany will not fire update middleware. Use pre('updateMany') and post('updateMany') instead.

    - -

    Example:

    - -
    const res = await Person.updateMany({ name: /Stark$/ }, { isDeleted: true });
    -res.n; // Number of documents matched
    -res.nModified; // Number of documents modified
    - -

    This function triggers the following middleware.

    - -
      -
    • updateMany()
    • -

    Query.prototype.updateOne()

    Parameters
    • [filter] -«Object»
    • [doc] -«Object» the update command
    • [options] -«Object»
      • [options.multipleCastError] -«Boolean» by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
      • [options.upsert=false] -«Boolean» if true, and no documents found, insert a new document
      • [options.omitUndefined=false] -«Boolean» If true, delete any properties whose value is undefined when casting an update. In other words, if this is set, Mongoose will delete baz from the update in Model.updateOne({}, { foo: 'bar', baz: undefined }) before sending the update to the server.
      • [options.timestamps=null] -«Boolean» If set to false and schema-level timestamps are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
    • [callback] -«Function» params are (error, writeOpResult)
    Returns:
    • «Query» this

    Declare and/or execute this query as an updateOne() operation. Same as update(), except it does not support the multi or overwrite options.

    - -
      -
    • MongoDB will update only the first document that matches filter regardless of the value of the multi option.
    • -
    • Use replaceOne() if you want to overwrite an entire document rather than using atomic operators like $set.
    • -
    - -

    Note updateOne will not fire update middleware. Use pre('updateOne') and post('updateOne') instead.

    - -

    Example:

    - -
    const res = await Person.updateOne({ name: 'Jean-Luc Picard' }, { ship: 'USS Enterprise' });
    -res.n; // Number of documents matched
    -res.nModified; // Number of documents modified
    - -

    This function triggers the following middleware.

    - -
      -
    • updateOne()
    • -

    Query.prototype.use$geoWithin

    Type:
    • «property»

    Flag to opt out of using $geoWithin.

    - -
    mongoose.Query.use$geoWithin = false;
    - -

    MongoDB 2.4 deprecated the use of $within, replacing it with $geoWithin. Mongoose uses $geoWithin by default (which is 100% backward compatible with $within). If you are running an older version of MongoDB, set this flag to false so your within() queries continue to work.


    Query.prototype.w()

    Parameters
    • val -«String|number» 0 for fire-and-forget, 1 for acknowledged by one server, 'majority' for majority of the replica set, or any of the more advanced options.
    Returns:
    • «Query» this

    Sets the specified number of mongod servers, or tag set of mongod servers, that must acknowledge this write before this write is considered successful.

    - -

    This option is only valid for operations that write to the database

    - -
      -
    • deleteOne()
    • -
    • deleteMany()
    • -
    • findOneAndDelete()
    • -
    • findOneAndReplace()
    • -
    • findOneAndUpdate()
    • -
    • remove()
    • -
    • update()
    • -
    • updateOne()
    • -
    • updateMany()
    • -
    - -

    Defaults to the schema's writeConcern.w option

    - -

    Example:

    - -
    // The 'majority' option means the `deleteOne()` promise won't resolve
    -// until the `deleteOne()` has propagated to the majority of the replica set
    -await mongoose.model('Person').
    -  deleteOne({ name: 'Ned Stark' }).
    -  w('majority');

    Query.prototype.where()

    Parameters
    • [path] -«String|Object»
    • [val] -«any»
    Returns:
    • «Query» this

    Specifies a path for use with chaining.

    - -

    Example

    - -
    // instead of writing:
    -User.find({age: {$gte: 21, $lte: 65}}, callback);
    -
    -// we can instead write:
    -User.where('age').gte(21).lte(65);
    -
    -// passing query conditions is permitted
    -User.find().where({ name: 'vonderful' })
    -
    -// chaining
    -User
    -.where('age').gte(21).lte(65)
    -.where('name', /^vonderful/i)
    -.where('friends').slice(10)
    -.exec(callback)

    Query.prototype.within()

    Returns:
    • «Query» this

    Defines a $within or $geoWithin argument for geo-spatial queries.

    - -

    Example

    - -
    query.where(path).within().box()
    -query.where(path).within().circle()
    -query.where(path).within().geometry()
    -
    -query.where('loc').within({ center: [50,50], radius: 10, unique: true, spherical: true });
    -query.where('loc').within({ box: [[40.73, -73.9], [40.7, -73.988]] });
    -query.where('loc').within({ polygon: [[],[],[],[]] });
    -
    -query.where('loc').within([], [], []) // polygon
    -query.where('loc').within([], []) // box
    -query.where('loc').within({ type: 'LineString', coordinates: [...] }); // geometry
    - -

    MUST be used after where().

    - -

    NOTE:

    - -

    As of Mongoose 3.7, $geoWithin is always used for queries. To change this behavior, see Query.use$geoWithin.

    - -

    NOTE:

    - -

    In Mongoose 3.7, within changed from a getter to a function. If you need the old syntax, use this.


    Query.prototype.wtimeout()

    Parameters
    • ms -«number» number of milliseconds to wait
    Returns:
    • «Query» this

    If w > 1, 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

    - -
      -
    • deleteOne()
    • -
    • deleteMany()
    • -
    • findOneAndDelete()
    • -
    • findOneAndReplace()
    • -
    • findOneAndUpdate()
    • -
    • remove()
    • -
    • update()
    • -
    • updateOne()
    • -
    • updateMany()
    • -
    - -

    Defaults to the schema's writeConcern.wtimeout option

    - -

    Example:

    - -
    // The `deleteOne()` promise won't resolve until this `deleteOne()` has
    -// propagated to at least `w = 2` members of the replica set. If it takes
    -// longer than 1 second, this `deleteOne()` will fail.
    -await mongoose.model('Person').
    -  deleteOne({ name: 'Ned Stark' }).
    -  w(2).
    -  wtimeout(1000);
    \ No newline at end of file diff --git a/docs/api/querycursor.html b/docs/api/querycursor.html deleted file mode 100644 index 31aeb4dda1d..00000000000 --- a/docs/api/querycursor.html +++ /dev/null @@ -1,103 +0,0 @@ -Mongoose v5.6.0:

    QueryCursor


    QueryCursor()

    Parameters
    • query -«Query»
    • options -«Object» query options passed to .find()

    A QueryCursor is a concurrency primitive for processing query results one document at a time. A QueryCursor fulfills the Node.js streams3 API, in addition to several other mechanisms for loading documents from MongoDB one at a time.

    - -

    QueryCursors execute the model's pre find hooks, but not the model's post find hooks.

    - -

    Unless you're an advanced user, do not instantiate this class directly. Use Query#cursor() instead.


    QueryCursor.prototype.addCursorFlag()

    Parameters
    • flag -«String»
    • value -«Boolean»
    Returns:
    • «AggregationCursor» this

    Adds a cursor flag. Useful for setting the noCursorTimeout and tailable flags.


    QueryCursor.prototype.close()

    Parameters
    • callback -«Function»
    Returns:
    • «Promise»

    Marks this cursor as closed. Will stop streaming and subsequent calls to next() will error.


    QueryCursor.prototype.eachAsync()

    Parameters
    • fn -«Function»
    • [options] -«Object»
      • [options.parallel] -«Number» the number of promises to execute in parallel. Defaults to 1.
    • [callback] -«Function» executed when all docs have been processed
    Returns:
    • «Promise»

    Execute fn for every document in the cursor. If fn returns a promise, will wait for the promise to resolve before iterating on to the next one. Returns a promise that resolves when done.


    QueryCursor.prototype.map()

    Parameters
    • fn -«Function»
    Returns:
    • «QueryCursor»

    Registers a transform function which subsequently maps documents retrieved via the streams interface or .next()

    - -

    Example

    - -
    // Map documents returned by `data` events
    -Thing.
    -  find({ name: /^hello/ }).
    -  cursor().
    -  map(function (doc) {
    -   doc.foo = "bar";
    -   return doc;
    -  })
    -  on('data', function(doc) { console.log(doc.foo); });
    -
    -// Or map documents returned by `.next()`
    -var cursor = Thing.find({ name: /^hello/ }).
    -  cursor().
    -  map(function (doc) {
    -    doc.foo = "bar";
    -    return doc;
    -  });
    -cursor.next(function(error, doc) {
    -  console.log(doc.foo);
    -});

    QueryCursor.prototype.next()

    Parameters
    • callback -«Function»
    Returns:
    • «Promise»

    Get the next document from this cursor. Will return null when there are no documents left.

    \ No newline at end of file diff --git a/docs/api/schema.html b/docs/api/schema.html deleted file mode 100644 index 37ca7ed187f..00000000000 --- a/docs/api/schema.html +++ /dev/null @@ -1,426 +0,0 @@ -Mongoose v5.6.0:

    Schema


    Schema()

    Parameters
    • [definition] -«Object|Schema|Array» Can be one of: object describing schema paths, or schema to copy, or array of objects and schemas
    • [options] -«Object»

    Schema constructor.

    - -

    Example:

    - -
    var child = new Schema({ name: String });
    -var schema = new Schema({ name: String, age: Number, children: [child] });
    -var Tree = mongoose.model('Tree', schema);
    -
    -// setting schema options
    -new Schema({ name: String }, { _id: false, autoIndex: false })
    - -

    Options:

    - - - -

    Note:

    - -

    When nesting schemas, (children in the example above), always declare the child schema first before passing it into its parent.


    Schema.Types

    Type:
    • «property»

    The various built-in Mongoose Schema Types.

    - -

    Example:

    - -
    var mongoose = require('mongoose');
    -var ObjectId = mongoose.Schema.Types.ObjectId;
    - -

    Types:

    - - - -

    Using this exposed access to the Mixed SchemaType, we can use them in our schema.

    - -
    var Mixed = mongoose.Schema.Types.Mixed;
    -new mongoose.Schema({ _user: Mixed })

    Schema.indexTypes

    Type:
    • «property»

    The allowed index types


    Schema.prototype.add()

    Parameters
    • obj -«Object|Schema» plain object with paths to add, or another schema
    • [prefix] -«String» path to prefix the newly added paths with
    Returns:
    • «Schema» the Schema instance

    Adds key path / schema type pairs to this schema.

    - -

    Example:

    - -
    const ToySchema = new Schema();
    -ToySchema.add({ name: 'string', color: 'string', price: 'number' });
    -
    -const TurboManSchema = new Schema();
    -// You can also `add()` another schema and copy over all paths, virtuals,
    -// getters, setters, indexes, methods, and statics.
    -TurboManSchema.add(ToySchema).add({ year: Number });

    Schema.prototype.childSchemas

    Type:
    • «property»

    Array of child schemas (from document arrays and single nested subdocs) and their corresponding compiled models. Each element of the array is an object with 2 properties: schema and model.

    - -

    This property is typically only useful for plugin authors and advanced users. You do not need to interact with this property at all to use mongoose.


    Schema.prototype.clone()

    Returns:
    • «Schema» the cloned schema

    Returns a deep copy of the schema

    - -

    Example:

    - -
    const schema = new Schema({ name: String });
    -const clone = schema.clone();
    -clone === schema; // false
    -clone.path('name'); // SchemaString { ... }

    Schema.prototype.eachPath()

    Parameters
    • fn -«Function» callback function
    Returns:
    • «Schema» this

    Iterates the schemas paths similar to Array#forEach.

    - -

    The callback is passed the pathname and the schemaType instance.

    - -

    Example:

    - -
    const userSchema = new Schema({ name: String, registeredAt: Date });
    -userSchema.eachPath((pathname, schematype) => {
    -  // Prints twice:
    -  // name SchemaString { ... }
    -  // registeredAt SchemaDate { ... }
    -  console.log(pathname, schematype);
    -});

    Schema.prototype.get()

    Parameters
    • key -«String» option name
    Returns:
    • «Any» the option's value

    Gets a schema option.

    - -

    Example:

    - -
    schema.get('strict'); // true
    -schema.set('strict', false);
    -schema.get('strict'); // false

    Schema.prototype.index()

    Parameters
    • fields -«Object»
    • [options] -«Object» Options to pass to MongoDB driver's createIndex() function
      • [options.expires=null] -«String» Mongoose-specific syntactic sugar, uses ms to convert expires option into seconds for the expireAfterSeconds in the above link.

    Defines an index (most likely compound) for this schema.

    - -

    Example

    - -
    schema.index({ first: 1, last: -1 })

    Schema.prototype.indexes()

    Returns:
    • «Array» list of indexes defined in the schema

    Returns a list of indexes that this schema declares, via schema.index() or by index: true in a path's options.

    - -

    Example:

    - -
    const userSchema = new Schema({
    -  email: { type: String, required: true, unique: true },
    -  registeredAt: { type: Date, index: true }
    -});
    -
    -// [ [ { email: 1 }, { unique: true, background: true } ],
    -//   [ { registeredAt: 1 }, { background: true } ] ]
    -userSchema.indexes();

    Schema.prototype.loadClass()

    Parameters
    • model -«Function»
    • [virtualsOnly] -«Boolean» if truthy, only pulls virtuals from the class, not methods or statics

    Loads an ES6 class into a schema. Maps setters + getters, static methods, and instance methods to schema virtuals, statics, and methods.

    - -

    Example:

    - -
    const md5 = require('md5');
    -const userSchema = new Schema({ email: String });
    -class UserClass {
    -  // `gravatarImage` becomes a virtual
    -  get gravatarImage() {
    -    const hash = md5(this.email.toLowerCase());
    -    return `https://www.gravatar.com/avatar/${hash}`;
    -  }
    -
    -  // `getProfileUrl()` becomes a document method
    -  getProfileUrl() {
    -    return `https://mysite.com/${this.email}`;
    -  }
    -
    -  // `findByEmail()` becomes a static
    -  static findByEmail(email) {
    -    return this.findOne({ email });
    -  }
    -}
    -
    -// `schema` will now have a `gravatarImage` virtual, a `getProfileUrl()` method,
    -// and a `findByEmail()` static
    -userSchema.loadClass(UserClass);
    -

    Schema.prototype.method()

    Parameters
    • method -«String|Object» name
    • [fn] -«Function»

    Adds an instance method to documents constructed from Models compiled from this schema.

    - -

    Example

    - -
    var schema = kittySchema = new Schema(..);
    -
    -schema.method('meow', function () {
    -  console.log('meeeeeoooooooooooow');
    -})
    -
    -var Kitty = mongoose.model('Kitty', schema);
    -
    -var fizz = new Kitty;
    -fizz.meow(); // meeeeeooooooooooooow
    - -

    If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as methods.

    - -
    schema.method({
    -    purr: function () {}
    -  , scratch: function () {}
    -});
    -
    -// later
    -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


    Schema.prototype.obj

    Type:
    • «property»

    The original object passed to the schema constructor

    - -

    Example:

    - -
    var schema = new Schema({ a: String }).add({ b: String });
    -schema.obj; // { a: String }

    Schema.prototype.path()

    Parameters
    • path -«String»
    • constructor -«Object»

    Gets/sets schema paths.

    - -

    Sets a path (if arity 2) Gets a path (if arity 1)

    - -

    Example

    - -
    schema.path('name') // returns a SchemaType
    -schema.path('name', Number) // changes the schemaType of `name` to Number

    Schema.prototype.pathType()

    Parameters
    • path -«String»
    Returns:
    • «String»

    Returns the pathType of path for this schema.

    - -

    Given a path, returns whether it is a real, virtual, nested, or ad-hoc/undefined path.

    - -

    Example:

    - -
    const s = new Schema({ name: String, nested: { foo: String } });
    -s.virtual('foo').get(() => 42);
    -s.pathType('name'); // "real"
    -s.pathType('nested'); // "nested"
    -s.pathType('foo'); // "virtual"
    -s.pathType('fail'); // "adhocOrUndefined"

    Schema.prototype.plugin()

    Parameters
    • plugin -«Function» callback
    • [opts] -«Object»

    Registers a plugin for this schema.

    - -

    Example:

    - -
    const s = new Schema({ name: String });
    -s.plugin(schema => console.log(schema.path('name').path));
    -mongoose.model('Test', schema); // Prints 'name'

    Schema.prototype.post()

    Parameters
    • The -«String|RegExp» method name or regular expression to match method name
    • [options] -«Object»
      • [options.document] -«Boolean» If name is a hook for both document and query middleware, set to true to run on document middleware.
      • [options.query] -«Boolean» If name is a hook for both document and query middleware, set to true to run on query middleware.
    • fn -«Function» callback

    Defines a post hook for the document

    - -
    var schema = new Schema(..);
    -schema.post('save', function (doc) {
    -  console.log('this fired after a document was saved');
    -});
    -
    -schema.post('find', function(docs) {
    -  console.log('this fired after you ran a find query');
    -});
    -
    -schema.post(/Many$/, function(res) {
    -  console.log('this fired after you ran `updateMany()` or `deleteMany()`);
    -});
    -
    -var Model = mongoose.model('Model', schema);
    -
    -var m = new Model(..);
    -m.save(function(err) {
    -  console.log('this fires after the `post` hook');
    -});
    -
    -m.find(function(err, docs) {
    -  console.log('this fires after the post find hook');
    -});

    Schema.prototype.pre()

    Parameters
    • The -«String|RegExp» method name or regular expression to match method name
    • [options] -«Object»
      • [options.document] -«Boolean» If name is a hook for both document and query middleware, set to true to run on document middleware.
      • [options.query] -«Boolean» If name is a hook for both document and query middleware, set to true to run on query middleware.
    • callback -«Function»

    Defines a pre hook for the document.

    - -

    Example

    - -
    var toySchema = new Schema({ name: String, created: Date });
    -
    -toySchema.pre('save', function(next) {
    -  if (!this.created) this.created = new Date;
    -  next();
    -});
    -
    -toySchema.pre('validate', function(next) {
    -  if (this.name !== 'Woody') this.name = 'Woody';
    -  next();
    -});
    -
    -// Equivalent to calling `pre()` on `find`, `findOne`, `findOneAndUpdate`.
    -toySchema.pre(/^find/, function(next) {
    -  console.log(this.getFilter());
    -});

    Schema.prototype.queue()

    Parameters
    • name -«String» name of the document method to call later
    • args -«Array» arguments to pass to the method

    Adds a method call to the queue.

    - -

    Example:

    - -
    schema.methods.print = function() { console.log(this); };
    -schema.queue('print', []); // Print the doc every one is instantiated
    -
    -const Model = mongoose.model('Test', schema);
    -new Model({ name: 'test' }); // Prints '{"_id": ..., "name": "test" }'

    Schema.prototype.remove()

    Parameters
    • path -«String|Array»
    Returns:
    • «Schema» the Schema instance

    Removes the given path (or [paths]).

    - -

    Example:

    - -
    const schema = new Schema({ name: String, age: Number });
    -schema.remove('name');
    -schema.path('name'); // Undefined
    -schema.path('age'); // SchemaNumber { ... }

    Schema.prototype.requiredPaths()

    Parameters
    • invalidate -«Boolean» refresh the cache
    Returns:
    • «Array»

    Returns an Array of path strings that are required by this schema.

    - -

    Example:

    - -
    const s = new Schema({
    -  name: { type: String, required: true },
    -  age: { type: String, required: true },
    -  notes: String
    -});
    -s.requiredPaths(); // [ 'age', 'name' ]

    Schema.prototype.set()

    Parameters
    • key -«String» option name
    • [value] -«Object» if not passed, the current option value is returned

    Sets/gets a schema option.

    - -

    Example

    - -
    schema.set('strict'); // 'true' by default
    -schema.set('strict', false); // Sets 'strict' to false
    -schema.set('strict'); // 'false'

    Schema.prototype.static()

    Parameters
    • name -«String|Object»
    • [fn] -«Function»

    Adds static "class" methods to Models compiled from this schema.

    - -

    Example

    - -
    const schema = new Schema(..);
    -// Equivalent to `schema.statics.findByName = function(name) {}`;
    -schema.static('findByName', function(name) {
    -  return this.find({ name: name });
    -});
    -
    -const Drink = mongoose.model('Drink', schema);
    -await Drink.findByName('LaCroix');
    - -

    If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as statics.


    Schema.prototype.virtual()

    Parameters
    • name -«String»
    • [options] -«Object»
      • [options.ref] -«String|Model» model name or model instance. Marks this as a populate virtual.
      • [options.localField] -«String|Function» Required for populate virtuals. See populate virtual docs for more information.
      • [options.foreignField] -«String|Function» Required for populate virtuals. See populate virtual docs for more information.
      • [options.justOne=false] -«Boolean|Function» Only works with populate virtuals. If truthy, will be a single doc or null. Otherwise, the populate virtual will be an array.
      • [options.count=false] -«Boolean» Only works with populate virtuals. If truthy, this populate virtual will contain the number of documents rather than the documents themselves when you populate().
    Returns:
    • «VirtualType»

    Creates a virtual type with the given name.


    Schema.prototype.virtualpath()

    Parameters
    • name -«String»
    Returns:
    • «VirtualType»

    Returns the virtual type with the given name.


    Schema.reserved

    Type:
    • «property»

    Reserved document keys.

    - -

    Keys in this object are names that are rejected in schema declarations because they conflict with Mongoose functionality. If you create a schema using new Schema() with one of these property names, Mongoose will throw an error.

    - -
      -
    • prototype
    • -
    • emit
    • -
    • on
    • -
    • once
    • -
    • listeners
    • -
    • removeListener
    • -
    • collection
    • -
    • db
    • -
    • errors
    • -
    • init
    • -
    • isModified
    • -
    • isNew
    • -
    • get
    • -
    • modelName
    • -
    • save
    • -
    • schema
    • -
    • toObject
    • -
    • validate
    • -
    • remove
    • -
    • populated
    • -
    • _pres
    • -
    • _posts
    • -
    - -

    NOTE: Use of these terms as method names is permitted, but play at your own risk, as they may be existing mongoose document methods you are stomping on.

    - -
    var schema = new Schema(..);
    - schema.methods.init = function () {} // potentially breaking
    \ No newline at end of file diff --git a/docs/api/schematype.html b/docs/api/schematype.html deleted file mode 100644 index 28ba7f48156..00000000000 --- a/docs/api/schematype.html +++ /dev/null @@ -1,498 +0,0 @@ -Mongoose v5.6.0:

    Schematype


    SchemaType()

    Parameters
    • path -«String»
    • [options] -«Object»
    • [instance] -«String»

    SchemaType constructor. Do not instantiate SchemaType directly. Mongoose converts your schema paths into SchemaTypes automatically.

    - -

    Example:

    - -
    const schema = new Schema({ name: String });
    -schema.path('name') instanceof SchemaType; // true

    SchemaType.cast()

    Parameters
    • caster -«Function|false» Function that casts arbitrary values to this type, or throws an error if casting failed
    Returns:
    • «Function»

    Get/set the function used to cast arbitrary values to this type.

    - -

    Example:

    - -
    // Disallow `null` for numbers, and don't try to cast any values to
    -// numbers, so even strings like '123' will cause a CastError.
    -mongoose.Number.cast(function(v) {
    -  assert.ok(v === undefined || typeof v === 'number');
    -  return v;
    -});

    SchemaType.checkRequired()

    Parameters
    • fn -«Function»
    Returns:
    • «Function»

    Override the function the required validator uses to check whether a value passes the required check. Override this on the individual SchemaType.

    - -

    Example:

    - -
    // Use this to allow empty strings to pass the `required` validator
    -mongoose.Schema.Types.String.checkRequired(v => typeof v === 'string');

    SchemaType.get()

    Parameters
    • getter -«Function»
    Returns:
    • «this»

    Attaches a getter for all instances of this schema type.

    - -

    Example:

    - -
    // Make all numbers round down
    -mongoose.Number.get(function(v) { return Math.floor(v); });

    SchemaType.prototype.default()

    Parameters
    • val -«Function|any» the default value
    Returns:
    • «defaultValue»

    Sets a default value for this SchemaType.

    - -

    Example:

    - -
    var schema = new Schema({ n: { type: Number, default: 10 })
    -var M = db.model('M', schema)
    -var m = new M;
    -console.log(m.n) // 10
    - -

    Defaults can be either functions which return the value to use as the default or the literal value itself. Either way, the value will be cast based on its schema type before being set during document creation.

    - -

    Example:

    - -
    // values are cast:
    -var schema = new Schema({ aNumber: { type: Number, default: 4.815162342 }})
    -var M = db.model('M', schema)
    -var m = new M;
    -console.log(m.aNumber) // 4.815162342
    -
    -// default unique objects for Mixed types:
    -var schema = new Schema({ mixed: Schema.Types.Mixed });
    -schema.path('mixed').default(function () {
    -  return {};
    -});
    -
    -// if we don't use a function to return object literals for Mixed defaults,
    -// each document will receive a reference to the same object literal creating
    -// a "shared" object instance:
    -var schema = new Schema({ mixed: Schema.Types.Mixed });
    -schema.path('mixed').default({});
    -var M = db.model('M', schema);
    -var m1 = new M;
    -m1.mixed.added = 1;
    -console.log(m1.mixed); // { added: 1 }
    -var m2 = new M;
    -console.log(m2.mixed); // { added: 1 }

    SchemaType.prototype.get()

    Parameters
    • fn -«Function»
    Returns:
    • «SchemaType» this

    Adds a getter to this schematype.

    - -

    Example:

    - -
    function dob (val) {
    -  if (!val) return val;
    -  return (val.getMonth() + 1) + "/" + val.getDate() + "/" + val.getFullYear();
    -}
    -
    -// defining within the schema
    -var s = new Schema({ born: { type: Date, get: dob })
    -
    -// or by retreiving its SchemaType
    -var s = new Schema({ born: Date })
    -s.path('born').get(dob)
    - -

    Getters allow you to transform the representation of the data as it travels from the raw mongodb document to the value that you see.

    - -

    Suppose you are storing credit card numbers and you want to hide everything except the last 4 digits to the mongoose user. You can do so by defining a getter in the following way:

    - -
    function obfuscate (cc) {
    -  return '****-****-****-' + cc.slice(cc.length-4, cc.length);
    -}
    -
    -var AccountSchema = new Schema({
    -  creditCardNumber: { type: String, get: obfuscate }
    -});
    -
    -var Account = db.model('Account', AccountSchema);
    -
    -Account.findById(id, function (err, found) {
    -  console.log(found.creditCardNumber); // '****-****-****-1234'
    -});
    - -

    Getters are also passed a second argument, the schematype on which the getter was defined. This allows for tailored behavior based on options passed in the schema.

    - -
    function inspector (val, schematype) {
    -  if (schematype.options.required) {
    -    return schematype.path + ' is required';
    -  } else {
    -    return schematype.path + ' is not';
    -  }
    -}
    -
    -var VirusSchema = new Schema({
    -  name: { type: String, required: true, get: inspector },
    -  taxonomy: { type: String, get: inspector }
    -})
    -
    -var Virus = db.model('Virus', VirusSchema);
    -
    -Virus.findById(id, function (err, virus) {
    -  console.log(virus.name);     // name is required
    -  console.log(virus.taxonomy); // taxonomy is not
    -})

    SchemaType.prototype.immutable()

    Parameters
    • bool -«Boolean»
    Returns:
    • «SchemaType» this

    Defines this path as immutable. Mongoose prevents you from changing immutable paths unless the parent document has isNew: true.

    - -

    Example:

    - -
    const schema = new Schema({
    -  name: { type: String, immutable: true },
    -  age: Number
    -});
    -const Model = mongoose.model('Test', schema);
    -
    -await Model.create({ name: 'test' });
    -const doc = await Model.findOne();
    -
    -doc.isNew; // false
    -doc.name = 'new name';
    -doc.name; // 'test', because `name` is immutable
    - -

    Mongoose also prevents changing immutable properties using updateOne() and updateMany() based on strict mode.

    - -

    Example:

    - -
    // Mongoose will strip out the `name` update, because `name` is immutable
    -Model.updateOne({}, { $set: { name: 'test2' }, $inc: { age: 1 } });
    -
    -// If `strict` is set to 'throw', Mongoose will throw an error if you
    -// update `name`
    -const err = await Model.updateOne({}, { name: 'test2' }, { strict: 'throw' }).
    -  then(() => null, err =&gt; err);
    -err.name; // StrictModeError
    -
    -// If `strict` is `false`, Mongoose allows updating `name` even though
    -// the property is immutable.
    -Model.updateOne({}, { name: 'test2' }, { strict: false });

    SchemaType.prototype.index()

    Parameters
    • options -«Object|Boolean|String»
    Returns:
    • «SchemaType» this

    Declares the index options for this schematype.

    - -

    Example:

    - -
    var s = new Schema({ name: { type: String, index: true })
    -var s = new Schema({ loc: { type: [Number], index: 'hashed' })
    -var s = new Schema({ loc: { type: [Number], index: '2d', sparse: true })
    -var s = new Schema({ loc: { type: [Number], index: { type: '2dsphere', sparse: true }})
    -var s = new Schema({ date: { type: Date, index: { unique: true, expires: '1d' }})
    -Schema.path('my.path').index(true);
    -Schema.path('my.date').index({ expires: 60 });
    -Schema.path('my.path').index({ unique: true, sparse: true });
    - -

    NOTE:

    - -

    Indexes are created in the background by default. If background is set to false, MongoDB will not execute any read/write operations you send until the index build. Specify background: false to override Mongoose's default.


    SchemaType.prototype.ref()

    Parameters
    • ref -«String|Model|Function» either a model name, a Model, or a function that returns a model name or model.
    Returns:
    • «SchemaType» this

    Set the model that this path refers to. This is the option that populate looks at to determine the foreign collection it should query.

    - -

    Example:

    - -
    const userSchema = new Schema({ name: String });
    -const User = mongoose.model('User', userSchema);
    -
    -const postSchema = new Schema({ user: mongoose.ObjectId });
    -postSchema.path('user').ref('User'); // By model name
    -postSchema.path('user').ref(User); // Can pass the model as well
    -
    -// Or you can just declare the `ref` inline in your schema
    -const postSchema2 = new Schema({
    -  user: { type: mongoose.ObjectId, ref: User }
    -});

    SchemaType.prototype.required()

    Parameters
    • required -«Boolean|Function|Object» enable/disable the validator, or function that returns required boolean, or options object
      • [options.isRequired] -«Boolean|Function» enable/disable the validator, or function that returns required boolean
      • [options.ErrorConstructor] -«Function» custom error constructor. The constructor receives 1 parameter, an object containing the validator properties.
    • [message] -«String» optional custom error message
    Returns:
    • «SchemaType» this

    Adds a required validator to this SchemaType. The validator gets added to the front of this SchemaType's validators array using unshift().

    - -

    Example:

    - -
    var s = new Schema({ born: { type: Date, required: true })
    -
    -// or with custom error message
    -
    -var s = new Schema({ born: { type: Date, required: '{PATH} is required!' })
    -
    -// or with a function
    -
    -var s = new Schema({
    -  userId: ObjectId,
    -  username: {
    -    type: String,
    -    required: function() { return this.userId != null; }
    -  }
    -})
    -
    -// or with a function and a custom message
    -var s = new Schema({
    -  userId: ObjectId,
    -  username: {
    -    type: String,
    -    required: [
    -      function() { return this.userId != null; },
    -      'username is required if id is specified'
    -    ]
    -  }
    -})
    -
    -// or through the path API
    -
    -Schema.path('name').required(true);
    -
    -// with custom error messaging
    -
    -Schema.path('name').required(true, 'grrr :( ');
    -
    -// or make a path conditionally required based on a function
    -var isOver18 = function() { return this.age >= 18; };
    -Schema.path('voterRegistrationId').required(isOver18);
    - -

    The required validator uses the SchemaType's checkRequired function to determine whether a given value satisfies the required validator. By default, a value satisfies the required validator if val != null (that is, if the value is not null nor undefined). However, most built-in mongoose schema types override the default checkRequired function:


    SchemaType.prototype.select()

    Parameters
    • val -«Boolean»
    Returns:
    • «SchemaType» this

    Sets default select() behavior for this path.

    - -

    Set to true if this path should always be included in the results, false if it should be excluded by default. This setting can be overridden at the query level.

    - -

    Example:

    - -
    T = db.model('T', new Schema({ x: { type: String, select: true }}));
    -T.find(..); // field x will always be selected ..
    -// .. unless overridden;
    -T.find().select('-x').exec(callback);

    SchemaType.prototype.set()

    Parameters
    • fn -«Function»
    Returns:
    • «SchemaType» this

    Adds a setter to this schematype.

    - -

    Example:

    - -
    function capitalize (val) {
    -  if (typeof val !== 'string') val = '';
    -  return val.charAt(0).toUpperCase() + val.substring(1);
    -}
    -
    -// defining within the schema
    -var s = new Schema({ name: { type: String, set: capitalize }});
    -
    -// or with the SchemaType
    -var s = new Schema({ name: String })
    -s.path('name').set(capitalize);
    - -

    Setters allow you to transform the data before it gets to the raw mongodb document or query.

    - -

    Suppose you are implementing user registration for a website. Users provide an email and password, which gets saved to mongodb. The email is a string that you will want to normalize to lower case, in order to avoid one email having more than one account -- e.g., otherwise, avenue@q.com can be registered for 2 accounts via avenue@q.com and AvEnUe@Q.CoM.

    - -

    You can set up email lower case normalization easily via a Mongoose setter.

    - -
    function toLower(v) {
    -  return v.toLowerCase();
    -}
    -
    -var UserSchema = new Schema({
    -  email: { type: String, set: toLower }
    -});
    -
    -var User = db.model('User', UserSchema);
    -
    -var user = new User({email: 'AVENUE@Q.COM'});
    -console.log(user.email); // 'avenue@q.com'
    -
    -// or
    -var user = new User();
    -user.email = 'Avenue@Q.com';
    -console.log(user.email); // 'avenue@q.com'
    -User.updateOne({ _id: _id }, { $set: { email: 'AVENUE@Q.COM' } }); // update to 'avenue@q.com'
    -
    - -

    As you can see above, setters allow you to transform the data before it stored in MongoDB, or before executing a query.

    - -

    NOTE: we could have also just used the built-in lowercase: true SchemaType option instead of defining our own function.

    - -
    new Schema({ email: { type: String, lowercase: true }})
    - -

    Setters are also passed a second argument, the schematype on which the setter was defined. This allows for tailored behavior based on options passed in the schema.

    - -
    function inspector (val, schematype) {
    -  if (schematype.options.required) {
    -    return schematype.path + ' is required';
    -  } else {
    -    return val;
    -  }
    -}
    -
    -var VirusSchema = new Schema({
    -  name: { type: String, required: true, set: inspector },
    -  taxonomy: { type: String, set: inspector }
    -})
    -
    -var Virus = db.model('Virus', VirusSchema);
    -var v = new Virus({ name: 'Parvoviridae', taxonomy: 'Parvovirinae' });
    -
    -console.log(v.name);     // name is required
    -console.log(v.taxonomy); // Parvovirinae
    - -

    You can also use setters to modify other properties on the document. If you're setting a property name on a document, the setter will run with this as the document. Be careful, in mongoose 5 setters will also run when querying by name with this as the query.

    - -
    const nameSchema = new Schema({ name: String, keywords: [String] });
    -nameSchema.path('name').set(function(v) {
    -  // Need to check if `this` is a document, because in mongoose 5
    -  // setters will also run on queries, in which case `this` will be a
    -  // mongoose query object.
    -  if (this instanceof Document && v != null) {
    -    this.keywords = v.split(' ');
    -  }
    -  return v;
    -});
    -

    SchemaType.prototype.sparse()

    Parameters
    • bool -«Boolean»
    Returns:
    • «SchemaType» this

    Declares a sparse index.

    - -

    Example:

    - -
    var s = new Schema({ name: { type: String, sparse: true } });
    -Schema.path('name').index({ sparse: true });

    SchemaType.prototype.text()

    Parameters
    • bool -«Boolean»
    Returns:
    • «SchemaType» this

    Declares a full text index.

    - -

    Example:

    - -
    var s = new Schema({name : {type: String, text : true })
    - Schema.path('name').index({text : true});

    SchemaType.prototype.unique()

    Parameters
    • bool -«Boolean»
    Returns:
    • «SchemaType» this

    Declares an unique index.

    - -

    Example:

    - -
    var s = new Schema({ name: { type: String, unique: true }});
    -Schema.path('name').index({ unique: true });
    - -

    NOTE: violating the constraint returns an E11000 error from MongoDB when saving, not a Mongoose validation error.


    SchemaType.prototype.validate()

    Parameters
    • obj -«RegExp|Function|Object» validator function, or hash describing options
      • [obj.validator] -«Function» validator function. If the validator function returns undefined or a truthy value, validation succeeds. If it returns falsy (except undefined) or throws an error, validation fails.
      • [obj.message] -«String|Function» optional error message. If function, should return the error message as a string
      • [obj.propsParameter=false] -«Boolean» If true, Mongoose will pass the validator properties object (with the validator function, message, etc.) as the 2nd arg to the validator function. This is disabled by default because many validators rely on positional args, so turning this on may cause unpredictable behavior in external validators.
    • [errorMsg] -«String|Function» optional error message. If function, should return the error message as a string
    • [type] -«String» optional validator type
    Returns:
    • «SchemaType» this

    Adds validator(s) for this document path.

    - -

    Validators always receive the value to validate as their first argument and 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 will be used.

    - -

    Examples:

    - -
    // make sure every value is equal to "something"
    -function validator (val) {
    -  return val == 'something';
    -}
    -new Schema({ name: { type: String, validate: validator }});
    -
    -// with a custom error message
    -
    -var custom = [validator, 'Uh oh, {PATH} does not equal "something".']
    -new Schema({ name: { type: String, validate: custom }});
    -
    -// adding many validators at a time
    -
    -var many = [
    -    { validator: validator, msg: 'uh oh' }
    -  , { validator: anotherValidator, msg: 'failed' }
    -]
    -new Schema({ name: { type: String, validate: many }});
    -
    -// or utilizing SchemaType methods directly:
    -
    -var schema = new Schema({ name: 'string' });
    -schema.path('name').validate(validator, 'validation of `{PATH}` failed with value `{VALUE}`');
    - -

    Error message templates:

    - -

    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.

    - -

    If Mongoose's built-in error message templating isn't enough, Mongoose supports setting the message property to a function.

    - -
    schema.path('name').validate({
    -  validator: function() { return v.length > 5; },
    -  // `errors['name']` will be "name must have length 5, got 'foo'"
    -  message: function(props) {
    -    return `${props.path} must have length 5, got '${props.value}'`;
    -  }
    -});
    - -

    To bypass Mongoose's error messages and just copy the error message that the validator throws, do this:

    - -
    schema.path('name').validate({
    -  validator: function() { throw new Error('Oops!'); },
    -  // `errors['name']` will be "Oops!"
    -  message: function(props) { return props.reason.message; }
    -});
    - -

    Asynchronous validation:

    - -

    Mongoose supports validators that return a promise. A validator that returns a promise is called an async validator. Async validators run in parallel, and validate() will wait until all async validators have settled.

    - -
    schema.path('name').validate({
    -  validator: function (value) {
    -    return new Promise(function (resolve, reject) {
    -      resolve(false); // validation failed
    -    });
    -  }
    -});
    - -

    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.

    - -

    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, passing the validation error object along.

    - -
    var conn = mongoose.createConnection(..);
    -conn.on('error', handleError);
    -
    -var Product = conn.model('Product', yourSchema);
    -var dvd = new Product(..);
    -dvd.save(); // emits error on the `conn` above
    - -

    If you want to handle these errors at the Model level, add an error listener to your Model as shown below.

    - -
    // registering an error listener on the Model lets us handle errors more locally
    -Product.on('error', handleError);
    \ No newline at end of file diff --git a/docs/api/singlenestedpath.html b/docs/api/singlenestedpath.html deleted file mode 100644 index c4a874438c6..00000000000 --- a/docs/api/singlenestedpath.html +++ /dev/null @@ -1,78 +0,0 @@ -Mongoose v5.6.6-pre:

    SingleNestedPath


    SingleNestedPath()

    Parameters
    • schema -«Schema»
    • key -«String»
    • options -«Object»
    Inherits:
    • «SchemaType»

    Single nested subdocument SchemaType constructor.


    SingleNestedPath.prototype.discriminator()

    Parameters
    • name -«String»
    • schema -«Schema» fields to add to the schema for instances of this sub-class

    Adds a discriminator to this single nested subdocument.

    - -

    Example:

    - -
    const shapeSchema = Schema({ name: String }, { discriminatorKey: 'kind' });
    -const schema = Schema({ shape: shapeSchema });
    -
    -const singleNestedPath = parentSchema.path('child');
    -singleNestedPath.discriminator('Circle', Schema({ radius: Number }));
    \ No newline at end of file diff --git a/docs/api/virtualtype.html b/docs/api/virtualtype.html deleted file mode 100644 index 28e7102cef8..00000000000 --- a/docs/api/virtualtype.html +++ /dev/null @@ -1,124 +0,0 @@ -Mongoose v5.6.0:

    Virtualtype


    VirtualType()

    Parameters
    • options -«Object»
      • [options.ref] -«string|function» if ref is not nullish, this becomes a populated virtual
      • [options.localField] -«string|function» the local field to populate on if this is a populated virtual.
      • [options.foreignField] -«string|function» the foreign field to populate on if this is a populated virtual.
      • [options.justOne=false] -«boolean» by default, a populated virtual is an array. If you set justOne, the populated virtual will be a single doc or null.
      • [options.getters=false] -«boolean» if you set this to true, Mongoose will call any custom getters you defined on this virtual
      • [options.count=false] -«boolean» 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()

    VirtualType constructor

    - -

    This is what mongoose uses to define virtual attributes via Schema.prototype.virtual.

    - -

    Example:

    - -
    const fullname = schema.virtual('fullname');
    -fullname instanceof mongoose.VirtualType // true

    VirtualType.prototype.applyGetters()

    Parameters
    • value -«Object»
    • doc -«Document» The document this virtual is attached to
    Returns:
    • «any» the value after applying all getters

    Applies getters to value.


    VirtualType.prototype.applySetters()

    Parameters
    • value -«Object»
    • doc -«Document»
    Returns:
    • «any» the value after applying all setters

    Applies setters to value.


    VirtualType.prototype.get()

    Parameters
    • VirtualType, -«Function(Any|» Document)} fn
    Returns:
    • «VirtualType» this

    Adds a custom getter to this virtual.

    - -

    Mongoose calls the getter function with 3 parameters

    - -
      -
    • value: the value returned by the previous getter. If there is only one getter, value will be undefined.
    • -
    • virtual: the virtual object you called .get() on
    • -
    • doc: the document this virtual is attached to. Equivalent to this.
    • -
    - -

    Example:

    - -
    var virtual = schema.virtual('fullname');
    -virtual.get(function(value, virtual, doc) {
    -  return this.name.first + ' ' + this.name.last;
    -});

    VirtualType.prototype.set()

    Parameters
    • VirtualType, -«Function(Any|» Document)} fn
    Returns:
    • «VirtualType» this

    Adds a custom setter to this virtual.

    - -

    Mongoose calls the setter function with 3 parameters

    - -
      -
    • value: the value being set
    • -
    • virtual: the virtual object you're calling .set() on
    • -
    • doc: the document this virtual is attached to. Equivalent to this.
    • -
    - -

    Example:

    - -
    const virtual = schema.virtual('fullname');
    -virtual.set(function(value, virtual, doc) {
    -  var parts = value.split(' ');
    -  this.name.first = parts[0];
    -  this.name.last = parts[1];
    -});
    -
    -const Model = mongoose.model('Test', schema);
    -const doc = new Model();
    -// Calls the setter with `value = 'Jean-Luc Picard'`
    -doc.fullname = 'Jean-Luc Picard';
    -doc.name.first; // 'Jean-Luc'
    -doc.name.last; // 'Picard'
    \ No newline at end of file diff --git a/docs/async-await.md b/docs/async-await.md new file mode 100644 index 00000000000..d68616299f4 --- /dev/null +++ b/docs/async-await.md @@ -0,0 +1,151 @@ +# Using Async/Await with Mongoose + +* [Basic Use](#basic-use) +* [Async Functions](#async-functions) +* [Queries](#queries) + +### Basic Use + +Async/await lets us write asynchronous code as if it were synchronous. +This is especially helpful for avoiding callback hell when executing multiple async operations in sequence--a common scenario when working with Mongoose. +Each of the three functions below retrieves a record from the database, updates it, and prints the updated record to the console. + +```javascript +// Works. +function callbackUpdate() { + MyModel.findOne({firstName: 'franklin', lastName: 'roosevelt'}, function(err, doc) { + if(err) { + handleError(err); + } + + doc.middleName = 'delano'; + + doc.save(function(err, updatedDoc) { + if(err) { + handleError(err); + } + + // Final logic is 2 callbacks deep + console.log(updatedDoc); + }); + }) +} + +// Better. +function thenUpdate() { + MyModel.findOne({firstName: 'franklin', lastName: 'roosevelt'}) + .then(function(doc) { + doc.middleName = 'delano'; + return doc.save(); + }) + .then(console.log) + .catch(function(err) { + handleError(err); + }); +}; + +// Best? +async function awaitUpdate() { + try { + const doc = await MyModel.findOne({ + firstName: 'franklin', + lastName: 'roosevelt' + }); + + doc.middleName = 'delano'; + + console.log(await doc.save()); + } + catch(err) { + handleError(err); + } +} +``` + +Note that the specific fulfillment values of different Mongoose methods vary, and may be affected by configuration. Please refer to the [API documentation](./api.html) for information about specific methods. + +### Async Functions + +Adding the keyword *async* to a JavaScript function automatically causes it to return a native JavaScript promise. +This is true [regardless of the return value we specify in the function body](http://thecodebarbarian.com/async-functions-in-javascript.html#an-async-function-always-returns-a-promise). + +```javascript +async function getUser() { + //Inside getUser, we can await an async operation and interact with + //foundUser as a normal, non-promise value... + const foundUser = await User.findOne({name: 'bill'}); + + console.log(foundUser); //Prints '{name: 'bill', admin: false}' + return foundUser; +} + +//However, because async functions always return a promise, +//user is a promise. +const user = getUser(); + +console.log(user) //Oops. Prints '[Promise]' +``` + +Instead, treat the return value of an async function as you would any other promise. Await its fulfillment inside another async function, or chain onto it using `.then` blocks. + +```javascript +async function getUser() { + const foundUser = await User.findOne({name: 'bill'}); + return foundUser; +}; + +async function doStuffWithUser() { + //Await the promise returned from calling getUser. + const user = await getUser(); + + console.log(user); //Prints '{name: 'bill', admin: false}' +} +``` + +

    Async/Await with Mongoose Queries

    + +Under the hood, [async/await is syntactic sugar](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await) over the Promise API. +Due to the surprisingly simple way promises are implemented in JavaScript, the keyword `await` will try to unwrap any object with a property whose key is the string ‘then’ and whose value is a function. +Such objects belong to a broader class of objects called [thenables](https://masteringjs.io/tutorials/fundamentals/thenable). +If the thenable being unwrapped is a genuine promise, e.g. an instance of the [Promise constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), we enjoy several guarantees about how the object’s ‘then’ function will behave. +However, Mongoose provides several static helper methods that return a different class of thenable object called a [Query](./queries.html)--and [Queries are not promises](./queries.html#queries-are-not-promises). +Because Queries are also *thenables*, we can interact with a Query using async/await just as we would interact with a genuine promise, with one key difference: observing the fulfillment value of a genuine promise cannot under any circumstances change that value, but trying to re-observe the value of a Query may cause the Query to be re-executed. + +```javascript +function isPromise(thenable) { + return thenable instanceof Promise; +}; + +// The fulfillment value of the promise returned by user.save() will always be the same, +// regardless of how, or how often, we observe it. +async function observePromise() { + const user = await User.findOne({firstName: 'franklin', lastName:'roosevelt'}); + + user.middleName = 'delano'; + + // Document.prototype.save() returns a *genuine* promise + const realPromise = user.save(); + + console.log(isPromise(realPromise)); //true + + const awaitedValue = await realPromise; + + realPromise.then(chainedValue => console.log(chainedValue === awaitedValue)); //true +} + +// By contrast, the value we receive when we try to observe the same Query more than +// once is different every time. The Query is re-executing. +async function observeQuery() { + const query = User.findOne({firstname: 'leroy', lastName: 'jenkins'}); + + console.log(isPromise(query)); //false + + const awaitedValue = await query; + + query.then(chainedValue => console.log(chainedValue === awaitedValue)); //false +} +``` + +You are most likely to accidentally re-execute queries in this way when mixing callbacks with async/await. + This is never necessary and should be avoided. + If you need a Query to return a fully-fleged promise instead of a thenable, you can use [Query#exec()](./api/query.html#query_Query-exec). \ No newline at end of file diff --git a/docs/async-await.pug b/docs/async-await.pug deleted file mode 100644 index 9429e4d8dd9..00000000000 --- a/docs/async-await.pug +++ /dev/null @@ -1,172 +0,0 @@ -extends layout - -append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown - ## Async/Await - - - - - - ul - li - a(href="#basic-use") Basic Use - li - a(href="#async-functions") Async Functions - li - a(href="#queries") Queries - - h3(id="basic-use") Basic Use - :markdown - Async/await lets us write asynchronous code as if it were synchronous. This is especially helpful for avoiding callback hell when executing multiple async operations in sequence--a common scenario when working with Mongoose. Each of the three functions below retrieves a record from the database, updates it, - and prints the updated record to the console. - - ```javascript - // Works. - function callbackUpdate() { - MyModel.findOne({firstName: 'franklin', lastName: 'roosevelt'}, function(err, doc) { - if(err) { - handleError(err); - } - - doc.middleName = 'delano'; - - doc.save(function(err, updatedDoc) { - if(err) { - handleError(err); - } - - // Final logic is 2 callbacks deep - console.log(updatedDoc); - }); - }) - } - - // Better. - function thenUpdate() { - MyModel.findOne({firstName: 'franklin', lastName: 'roosevelt'}) - .then(function(doc) { - doc.middleName = 'delano'; - return doc.save(); - }) - .then(console.log) - .catch(function(err) { - handleError(err); - }); - }; - - // Best? - async function awaitUpdate() { - try { - const doc = await MyModel.findOne({ - firstName: 'franklin', - lastName: 'roosevelt' - }); - - doc.middleName = 'delano'; - - console.log(await doc.save()); - } - - catch(err) { - handleError(err); - } - } - ``` - :markdown - Note that the specific fulfillment values of different Mongoose methods vary, and may be affected by configuration. Please refer to the [API documentation](./api.html) for information about specific methods. - - h3(id="async-functions") Async Functions - :markdown - Adding the keyword *async* to a JavaScript function automatically causes it to return a native JavaScript promise. This is true [regardless of the return value we specify in the function body](http://thecodebarbarian.com/async-functions-in-javascript.html#an-async-function-always-returns-a-promise). - - ```javascript - async function getUser() { - //Inside getUser, we can await an async operation and interact with - //foundUser as a normal, non-promise value... - const foundUser = await User.findOne({name: 'bill'}); - - console.log(foundUser); //Prints '{name: 'bill', admin: false}' - return foundUser; - } - - //However, because async functions always return a promise, - //user is a promise. - const user = getUser(); - - console.log(user) //Oops. Prints '[Promise]' - ``` - :markdown - Instead, treat the return value of an async function as you would any other promise. Await its fulfillment inside another async function, or chain onto it using ‘.then’ blocks. - - ```javascript - async function getUser() { - const foundUser = await User.findOne({name: 'bill'}); - return foundUser; - }; - - async function doStuffWithUser() { - //Await the promise returned from calling getUser. - const user = await getUser(); - - console.log(user); //Prints '{name: 'bill', admin: false}' - } - - h3(id="queries") Async/Await with Mongoose Queries - :markdown - Under the hood, [*async/await* is syntactic sugar](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await) - over the Promise API. Due to the surprisingly simple way promises are implemented in JavaScript, the keyword *await* will try to unwrap any object with a property whose key is the string ‘then’ and whose value is a function. Such objects belong to a broader class of objects called thenables. If the thenable being unwrapped is a genuine promise, e.g. an instance of the [Promise constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), we enjoy several guarantees about how the object’s ‘then’ function will behave. However, Mongoose provides several static helper methods that return a different class of thenable object called a [Query](./queries.html)--and [Queries are not promises](./queries.html#queries-are-not-promises). Because Queries are also *thenables*, we can interact with a Query using async/await just as we would interact with a genuine promise, with one key difference: observing the fulfillment value of a genuine promise cannot under any circumstances change that value, but trying to re-observe the value of a Query may cause the Query to be re-executed. - - ```javascript - function isPromise(thenable) { - return thenable instanceof Promise; - }; - - // The fulfillment value of the promise returned by user.save() will always be the same, - // regardless of how, or how often, we observe it. - async function observePromise() { - const user = await User.findOne({firstName: 'franklin', lastName:'roosevelt'}); - - user.middleName = 'delano'; - - // Document.prototype.save() returns a *genuine* promise - const realPromise = user.save(); - - console.log(isPromise(realPromise)); //true - - const awaitedValue = await realPromise; - - realPromise.then(chainedValue => console.log(chainedValue === awaitedValue)); //true - } - - // By contrast, the value we receive when we try to observe the same Query more than - // once is different every time. The Query is re-executing. - async function observeQuery() { - const query = User.findOne({firstname: 'leroy', lastName: 'jenkins'}); - - console.log(isPromise(query)); //false - - const awaitedValue = await query; - - query.then(chainedValue => console.log(chainedValue === awaitedValue)); //false - } - ``` - :markdown - You are most likely to accidentally re-execute queries in this way when mixing callbacks with async/await. This is never necessary and should be avoided. If you need a Query to return a fully-fleged promise instead of a thenable, you can use [Query#exec()](./api/query.html#query_Query-exec). - - - diff --git a/docs/browser.html b/docs/browser.html deleted file mode 100644 index ea0a5b45039..00000000000 --- a/docs/browser.html +++ /dev/null @@ -1,140 +0,0 @@ -Mongoose v5.6.0: Browser Library

    Mongoose in the Browser

    - - - - - -

    Mongoose supports creating schemas and validating documents in the browser. -Mongoose's browser library does not support saving documents, queries, -populate, -discriminators, -or any other Mongoose feature other than schemas and validating documents.

    -

    Use the below syntax to access the Mongoose browser library.

    -
    // Using `require()`
    -const mongoose = require('mongoose/browser');
    -
    -// Using ES6 imports
    -import mongoose from 'mongoose/browser';
    - - - -

    Using the Browser Library

    - -

    Mongoose's browser library is very limited. The only use case it supports -is validating documents as shown below.

    -
    import mongoose from 'mongoose/browser';
    -
    -// Mongoose's browser library does **not** have models. It only supports
    -// schemas and documents. The primary use case is validating documents
    -// against Mongoose schemas.
    -const doc = new mongoose.Document({}, new mongoose.Schema({
    -  name: { type: String, required: true }
    -}));
    -// Prints an error because `name` is required.
    -console.log(doc.validateSync());
    -

    Building With Webpack

    - -

    Mongoose uses ES2015 (also known as ES6) -syntax, so in order to use Mongoose with older browsers you'll need to use -a tool like Babel. As of version -5.x, Mongoose no longer has an officially supported pre-built browser bundle, -you need to compile the browser library yourself.

    -

    To build Mongoose's browser library using Webpack, you'll need to use -babel-loader. Because of -how Webpack loads require() statements, it pulls in a lot of built-in -Node.js modules. In order to avoid this, you need to use the -node Webpack config option -as shown below.

    -
    // Below is the Webpack config Mongoose uses for testing
    -const config = {
    -  entry: ['./test/files/sample.js'],
    -  module: {
    -    rules: [
    -      {
    -        test: /\.js$/,
    -        include: [
    -          /\/mongoose\//i,
    -          /\/kareem\//i
    -        ],
    -        loader: 'babel-loader',
    -        options: {
    -          presets: ['es2015']
    -        }
    -      }
    -    ]
    -  },
    -  node: {
    -    // Replace these Node.js native modules with empty objects, Mongoose's
    -    // browser library does not use them.
    -    // See https://webpack.js.org/configuration/node/
    -    dns: 'empty',
    -    fs: 'empty',
    -    'module': 'empty',
    -    net: 'empty',
    -    tls: 'empty'
    -  },
    -  target: 'web',
    -  mode: 'production'
    -};
    -
    \ No newline at end of file diff --git a/docs/browser.md b/docs/browser.md new file mode 100644 index 00000000000..8fc68fa29d4 --- /dev/null +++ b/docs/browser.md @@ -0,0 +1,37 @@ +# Mongoose in the Browser + +Mongoose supports creating schemas and validating documents in the browser. +Mongoose's browser library does **not** support saving documents, [queries](http://mongoosejs.com/docs/queries.html), [populate](http://mongoosejs.com/docs/populate.html), [discriminators](http://mongoosejs.com/docs/discriminators.html), or any other Mongoose feature other than schemas and validating documents. + +Mongoose has a pre-built bundle of the browser library. If you're bundling your code with [Webpack](https://webpack.js.org/), you should be able to import Mongoose's browser library as shown below if your Webpack `target` is `'web'`: + +```javascript +import mongoose from 'mongoose'; +``` + +You can use the below syntax to access the Mongoose browser library from Node.js: + +```javascript +// Using `require()` +const mongoose = require('mongoose/browser'); + +// Using ES6 imports +import mongoose from 'mongoose/browser'; +``` + +

    Using the Browser Library

    + +Mongoose's browser library is very limited. The only use case it supports is validating documents as shown below. + +```javascript +import mongoose from 'mongoose'; + +// Mongoose's browser library does **not** have models. It only supports +// schemas and documents. The primary use case is validating documents +// against Mongoose schemas. +const doc = new mongoose.Document({}, new mongoose.Schema({ + name: { type: String, required: true } +})); +// Prints an error because `name` is required. +console.log(doc.validateSync()); +``` \ No newline at end of file diff --git a/docs/browser.pug b/docs/browser.pug deleted file mode 100644 index d04e3ee6580..00000000000 --- a/docs/browser.pug +++ /dev/null @@ -1,69 +0,0 @@ -extends layout - -block append style - link(rel="stylesheet" href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown -

    Mongoose in the Browser

    - - - - - - Mongoose supports creating schemas and validating documents in the browser. - Mongoose's browser library does **not** support saving documents, [queries](http://mongoosejs.com/docs/queries.html), - [populate](http://mongoosejs.com/docs/populate.html), - [discriminators](http://mongoosejs.com/docs/discriminators.html), - or any other Mongoose feature other than schemas and validating documents. - - Mongoose has a pre-built bundle of the browser library. If you're bundling your code with [Webpack](https://webpack.js.org/), - you should be able to import Mongoose's browser library as shown below if your Webpack `target` is `'web'`: - - ```javascript - import mongoose from 'mongoose'; - ``` - - You can use the below syntax to access the Mongoose browser library from Node.js: - - ```javascript - // Using `require()` - const mongoose = require('mongoose/browser'); - - // Using ES6 imports - import mongoose from 'mongoose/browser'; - ``` - - - - -

    Using the Browser Library

    - - Mongoose's browser library is very limited. The only use case it supports - is validating documents as shown below. - - ```javascript - import mongoose from 'mongoose'; - - // Mongoose's browser library does **not** have models. It only supports - // schemas and documents. The primary use case is validating documents - // against Mongoose schemas. - const doc = new mongoose.Document({}, new mongoose.Schema({ - name: { type: String, required: true } - })); - // Prints an error because `name` is required. - console.log(doc.validateSync()); - ``` \ No newline at end of file diff --git a/docs/built-with-mongoose.md b/docs/built-with-mongoose.md new file mode 100644 index 00000000000..bbd19d522a1 --- /dev/null +++ b/docs/built-with-mongoose.md @@ -0,0 +1,85 @@ +## Built With Mongoose + +According to [GitHub](https://github.com/Automattic/mongoose), there are over 870,000 projects that depend on Mongoose. +Here are a few of our favorite apps that are built with Mongoose. + +
    +
    + + + +
    +
    +

    + + Terra Vera + +

    + Terra Vera produces on-demand antimicrobial chemistry for agriculture and surfaces. Terra Vera's on-site generators convert water, electricity, and a proprietary blend of salts and amino acids into an organic solution that eliminates many common pathogens (like mold, powdery mildew, and viruses), but is safe for plants, people, and pets. +
    +
    + +
    +
    + + + +
    +
    +

    SixPlus

    + SixPlus is an online marketplace for corporate event professionals to + book private dining spaces in restaurants and hotels across the US. + You can book your next company event at venues like Refinery Rooftop or + Good Behavior on SixPlus. +
    +
    + +
    +
    + + + +
    +
    +

    Payment Ninja

    + Payment Ninja is an online payment gateway that lets you save up to + 50% on payment processing over processors like PayPal and Stripe. +
    +
    + +
    +
    + + + +
    +
    +

    + + Mixmax + +

    + Mixmax is a app that sends engaging emails with instant scheduling, free unlimited email tracking, polls, and surveys right in Gmail. +
    +
    + +
    +
    + + + +
    +
    +

    + + Builder Book + +

    + Learn how to build a full-stack, production-ready JavaScript web application from scratch. You'll go from 0 lines of code in Chapter 1 to over 10,000 lines of code by Chapter 8. +
    +
    + +## Add Your Own + +Have an app that you built with Mongoose that you want to feature here? +Let's talk! [DM Mongoose on Twitter](https://twitter.com/mongoosejs). \ No newline at end of file diff --git a/docs/built-with-mongoose.pug b/docs/built-with-mongoose.pug deleted file mode 100644 index 2c30c30dce4..00000000000 --- a/docs/built-with-mongoose.pug +++ /dev/null @@ -1,94 +0,0 @@ -extends layout - -block content - - - - - :markdown - ## Built With Mongoose - - According to [GitHub](https://github.com/Automattic/mongoose), there are - over 870,000 projects that depend on Mongoose. Here are a few of - our favorite apps that are built with Mongoose. - -
    -
    - - - -
    -
    -

    - - Terra Vera - -

    - Terra Vera produces on-demand antimicrobial chemistry for agriculture and surfaces. Terra Vera's on-site generators convert water, electricity, and a proprietary blend of salts and amino acids into an organic solution that eliminates many common pathogens (like mold, powdery mildew, and viruses), but is safe for plants, people, and pets. -
    -
    - -
    -
    - - - -
    -
    -

    SixPlus

    - SixPlus is an online marketplace for corporate event professionals to - book private dining spaces in restaurants and hotels across the US. - You can book your next company event at venues like Refinery Rooftop or - Good Behavior on SixPlus. -
    -
    - -
    -
    - - - -
    -
    -

    Payment Ninja

    - Payment Ninja is an online payment gateway that lets you save up to - 50% on payment processing over processors like PayPal and Stripe. -
    -
    - -
    -
    - - - -
    -
    -

    - - Mixmax - -

    - Mixmax is a app that sends engaging emails with instant scheduling, free unlimited email tracking, polls, and surveys right in Gmail. -
    -
    - -
    -
    - - - -
    -
    -

    - - Builder Book - -

    - Learn how to build a full-stack, production-ready JavaScript web application from scratch. You'll go from 0 lines of code in Chapter 1 to over 10,000 lines of code by Chapter 8. -
    -
    - - ## Add Your Own - - Have an app that you built with Mongoose that you want to feature here? - Let's talk! [DM Mongoose on Twitter](https://twitter.com/mongoosejs). diff --git a/docs/compatibility.html b/docs/compatibility.html deleted file mode 100644 index cdd4d767b52..00000000000 --- a/docs/compatibility.html +++ /dev/null @@ -1,88 +0,0 @@ -Mongoose v5.6.0: MongoDB Version Compatibility

    MongoDB Server Version Compatibility

    - - - - -

    Mongoose relies on the MongoDB Node.js Driver -to talk to MongoDB. You can refer to -this table -for up-to-date information as to which version of the MongoDB driver -supports which version of MongoDB.

    -

    Below are the semver ranges representing which -versions of mongoose are compatible with the listed versions of MongoDB -server.

    -
      -
    • MongoDB Server 2.4.x: mongoose ^3.8 or 4.x
    • -
    • MongoDB Server 2.6.x: mongoose ^3.8.8 or 4.x
    • -
    • MongoDB Server 3.0.x: mongoose ^3.8.22, 4.x, or 5.x
    • -
    • MongoDB Server 3.2.x: mongoose ^4.3.0 or 5.x
    • -
    • MongoDB Server 3.4.x: mongoose ^4.7.3 or 5.x
    • -
    • MongoDB Server 3.6.x: mongoose 5.x
    • -
    • MongoDB Server 4.0.x: mongoose ^5.2.0
    • -
    -

    Note that Mongoose 5.x dropped support for all versions of MongoDB before -3.0.0. If you need to use MongoDB 2.6 or older, use Mongoose 4.x.

    -

    Further Reading

    -

    Are you on Mongoose 3.x and looking to migrate to 4.x? Check out -Moving Forward with Mongoose.js on Pluralsight. -This video course walks you through the new features and backwards breaking -changes in Mongoose 4, so you can upgrade with confidence.

    -
    \ No newline at end of file diff --git a/docs/compatibility.md b/docs/compatibility.md new file mode 100644 index 00000000000..5e4919f592d --- /dev/null +++ b/docs/compatibility.md @@ -0,0 +1,23 @@ +# MongoDB Server Version Compatibility + +Mongoose relies on the [MongoDB Node.js Driver](http://mongodb.github.io/node-mongodb-native/) to talk to MongoDB. +You can refer to [this table](https://docs.mongodb.com/ecosystem/drivers/driver-compatibility-reference/#node-js-driver-compatibility) for up-to-date information as to which version of the MongoDB driver supports which version of MongoDB. + +Below are the [semver](http://semver.org/) ranges representing which versions of mongoose are compatible with the listed versions of MongoDB server. + +* MongoDB Server 2.4.x: mongoose `^3.8` or `4.x` +* MongoDB Server 2.6.x: mongoose `^3.8.8` or `4.x` +* MongoDB Server 3.0.x: mongoose `^3.8.22`, `4.x`, or `5.x` +* MongoDB Server 3.2.x: mongoose `^4.3.0` or `5.x` +* MongoDB Server 3.4.x: mongoose `^4.7.3` or `5.x` +* MongoDB Server 3.6.x: mongoose `5.x` +* MongoDB Server 4.0.x: mongoose `^5.2.0` +* MongoDB Server 4.2.x: mongoose `^5.7.0` +* MongoDB Server 4.4.x: mongoose `^5.10.0` + +Note that Mongoose 5.x dropped support for all versions of MongoDB before 3.0.0. If you need to use MongoDB 2.6 or older, use Mongoose 4.x. + +## Further Reading + +Are you on Mongoose 3.x and looking to migrate to 4.x? Check out [_Moving Forward with Mongoose.js_ on Pluralsight](https://pluralsight.pxf.io/c/1321469/424552/7490?u=https%3A%2F%2Fapp.pluralsight.com%2Flibrary%2Fcourses%2Fmongoosejs-moving-forward%2Ftable-of-contents). +This video course walks you through the new features and backwards breaking changes in Mongoose 4, so you can upgrade with confidence. \ No newline at end of file diff --git a/docs/compatibility.pug b/docs/compatibility.pug deleted file mode 100644 index 1ce8f946aec..00000000000 --- a/docs/compatibility.pug +++ /dev/null @@ -1,53 +0,0 @@ -extends layout - -append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown - ## MongoDB Server Version Compatibility - - - - - - Mongoose relies on the [MongoDB Node.js Driver](http://mongodb.github.io/node-mongodb-native/) - to talk to MongoDB. You can refer to - [this table](https://docs.mongodb.com/ecosystem/drivers/driver-compatibility-reference/#node-js-driver-compatibility) - for up-to-date information as to which version of the MongoDB driver - supports which version of MongoDB. - - Below are the [semver](http://semver.org/) ranges representing which - versions of mongoose are compatible with the listed versions of MongoDB - server. - - * MongoDB Server 2.4.x: mongoose `^3.8` or `4.x` - * MongoDB Server 2.6.x: mongoose `^3.8.8` or `4.x` - * MongoDB Server 3.0.x: mongoose `^3.8.22`, `4.x`, or `5.x` - * MongoDB Server 3.2.x: mongoose `^4.3.0` or `5.x` - * MongoDB Server 3.4.x: mongoose `^4.7.3` or `5.x` - * MongoDB Server 3.6.x: mongoose `5.x` - * MongoDB Server 4.0.x: mongoose `^5.2.0` - * MongoDB Server 4.2.x: mongoose `^5.7.0` - * MongoDB Server 4.4.x: mongoose `^5.10.0` - - Note that Mongoose 5.x dropped support for all versions of MongoDB before - 3.0.0. If you need to use MongoDB 2.6 or older, use Mongoose 4.x. - - ## Further Reading - - Are you on Mongoose 3.x and looking to migrate to 4.x? Check out - [_Moving Forward with Mongoose.js_ on Pluralsight](https://pluralsight.pxf.io/c/1321469/424552/7490?u=https%3A%2F%2Fapp.pluralsight.com%2Flibrary%2Fcourses%2Fmongoosejs-moving-forward%2Ftable-of-contents). - This video course walks you through the new features and backwards breaking - changes in Mongoose 4, so you can upgrade with confidence. diff --git a/docs/connections.html b/docs/connections.html deleted file mode 100644 index 554169e5bf0..00000000000 --- a/docs/connections.html +++ /dev/null @@ -1,319 +0,0 @@ -Mongoose v5.6.0: Connecting to MongoDB

    Connections

    - - - - - -

    You can connect to MongoDB with the mongoose.connect() method.

    -
    mongoose.connect('mongodb://localhost:27017/myapp', {useNewUrlParser: true});
    -

    This is the minimum needed to connect the myapp database running locally -on the default port (27017). If connecting fails on your machine, try using -127.0.0.1 instead of localhost.

    -

    You can also specify several more parameters in the uri:

    -
    mongoose.connect('mongodb://username:password@host:port/database?options...', {useNewUrlParser: true});
    -

    See the mongodb connection string spec for more detail.

    - - -

    Operation Buffering

    - -

    Mongoose lets you start using your models immediately, without waiting for -mongoose to establish a connection to MongoDB.

    -
    mongoose.connect('mongodb://localhost:27017/myapp', {useNewUrlParser: true});
    -var MyModel = mongoose.model('Test', new Schema({ name: String }));
    -// Works
    -MyModel.findOne(function(error, result) { /* ... */ });
    -

    That's because mongoose buffers model function calls internally. This -buffering is convenient, but also a common source of confusion. Mongoose -will not throw any errors by default if you use a model without -connecting.

    -
    var MyModel = mongoose.model('Test', new Schema({ name: String }));
    -// Will just hang until mongoose successfully connects
    -MyModel.findOne(function(error, result) { /* ... */ });
    -
    -setTimeout(function() {
    -  mongoose.connect('mongodb://localhost:27017/myapp', {useNewUrlParser: true});
    -}, 60000);
    -

    To disable buffering, turn off the bufferCommands option on your schema. -If you have bufferCommands on and your connection is hanging, try turning -bufferCommands off to see if you haven't opened a connection properly. -You can also disable bufferCommands globally:

    -
    mongoose.set('bufferCommands', false);
    -

    Options

    - -

    The connect method also accepts an options object which will be passed -on to the underlying MongoDB driver.

    -
    mongoose.connect(uri, options);
    -

    A full list of options can be found on the MongoDB Node.js driver docs for connect(). -Mongoose passes options to the driver without modification, modulo a few -exceptions that are explained below.

    -
      -
    • bufferCommands - This is a mongoose-specific option (not passed to the MongoDB driver) that disables mongoose's buffering mechanism
    • -
    • user/pass - The username and password for authentication. These options are mongoose-specific, they are equivalent to the MongoDB driver's auth.user and auth.password options.
    • -
    • autoIndex - By default, mongoose will automatically build indexes defined in your schema when it connects. This is great for development, but not ideal for large production deployments, because index builds can cause performance degradation. If you set autoIndex to false, mongoose will not automatically build indexes for any model associated with this connection.
    • -
    • dbName - Specifies which database to connect to and overrides any database specified in the connection string. If you're using the mongodb+srv syntax to connect to MongoDB Atlas, you should use dbName to specify the database because you currently cannot in the connection string.
    • -
    -

    Below are some of the options that are important for tuning mongoose.

    -
      -
    • useNewUrlParser - The underlying MongoDB driver has deprecated their current connection string parser. Because this is a major change, they added the useNewUrlParser flag to allow users to fall back to the old parser if they find a bug in the new parser. You should set useNewUrlParser: true unless that prevents you from connecting. Note that if you specify useNewUrlParser: true, you must specify a port in your connection string, like mongodb://localhost:27017/dbname. The new url parser does not support connection strings that do not have a port, like mongodb://localhost/dbname.
    • -
    • useCreateIndex - False by default. Set to true to make Mongoose's default index build use createIndex() instead of ensureIndex() to avoid deprecation warnings from the MongoDB driver.
    • -
    • useFindAndModify - True by default. Set to false to make findOneAndUpdate() and findOneAndRemove() use native findOneAndUpdate() rather than findAndModify().
    • -
    • autoReconnect - The underlying MongoDB driver will automatically try to reconnect when it loses connection to MongoDB. Unless you are an extremely advanced user that wants to manage their own connection pool, do not set this option to false.
    • -
    • reconnectTries - If you're connected to a single server or mongos proxy (as opposed to a replica set), the MongoDB driver will try to reconnect every reconnectInterval milliseconds for reconnectTries times, and give up afterward. When the driver gives up, the mongoose connection emits a reconnectFailed event. This option does nothing for replica set connections.
    • -
    • reconnectInterval - See reconnectTries
    • -
    • promiseLibrary - sets the underlying driver's promise library
    • -
    • poolSize - The maximum number of sockets the MongoDB driver will keep open for this connection. By default, poolSize is 5. Keep in mind that, as of MongoDB 3.4, MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding.
    • -
    • bufferMaxEntries - The MongoDB driver also has its own buffering mechanism that kicks in when the driver is disconnected. Set this option to 0 and set bufferCommands to false on your schemas if you want your database operations to fail immediately when the driver is not connected, as opposed to waiting for reconnection.
    • -
    • connectTimeoutMS - How long the MongoDB driver will wait before killing a socket due to inactivity during initial connection. Defaults to 30000. This option is passed transparently to Node.js' socket#setTimeout() function.
    • -
    • socketTimeoutMS - How long the MongoDB driver will wait before killing a socket due to inactivity after initial connection. A socket may be inactive because of either no activity or a long-running operation. This is set to 30000 by default, you should set this to 2-3x your longest running operation if you expect some of your database operations to run longer than 20 seconds. This option is passed to Node.js socket#setTimeout() function after the MongoDB driver successfully completes.
    • -
    • family - Whether to connect using IPv4 or IPv6. This option passed to Node.js' dns.lookup() function. If you don't specify this option, the MongoDB driver will try IPv6 first and then IPv4 if IPv6 fails. If your mongoose.connect(uri) call takes a long time, try mongoose.connect(uri, { family: 4 })
    • -
    -

    Example:

    -
    const options = {
    -  useNewUrlParser: true,
    -  useCreateIndex: true,
    -  useFindAndModify: false,
    -  autoIndex: false, // Don't build indexes
    -  reconnectTries: Number.MAX_VALUE, // Never stop trying to reconnect
    -  reconnectInterval: 500, // Reconnect every 500ms
    -  poolSize: 10, // Maintain up to 10 socket connections
    -  // If not connected, return errors immediately rather than waiting for reconnect
    -  bufferMaxEntries: 0,
    -  connectTimeoutMS: 10000, // Give up initial connection after 10 seconds
    -  socketTimeoutMS: 45000, // Close sockets after 45 seconds of inactivity
    -  family: 4 // Use IPv4, skip trying IPv6
    -};
    -mongoose.connect(uri, options);
    -

    See this page for more information about connectTimeoutMS and socketTimeoutMS

    -

    Callback

    - -

    The connect() function also accepts a callback parameter and returns a -promise.

    -
    mongoose.connect(uri, options, function(error) {
    -  // Check error in initial connection. There is no 2nd param to the callback.
    -});
    -
    -// Or using promises
    -mongoose.connect(uri, options).then(
    -  () => { /** ready to use. The `mongoose.connect()` promise resolves to undefined. */ },
    -  err => { /** handle initial connection error */ }
    -);
    -

    Connection String Options

    - -

    You can also specify driver options in your connection string as -parameters in the query string -portion of the URI. This only applies to options passed to the MongoDB -driver. You can't set Mongoose-specific options like bufferCommands -in the query string.

    -
    mongoose.connect('mongodb://localhost:27017/test?connectTimeoutMS=1000&bufferCommands=false');
    -// The above is equivalent to:
    -mongoose.connect('mongodb://localhost:27017/test', {
    -  connectTimeoutMS: 1000
    -  // Note that mongoose will **not** pull `bufferCommands` from the query string
    -});
    -

    The disadvantage of putting options in the query string is that query -string options are harder to read. The advantage is that you only need a -single configuration option, the URI, rather than separate options for -socketTimeoutMS, connectTimeoutMS, etc. Best practice is to put options -that likely differ between development and production, like replicaSet -or ssl, in the connection string, and options that should remain constant, -like connectTimeoutMS or poolSize, in the options object.

    -

    The MongoDB docs have a full list of -supported connection string options

    -

    Connection Events

    - -

    Connections inherit from Node.js' EventEmitter class, -and emit events when something happens to the connection, like losing -connectivity to the MongoDB server. Below is a list of events that a -connection may emit.

    -
      -
    • connecting: Emitted when Mongoose starts making its initial connection to the MongoDB server
    • -
    • connected: Emitted when Mongoose successfully makes its initial connection to the MongoDB server
    • -
    • open: Equivalent to connected
    • -
    • disconnecting: Your app called Connection#close() to disconnect from MongoDB
    • -
    • disconnected: Emitted when Mongoose lost connection to the MongoDB server. This event may be due to your code explicitly closing the connection, the database server crashing, or network connectivity issues.
    • -
    • close: Emitted after Connection#close() successfully closes the connection. If you call conn.close(), you'll get both a 'disconnected' event and a 'close' event.
    • -
    • reconnected: Emitted if Mongoose lost connectivity to MongoDB and successfully reconnected. Mongoose attempts to automatically reconnect when it loses connection to the database.
    • -
    • error: Emitted if an error occurs on a connection, like a parseError due to malformed data or a payload larger than 16MB.
    • -
    • fullsetup: Emitted when you're connecting to a replica set and Mongoose has successfully connected to the primary and at least one secondary.
    • -
    • all: Emitted when you're connecting to a replica set and Mongoose has successfully connected to all servers specified in your connection string.
    • -
    • reconnectFailed: Emitted when you're connected to a standalone server and Mongoose has run out of reconnectTries. The MongoDB driver will no longer attempt to reconnect after this event is emitted. This event will never be emitted if you're connected to a replica set.
    • -
    -

    A note about keepAlive

    - -

    For long running applications, it is often prudent to enable keepAlive -with a number of milliseconds. Without it, after some period of time -you may start to see "connection closed" errors for what seems like -no reason. If so, after -reading this, -you may decide to enable keepAlive:

    -
    mongoose.connect(uri, { keepAlive: true, keepAliveInitialDelay: 300000 });
    -

    keepAliveInitialDelay is the number of milliseconds to wait before initiating keepAlive on the socket. -keepAlive is true by default since mongoose 5.2.0.

    -

    Replica Set Connections

    - -

    To connect to a replica set you pass a comma delimited list of hosts to -connect to rather than a single host.

    -
    mongoose.connect('mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]' [, options]);
    -

    For example:

    -
    mongoose.connect('mongodb://user:pw@host1.com:27017,host2.com:27017,host3.com:27017/testdb');
    -

    To connect to a single node replica set, specify the replicaSet option.

    -
    mongoose.connect('mongodb://host1:port1/?replicaSet=rsName');
    -

    Replica Set Host Names

    - -

    MongoDB replica sets rely on being able to reliably figure out the domain name -for each member. On Linux and OSX, the MongoDB server uses the output of the -hostname command to figure out the -domain name to report to the replica set. This can cause confusing errors -if you're connecting to a remote MongoDB replica set running on a machine that -reports its hostname as localhost:

    -
    // Can get this error even if your connection string doesn't include
    -// `localhost` if `rs.conf()` reports that one replica set member has
    -// `localhost` as its host name.
    -failed to connect to server [localhost:27017] on first connect

    If you're experiencing a similar error, connect to the replica set using the -mongo shell and run the -rs.conf() command to check the host names of each replica set member. Follow -this page's instructions to change a replica set member's host name.

    -

    Multi-mongos support

    - -

    You can also connect to multiple mongos instances -for high availability in a sharded cluster. You do -not need to pass any special options to connect to multiple mongos in mongoose 5.x.

    -
    // Connect to 2 mongos servers
    -mongoose.connect('mongodb://mongosA:27501,mongosB:27501', cb);
    -

    Multiple connections

    - -

    So far we've seen how to connect to MongoDB using Mongoose's default -connection. At times we may need multiple connections open to Mongo, each -with different read/write settings, or maybe just to different databases for -example. In these cases we can utilize mongoose.createConnection() which -accepts all the arguments already discussed and returns a fresh connection -for you.

    -
    const conn = mongoose.createConnection('mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]', options);
    -

    This connection object is then used to -create and retrieve models. Models are -always scoped to a single connection.

    -

    Mongoose creates a default connection when you call mongoose.connect(). -You can access the default connection using mongoose.connection.

    -

    Connection Pools

    - -

    Each connection, whether created with mongoose.connect or -mongoose.createConnection are all backed by an internal configurable -connection pool defaulting to a maximum size of 5. Adjust the pool size -using your connection options:

    -
    // With object options
    -mongoose.createConnection(uri, { poolSize: 4 });
    -
    -const uri = 'mongodb://localhost:27017/test?poolSize=4';
    -mongoose.createConnection(uri);
    -

    Option Changes in v5.x

    - -

    You may see the following deprecation warning if upgrading from 4.x to 5.x -and you didn't use the useMongoClient option in 4.x:

    -
    the server/replset/mongos options are deprecated, all their options are supported at the top level of the options object

    In older version of the MongoDB driver you had to specify distinct options -for server connections, replica set connections, and mongos connections:

    -
    mongoose.connect(myUri, {
    -  server: {
    -    socketOptions: {
    -      socketTimeoutMS: 0,
    -      keepAlive: true
    -    },
    -    reconnectTries: 30
    -  },
    -  replset: {
    -    socketOptions: {
    -      socketTimeoutMS: 0,
    -      keepAlive: true
    -    },
    -    reconnectTries: 30
    -  },
    -  mongos: {
    -    socketOptions: {
    -      socketTimeoutMS: 0,
    -      keepAlive: true
    -    },
    -    reconnectTries: 30
    -  }
    -});
    -

    In mongoose v5.x you can instead declare these options at the top level, -without all that extra nesting. -Here's the list of all supported options.

    -
    // Equivalent to the above code
    -mongoose.connect(myUri, {
    -  socketTimeoutMS: 0,
    -  keepAlive: true,
    -  reconnectTries: 30
    -});
    -

    Next Up

    - -

    Now that we've covered connections, let's take a look at models.

    -
    \ No newline at end of file diff --git a/docs/connections.md b/docs/connections.md new file mode 100644 index 00000000000..c77239df486 --- /dev/null +++ b/docs/connections.md @@ -0,0 +1,529 @@ +## Connections + +You can connect to MongoDB with the `mongoose.connect()` method. + +```javascript +mongoose.connect('mongodb://localhost:27017/myapp', {useNewUrlParser: true}); +``` + +This is the minimum needed to connect the `myapp` database running locally +on the default port (27017). If connecting fails on your machine, try using +`127.0.0.1` instead of `localhost`. + +You can also specify several more parameters in the `uri`: + +```javascript +mongoose.connect('mongodb://username:password@host:port/database?options...', {useNewUrlParser: true}); +``` + +See the [mongodb connection string spec](http://docs.mongodb.org/manual/reference/connection-string/) for more detail. + + + +

    Operation Buffering

    + +Mongoose lets you start using your models immediately, without waiting for +mongoose to establish a connection to MongoDB. + +```javascript +mongoose.connect('mongodb://localhost:27017/myapp', {useNewUrlParser: true}); +const MyModel = mongoose.model('Test', new Schema({ name: String })); +// Works +MyModel.findOne(function(error, result) { /* ... */ }); +``` + +That's because mongoose buffers model function calls internally. This +buffering is convenient, but also a common source of confusion. Mongoose +will *not* throw any errors by default if you use a model without +connecting. + +```javascript +const MyModel = mongoose.model('Test', new Schema({ name: String })); +// Will just hang until mongoose successfully connects +MyModel.findOne(function(error, result) { /* ... */ }); + +setTimeout(function() { + mongoose.connect('mongodb://localhost:27017/myapp', {useNewUrlParser: true}); +}, 60000); +``` + +To disable buffering, turn off the [`bufferCommands` option on your schema](./guide.html#bufferCommands). +If you have `bufferCommands` on and your connection is hanging, try turning +`bufferCommands` off to see if you haven't opened a connection properly. +You can also disable `bufferCommands` globally: + +```javascript +mongoose.set('bufferCommands', false); +``` + +Note that buffering is also responsible for waiting until Mongoose +creates collections if you use the [`autoCreate` option](/docs/guide.html#autoCreate). +If you disable buffering, you should also disable the `autoCreate` +option and use [`createCollection()`](/docs/api/model.html#model_Model.createCollection) +to create [capped collections](/docs/guide.html#capped) or +[collections with collations](/docs/guide.html#collation). + +```javascript +const schema = new Schema({ + name: String +}, { + capped: { size: 1024 }, + bufferCommands: false, + autoCreate: false // disable `autoCreate` since `bufferCommands` is false +}); + +const Model = mongoose.model('Test', schema); +// Explicitly create the collection before using it +// so the collection is capped. +await Model.createCollection(); +``` + +

    Error Handling

    + +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. + +To handle initial connection errors, you should use `.catch()` or `try/catch` with async/await. + +```javascript +mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true }). + catch(error => handleError(error)); + +// Or: +try { + await mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true }); +} catch (error) { + handleError(error); +} +``` + +To handle errors after initial connection was established, you should +listen for error events on the connection. However, you still need to +handle initial connection errors as shown above. + +```javascript +mongoose.connection.on('error', err => { + logError(err); +}); +``` + +Note that Mongoose does not necessarily emit an 'error' event if it loses connectivity to MongoDB. You should +listen to the `disconnected` event to report when Mongoose is disconnected from MongoDB. + +

    Options

    + +The `connect` method also accepts an `options` object which will be passed +on to the underlying MongoDB driver. + +```javascript +mongoose.connect(uri, options); +``` + +A full list of options can be found on the [MongoDB Node.js driver docs for `connect()`](http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html#connect). +Mongoose passes options to the driver without modification, modulo a few +exceptions that are explained below. + +* `bufferCommands` - This is a mongoose-specific option (not passed to the MongoDB driver) that disables [Mongoose's buffering mechanism](http://mongoosejs.com/docs/faq.html#callback_never_executes) +* `user`/`pass` - The username and password for authentication. These options are Mongoose-specific, they are equivalent to the MongoDB driver's `auth.user` and `auth.password` options. +* `autoIndex` - By default, mongoose will automatically build indexes defined in your schema when it connects. This is great for development, but not ideal for large production deployments, because index builds can cause performance degradation. If you set `autoIndex` to false, mongoose will not automatically build indexes for **any** model associated with this connection. +* `dbName` - Specifies which database to connect to and overrides any database specified in the connection string. This is useful if you are unable to specify a default database in the connection string like with [some `mongodb+srv` syntax connections](https://stackoverflow.com/questions/48917591/fail-to-connect-mongoose-to-atlas/48917626#48917626). + +Below are some of the options that are important for tuning Mongoose. + +* `useNewUrlParser` - The underlying MongoDB driver has deprecated their current [connection string](https://docs.mongodb.com/manual/reference/connection-string/) parser. Because this is a major change, they added the `useNewUrlParser` flag to allow users to fall back to the old parser if they find a bug in the new parser. You should set `useNewUrlParser: true` unless that prevents you from connecting. Note that if you specify `useNewUrlParser: true`, you **must** specify a port in your connection string, like `mongodb://localhost:27017/dbname`. The new url parser does _not_ support connection strings that do not have a port, like `mongodb://localhost/dbname`. +* `useCreateIndex` - False by default. Set to `true` to make Mongoose's default index build use `createIndex()` instead of `ensureIndex()` to avoid deprecation warnings from the MongoDB driver. +* `useFindAndModify` - True by default. Set to `false` to make `findOneAndUpdate()` and `findOneAndRemove()` use native `findOneAndUpdate()` rather than `findAndModify()`. +* `useUnifiedTopology`- False by default. Set to `true` to opt in to using [the MongoDB driver's new connection management engine](/docs/deprecations.html#useunifiedtopology). You should set this option to `true`, except for the unlikely case that it prevents you from maintaining a stable connection. +* `promiseLibrary` - Sets the [underlying driver's promise library](http://mongodb.github.io/node-mongodb-native/3.1/api/MongoClient.html). +* `poolSize` - The maximum number of sockets the MongoDB driver will keep open for this connection. By default, `poolSize` is 5. Keep in mind that, as of MongoDB 3.4, MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). +* `socketTimeoutMS` - How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. A socket may be inactive because of either no activity or a long-running operation. This is set to `30000` by default, you should set this to 2-3x your longest running operation if you expect some of your database operations to run longer than 20 seconds. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes. +* `family` - Whether to connect using IPv4 or IPv6. This option passed to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. If you don't specify this option, the MongoDB driver will try IPv6 first and then IPv4 if IPv6 fails. If your `mongoose.connect(uri)` call takes a long time, try `mongoose.connect(uri, { family: 4 })` +* `authSource` - The database to use when authenticating with `user` and `pass`. In MongoDB, [users are scoped to a database](https://docs.mongodb.com/manual/tutorial/manage-users-and-roles/). If you are getting an unexpected login failure, you may need to set this option. + +The following options are important for tuning Mongoose only if you are +running **without** [the `useUnifiedTopology` option](/docs/deprecations.html#useunifiedtopology): + +* `autoReconnect` - The underlying MongoDB driver will automatically try to reconnect when it loses connection to MongoDB. Unless you are an extremely advanced user that wants to manage their own connection pool, do **not** set this option to `false`. +* `reconnectTries` - If you're connected to a single server or mongos proxy (as opposed to a replica set), the MongoDB driver will try to reconnect every `reconnectInterval` milliseconds for `reconnectTries` times, and give up afterward. When the driver gives up, the mongoose connection emits a `reconnectFailed` event. This option does nothing for replica set connections. +* `reconnectInterval` - See `reconnectTries` +* `bufferMaxEntries` - The MongoDB driver also has its own buffering mechanism that kicks in when the driver is disconnected. Set this option to 0 and set `bufferCommands` to `false` on your schemas if you want your database operations to fail immediately when the driver is not connected, as opposed to waiting for reconnection. +* `connectTimeoutMS` - How long the MongoDB driver will wait before killing a socket due to inactivity _during initial connection_. Defaults to 30000. This option is passed transparently to [Node.js' `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback). + +The following options are important for tuning Mongoose only if you are +running **with** [the `useUnifiedTopology` option](/docs/deprecations.html#useunifiedtopology): + +* `serverSelectionTimeoutMS` - With `useUnifiedTopology`, the MongoDB driver will try to find a server to send any given operation to, and keep retrying for `serverSelectionTimeoutMS` milliseconds. If not set, the MongoDB driver defaults to using `30000` (30 seconds). +* `heartbeatFrequencyMS` - With `useUnifiedTopology`, the MongoDB driver sends a heartbeat every `heartbeatFrequencyMS` to check on the status of the connection. A heartbeat is subject to `serverSelectionTimeoutMS`, so the MongoDB driver will retry failed heartbeats for up to 30 seconds by default. Mongoose only emits a `'disconnected'` event after a heartbeat has failed, so you may want to decrease this setting to reduce the time between when your server goes down and when Mongoose emits `'disconnected'`. We recommend you do **not** set this setting below 1000, too many heartbeats can lead to performance degradation. + +The `serverSelectionTimeoutMS` option also handles how long `mongoose.connect()` will +retry initial connection before erroring out. With `useUnifiedTopology`, `mongoose.connect()` +will retry for 30 seconds by default (default `serverSelectionTimeoutMS`) before +erroring out. To get faster feedback on failed operations, you can reduce `serverSelectionTimeoutMS` +to 5000 as shown below. + +Example: + +```javascript +const options = { + useNewUrlParser: true, + useUnifiedTopology: true, + useCreateIndex: true, + useFindAndModify: false, + autoIndex: false, // Don't build indexes + poolSize: 10, // Maintain up to 10 socket connections + serverSelectionTimeoutMS: 5000, // Keep trying to send operations for 5 seconds + socketTimeoutMS: 45000, // Close sockets after 45 seconds of inactivity + family: 4 // Use IPv4, skip trying IPv6 +}; +mongoose.connect(uri, options); +``` + +See [this page](http://mongodb.github.io/node-mongodb-native/3.1/reference/faq/) for more information about `connectTimeoutMS` and `socketTimeoutMS` + +

    Callback

    + +The `connect()` function also accepts a callback parameter and returns a +[promise](./promises.html). + +```javascript +mongoose.connect(uri, options, function(error) { + // Check error in initial connection. There is no 2nd param to the callback. +}); + +// Or using promises +mongoose.connect(uri, options).then( + () => { /** ready to use. The `mongoose.connect()` promise resolves to mongoose instance. */ }, + err => { /** handle initial connection error */ } +); +``` + +

    Connection String Options

    + +You can also specify driver options in your connection string as +[parameters in the query string](https://en.wikipedia.org/wiki/Query_string) +portion of the URI. This only applies to options passed to the MongoDB +driver. You **can't** set Mongoose-specific options like `bufferCommands` +in the query string. + +```javascript +mongoose.connect('mongodb://localhost:27017/test?connectTimeoutMS=1000&bufferCommands=false&authSource=otherdb'); +// The above is equivalent to: +mongoose.connect('mongodb://localhost:27017/test', { + connectTimeoutMS: 1000 + // Note that mongoose will **not** pull `bufferCommands` from the query string +}); +``` + +The disadvantage of putting options in the query string is that query +string options are harder to read. The advantage is that you only need a +single configuration option, the URI, rather than separate options for +`socketTimeoutMS`, `connectTimeoutMS`, etc. Best practice is to put options +that likely differ between development and production, like `replicaSet` +or `ssl`, in the connection string, and options that should remain constant, +like `connectTimeoutMS` or `poolSize`, in the options object. + +The MongoDB docs have a full list of +[supported connection string options](https://docs.mongodb.com/manual/reference/connection-string/). +Below are some options that are often useful to set in the connection string because they +are closely associated with the hostname and authentication information. + +* `authSource` - The database to use when authenticating with `user` and `pass`. In MongoDB, [users are scoped to a database](https://docs.mongodb.com/manual/tutorial/manage-users-and-roles/). If you are getting an unexpected login failure, you may need to set this option. +* `family` - Whether to connect using IPv4 or IPv6. This option passed to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. If you don't specify this option, the MongoDB driver will try IPv6 first and then IPv4 if IPv6 fails. If your `mongoose.connect(uri)` call takes a long time, try `mongoose.connect(uri, { family: 4 })` + +

    Connection Events

    + +Connections inherit from [Node.js' `EventEmitter` class](https://nodejs.org/api/events.html#events_class_eventemitter), +and emit events when something happens to the connection, like losing +connectivity to the MongoDB server. Below is a list of events that a +connection may emit. + +* `connecting`: Emitted when Mongoose starts making its initial connection to the MongoDB server +* `connected`: Emitted when Mongoose successfully makes its initial connection to the MongoDB server, or when Mongoose reconnects after losing connectivity. +* `open`: Equivalent to `connected` +* `disconnecting`: Your app called [`Connection#close()`](api.html#connection_Connection-close) to disconnect from MongoDB +* `disconnected`: Emitted when Mongoose lost connection to the MongoDB server. This event may be due to your code explicitly closing the connection, the database server crashing, or network connectivity issues. +* `close`: Emitted after [`Connection#close()`](api.html#connection_Connection-close) successfully closes the connection. If you call `conn.close()`, you'll get both a 'disconnected' event and a 'close' event. +* `reconnected`: Emitted if Mongoose lost connectivity to MongoDB and successfully reconnected. Mongoose attempts to [automatically reconnect](https://thecodebarbarian.com/managing-connections-with-the-mongodb-node-driver.html) when it loses connection to the database. +* `error`: Emitted if an error occurs on a connection, like a `parseError` due to malformed data or a payload larger than [16MB](https://docs.mongodb.com/manual/reference/limits/#BSON-Document-Size). +* `fullsetup`: Emitted when you're connecting to a replica set and Mongoose has successfully connected to the primary and at least one secondary. +* `all`: Emitted when you're connecting to a replica set and Mongoose has successfully connected to all servers specified in your connection string. +* `reconnectFailed`: Emitted when you're connected to a standalone server and Mongoose has run out of [`reconnectTries`](https://thecodebarbarian.com/managing-connections-with-the-mongodb-node-driver.html#handling-single-server-outages). The [MongoDB driver](http://npmjs.com/package/mongodb) will no longer attempt to reconnect after this event is emitted. This event will never be emitted if you're connected to a replica set. + +When you're connecting to a single MongoDB server (a "standalone"), Mongoose will emit 'disconnected' if it gets +disconnected from the standalone server, and 'connected' if it successfully connects to the standalone. In a +replica set with `useUnifiedTopology = true`, Mongoose will emit 'disconnected' if it loses connectivity to +_every_ server in the replica set, and 'connected' if it manages to reconnect to at least one server in the replica set. + +

    A note about keepAlive

    + +For long running applications, it is often prudent to enable `keepAlive` +with a number of milliseconds. Without it, after some period of time +you may start to see `"connection closed"` errors for what seems like +no reason. If so, after +[reading this](http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html), +you may decide to enable `keepAlive`: + +```javascript +mongoose.connect(uri, { keepAlive: true, keepAliveInitialDelay: 300000 }); +``` + +`keepAliveInitialDelay` is the number of milliseconds to wait before initiating `keepAlive` on the socket. +`keepAlive` is true by default since mongoose 5.2.0. + +

    Replica Set Connections

    + +To connect to a replica set you pass a comma delimited list of hosts to +connect to rather than a single host. + +```javascript +mongoose.connect('mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]' [, options]); +``` + +For example: + +```javascript +mongoose.connect('mongodb://user:pw@host1.com:27017,host2.com:27017,host3.com:27017/testdb'); +``` + +To connect to a single node replica set, specify the `replicaSet` option. + +```javascript +mongoose.connect('mongodb://host1:port1/?replicaSet=rsName'); +``` + +

    Server Selection

    + +If you enable the `useUnifiedTopology` option, the underlying MongoDB driver +will use [server selection](https://github.com/mongodb/specifications/blob/master/source/server-selection/server-selection.rst) +to connect to MongoDB and send operations to MongoDB. If the MongoDB +driver can't find a server to send an operation to after `serverSelectionTimeoutMS`, +you'll get the below error: + +``` +MongoTimeoutError: Server selection timed out after 30000 ms +``` + +You can configure the timeout using the `serverSelectionTimeoutMS` option +to `mongoose.connect()`: + +```javascript +mongoose.connect(uri, { + useNewUrlParser: true, + useUnifiedTopology: true, + serverSelectionTimeoutMS: 5000 // Timeout after 5s instead of 30s +}); +``` + +A `MongoTimeoutError` has a `reason` property that explains why +server selection timed out. For example, if you're connecting to +a standalone server with an incorrect password, `reason` +will contain an "Authentication failed" error. + +```javascript +const mongoose = require('mongoose'); + +const uri = 'mongodb+srv://username:badpw@cluster0-OMITTED.mongodb.net/' + + 'test?retryWrites=true&w=majority'; +// Prints "MongoError: bad auth Authentication failed." +mongoose.connect(uri, { + useNewUrlParser: true, + useUnifiedTopology: true, + serverSelectionTimeoutMS: 5000 +}).catch(err => console.log(err.reason)); +``` + +

    Replica Set Host Names

    + +MongoDB replica sets rely on being able to reliably figure out the domain name +for each member. On Linux and OSX, the MongoDB server uses the output of the +[`hostname` command](https://linux.die.net/man/1/hostname) to figure out the +domain name to report to the replica set. This can cause confusing errors +if you're connecting to a remote MongoDB replica set running on a machine that +reports its `hostname` as `localhost`: + +``` +// Can get this error even if your connection string doesn't include +// `localhost` if `rs.conf()` reports that one replica set member has +// `localhost` as its host name. +failed to connect to server [localhost:27017] on first connect +``` + +If you're experiencing a similar error, connect to the replica set using the +`mongo` shell and run the +[`rs.conf()`](https://docs.mongodb.com/manual/reference/method/rs.conf/) command to check the host names of each replica set member. Follow +[this page's instructions to change a replica set member's host name](https://docs.mongodb.com/manual/tutorial/change-hostnames-in-a-replica-set/#change-hostnames-while-maintaining-replica-set-availability). + +

    Multi-mongos support

    + +You can also connect to multiple [mongos](https://docs.mongodb.com/manual/reference/program/mongos/) instances +for high availability in a sharded cluster. You do +[not need to pass any special options to connect to multiple mongos](http://mongodb.github.io/node-mongodb-native/3.0/tutorials/connect/#connect-to-sharded-cluster) in mongoose 5.x. + +```javascript +// Connect to 2 mongos servers +mongoose.connect('mongodb://mongosA:27501,mongosB:27501', cb); +``` + +

    Multiple connections

    + +So far we've seen how to connect to MongoDB using Mongoose's default +connection. Mongoose creates a _default connection_ when you call `mongoose.connect()`. +You can access the default connection using `mongoose.connection`. + +You may need multiple connections to MongoDB for several reasons. +One reason is if you have multiple databases or multiple MongoDB clusters. +Another reason is to work around [slow trains](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). +The `mongoose.createConnection()` function takes the same arguments as +`mongoose.connect()` and returns a new connection. + +```javascript +const conn = mongoose.createConnection('mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]', options); +``` + +This [connection](./api.html#connection_Connection) object is then used to +create and retrieve [models](./api.html#model_Model). Models are +**always** scoped to a single connection. + +```javascript +const UserModel = conn.model('User', userSchema); +``` + +If you use multiple connections, you should make sure you export schemas, +**not** models. Exporting a model from a file is called the _export model pattern_. +The export model pattern is limited because you can only use one connection. + +```javascript +const userSchema = new Schema({ name: String, email: String }); + +// The alternative to the export model pattern is the export schema pattern. +module.exports = userSchema; + +// Because if you export a model as shown below, the model will be scoped +// to Mongoose's default connection. +// module.exports = mongoose.model('User', userSchema); +``` + +If you use the export schema pattern, you still need to create models +somewhere. There are two common patterns. First is to export a connection +and register the models on the connection in the file: + +```javascript +// connections/fast.js +const mongoose = require('mongoose'); + +const conn = mongoose.createConnection(process.env.MONGODB_URI); +conn.model('User', require('../schemas/user')); + +module.exports = conn; + +// connections/slow.js +const mongoose = require('mongoose'); + +const conn = mongoose.createConnection(process.env.MONGODB_URI); +conn.model('User', require('../schemas/user')); +conn.model('PageView', require('../schemas/pageView')); + +module.exports = conn; +``` + +Another alternative is to register connections with a dependency injector +or another [inversion of control (IOC) pattern](https://thecodebarbarian.com/using-ramda-as-a-dependency-injector). + +```javascript +const mongoose = require('mongoose'); + +module.exports = function connectionFactory() { + const conn = mongoose.createConnection(process.env.MONGODB_URI); + + conn.model('User', require('../schemas/user')); + conn.model('PageView', require('../schemas/pageView')); + + return conn; +}; +``` + +

    Connection Pools

    + +Each `connection`, whether created with `mongoose.connect` or +`mongoose.createConnection` are all backed by an internal configurable +connection pool defaulting to a maximum size of 5. Adjust the pool size +using your connection options: + +```javascript +// With object options +mongoose.createConnection(uri, { poolSize: 4 }); + +const uri = 'mongodb://localhost:27017/test?poolSize=4'; +mongoose.createConnection(uri); +``` + +

    Option Changes in v5.x

    + +You may see the following deprecation warning if upgrading from 4.x to 5.x +and you didn't use the `useMongoClient` option in 4.x: + +``` +the server/replset/mongos options are deprecated, all their options are supported at the top level of the options object +``` + +In older version of the MongoDB driver you had to specify distinct options +for server connections, replica set connections, and mongos connections: + +```javascript +mongoose.connect(myUri, { + server: { + socketOptions: { + socketTimeoutMS: 0, + keepAlive: true + }, + reconnectTries: 30 + }, + replset: { + socketOptions: { + socketTimeoutMS: 0, + keepAlive: true + }, + reconnectTries: 30 + }, + mongos: { + socketOptions: { + socketTimeoutMS: 0, + keepAlive: true + }, + reconnectTries: 30 + } +}); +``` + +In mongoose v5.x you can instead declare these options at the top level, +without all that extra nesting. +[Here's the list of all supported options](http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html). + +```javascript +// Equivalent to the above code +mongoose.connect(myUri, { + socketTimeoutMS: 0, + keepAlive: true, + reconnectTries: 30 +}); +``` + +

    Next Up

    + +Now that we've covered connections, let's take a look at [models](/docs/models.html). \ No newline at end of file diff --git a/docs/connections.pug b/docs/connections.pug deleted file mode 100644 index 64bc912c49b..00000000000 --- a/docs/connections.pug +++ /dev/null @@ -1,552 +0,0 @@ -extends layout - -append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown -

    Connections

    - - - - - - You can connect to MongoDB with the `mongoose.connect()` method. - - ```javascript - mongoose.connect('mongodb://localhost:27017/myapp', {useNewUrlParser: true}); - ``` - - This is the minimum needed to connect the `myapp` database running locally - on the default port (27017). If connecting fails on your machine, try using - `127.0.0.1` instead of `localhost`. - - You can also specify several more parameters in the `uri`: - - ```javascript - mongoose.connect('mongodb://username:password@host:port/database?options...', {useNewUrlParser: true}); - ``` - - See the [mongodb connection string spec](http://docs.mongodb.org/manual/reference/connection-string/) for more detail. - - - -

    Operation Buffering

    - - Mongoose lets you start using your models immediately, without waiting for - mongoose to establish a connection to MongoDB. - - ```javascript - mongoose.connect('mongodb://localhost:27017/myapp', {useNewUrlParser: true}); - const MyModel = mongoose.model('Test', new Schema({ name: String })); - // Works - MyModel.findOne(function(error, result) { /* ... */ }); - ``` - - That's because mongoose buffers model function calls internally. This - buffering is convenient, but also a common source of confusion. Mongoose - will *not* throw any errors by default if you use a model without - connecting. - - ```javascript - const MyModel = mongoose.model('Test', new Schema({ name: String })); - // Will just hang until mongoose successfully connects - MyModel.findOne(function(error, result) { /* ... */ }); - - setTimeout(function() { - mongoose.connect('mongodb://localhost:27017/myapp', {useNewUrlParser: true}); - }, 60000); - ``` - - To disable buffering, turn off the [`bufferCommands` option on your schema](./guide.html#bufferCommands). - If you have `bufferCommands` on and your connection is hanging, try turning - `bufferCommands` off to see if you haven't opened a connection properly. - You can also disable `bufferCommands` globally: - - ```javascript - mongoose.set('bufferCommands', false); - ``` - - Note that buffering is also responsible for waiting until Mongoose - creates collections if you use the [`autoCreate` option](/docs/guide.html#autoCreate). - If you disable buffering, you should also disable the `autoCreate` - option and use [`createCollection()`](/docs/api/model.html#model_Model.createCollection) - to create [capped collections](/docs/guide.html#capped) or - [collections with collations](/docs/guide.html#collation). - - ```javascript - const schema = new Schema({ - name: String - }, { - capped: { size: 1024 }, - bufferCommands: false, - autoCreate: false // disable `autoCreate` since `bufferCommands` is false - }); - - const Model = mongoose.model('Test', schema); - // Explicitly create the collection before using it - // so the collection is capped. - await Model.createCollection(); - ``` - -

    Error Handling

    - - There are two classes of errors that can occur with a Mongoose connection. - - - Error on initial connection. If initial connection fails, Mongoose will **not** attempt to reconnect, it will emit an 'error' event, and the promise `mongoose.connect()` returns will reject. - - 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. - - ```javascript - mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true }). - catch(error => handleError(error)); - - // Or: - try { - await mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true }); - } catch (error) { - handleError(error); - } - ``` - - To handle errors after initial connection was established, you should - listen for error events on the connection. However, you still need to - handle initial connection errors as shown above. - - ```javascript - mongoose.connection.on('error', err => { - logError(err); - }); - ``` - - Note that the `error` event in the code above does not fire when mongoose loses - connection after the initial connection was established. You can listen to the - `disconnected` event for that purpose. - -

    Options

    - - The `connect` method also accepts an `options` object which will be passed - on to the underlying MongoDB driver. - - ```javascript - mongoose.connect(uri, options); - ``` - - A full list of options can be found on the [MongoDB Node.js driver docs for `connect()`](http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html#connect). - Mongoose passes options to the driver without modification, modulo a few - exceptions that are explained below. - - * `bufferCommands` - This is a mongoose-specific option (not passed to the MongoDB driver) that disables [Mongoose's buffering mechanism](http://mongoosejs.com/docs/faq.html#callback_never_executes) - * `user`/`pass` - The username and password for authentication. These options are Mongoose-specific, they are equivalent to the MongoDB driver's `auth.user` and `auth.password` options. - * `autoIndex` - By default, mongoose will automatically build indexes defined in your schema when it connects. This is great for development, but not ideal for large production deployments, because index builds can cause performance degradation. If you set `autoIndex` to false, mongoose will not automatically build indexes for **any** model associated with this connection. - * `dbName` - Specifies which database to connect to and overrides any database specified in the connection string. This is useful if you are unable to specify a default database in the connection string like with [some `mongodb+srv` syntax connections](https://stackoverflow.com/questions/48917591/fail-to-connect-mongoose-to-atlas/48917626#48917626). - - Below are some of the options that are important for tuning Mongoose. - - * `useNewUrlParser` - The underlying MongoDB driver has deprecated their current [connection string](https://docs.mongodb.com/manual/reference/connection-string/) parser. Because this is a major change, they added the `useNewUrlParser` flag to allow users to fall back to the old parser if they find a bug in the new parser. You should set `useNewUrlParser: true` unless that prevents you from connecting. Note that if you specify `useNewUrlParser: true`, you **must** specify a port in your connection string, like `mongodb://localhost:27017/dbname`. The new url parser does _not_ support connection strings that do not have a port, like `mongodb://localhost/dbname`. - * `useCreateIndex` - False by default. Set to `true` to make Mongoose's default index build use `createIndex()` instead of `ensureIndex()` to avoid deprecation warnings from the MongoDB driver. - * `useFindAndModify` - True by default. Set to `false` to make `findOneAndUpdate()` and `findOneAndRemove()` use native `findOneAndUpdate()` rather than `findAndModify()`. - * `useUnifiedTopology`- False by default. Set to `true` to opt in to using [the MongoDB driver's new connection management engine](/docs/deprecations.html#useunifiedtopology). You should set this option to `true`, except for the unlikely case that it prevents you from maintaining a stable connection. - * `promiseLibrary` - Sets the [underlying driver's promise library](http://mongodb.github.io/node-mongodb-native/3.1/api/MongoClient.html). - * `poolSize` - The maximum number of sockets the MongoDB driver will keep open for this connection. By default, `poolSize` is 5. Keep in mind that, as of MongoDB 3.4, MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). - * `socketTimeoutMS` - How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. A socket may be inactive because of either no activity or a long-running operation. This is set to `30000` by default, you should set this to 2-3x your longest running operation if you expect some of your database operations to run longer than 20 seconds. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes. - * `family` - Whether to connect using IPv4 or IPv6. This option passed to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. If you don't specify this option, the MongoDB driver will try IPv6 first and then IPv4 if IPv6 fails. If your `mongoose.connect(uri)` call takes a long time, try `mongoose.connect(uri, { family: 4 })` - * `authSource` - The database to use when authenticating with `user` and `pass`. In MongoDB, [users are scoped to a database](https://docs.mongodb.com/manual/tutorial/manage-users-and-roles/). If you are getting an unexpected login failure, you may need to set this option. - - The following options are important for tuning Mongoose only if you are - running **without** [the `useUnifiedTopology` option](/docs/deprecations.html#useunifiedtopology): - - * `autoReconnect` - The underlying MongoDB driver will automatically try to reconnect when it loses connection to MongoDB. Unless you are an extremely advanced user that wants to manage their own connection pool, do **not** set this option to `false`. - * `reconnectTries` - If you're connected to a single server or mongos proxy (as opposed to a replica set), the MongoDB driver will try to reconnect every `reconnectInterval` milliseconds for `reconnectTries` times, and give up afterward. When the driver gives up, the mongoose connection emits a `reconnectFailed` event. This option does nothing for replica set connections. - * `reconnectInterval` - See `reconnectTries` - * `bufferMaxEntries` - The MongoDB driver also has its own buffering mechanism that kicks in when the driver is disconnected. Set this option to 0 and set `bufferCommands` to `false` on your schemas if you want your database operations to fail immediately when the driver is not connected, as opposed to waiting for reconnection. - * `connectTimeoutMS` - How long the MongoDB driver will wait before killing a socket due to inactivity _during initial connection_. Defaults to 30000. This option is passed transparently to [Node.js' `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback). - - The following options are important for tuning Mongoose only if you are - running **with** [the `useUnifiedTopology` option](/docs/deprecations.html#useunifiedtopology): - - * `serverSelectionTimeoutMS` - With `useUnifiedTopology`, the MongoDB driver will try to find a server to send any given operation to, and keep retrying for `serverSelectionTimeoutMS` milliseconds. If not set, the MongoDB driver defaults to using `30000` (30 seconds). - * `heartbeatFrequencyMS` - With `useUnifiedTopology`, the MongoDB driver sends a heartbeat every `heartbeatFrequencyMS` to check on the status of the connection. A heartbeat is subject to `serverSelectionTimeoutMS`, so the MongoDB driver will retry failed heartbeats for up to 30 seconds by default. Mongoose only emits a `'disconnected'` event after a heartbeat has failed, so you may want to decrease this setting to reduce the time between when your server goes down and when Mongoose emits `'disconnected'`. We recommend you do **not** set this setting below 1000, too many heartbeats can lead to performance degradation. - - The `serverSelectionTimeoutMS` option also handles how long `mongoose.connect()` will - retry initial connection before erroring out. With `useUnifiedTopology`, `mongoose.connect()` - will retry for 30 seconds by default (default `serverSelectionTimeoutMS`) before - erroring out. To get faster feedback on failed operations, you can reduce `serverSelectionTimeoutMS` - to 5000 as shown below. - - Example: - - ```javascript - const options = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true, - useFindAndModify: false, - autoIndex: false, // Don't build indexes - poolSize: 10, // Maintain up to 10 socket connections - serverSelectionTimeoutMS: 5000, // Keep trying to send operations for 5 seconds - socketTimeoutMS: 45000, // Close sockets after 45 seconds of inactivity - family: 4 // Use IPv4, skip trying IPv6 - }; - mongoose.connect(uri, options); - ``` - - See [this page](http://mongodb.github.io/node-mongodb-native/3.1/reference/faq/) for more information about `connectTimeoutMS` and `socketTimeoutMS` - -

    Callback

    - - The `connect()` function also accepts a callback parameter and returns a - [promise](./promises.html). - - ```javascript - mongoose.connect(uri, options, function(error) { - // Check error in initial connection. There is no 2nd param to the callback. - }); - - // Or using promises - mongoose.connect(uri, options).then( - () => { /** ready to use. The `mongoose.connect()` promise resolves to mongoose instance. */ }, - err => { /** handle initial connection error */ } - ); - ``` - -

    Connection String Options

    - - You can also specify driver options in your connection string as - [parameters in the query string](https://en.wikipedia.org/wiki/Query_string) - portion of the URI. This only applies to options passed to the MongoDB - driver. You **can't** set Mongoose-specific options like `bufferCommands` - in the query string. - - ```javascript - mongoose.connect('mongodb://localhost:27017/test?connectTimeoutMS=1000&bufferCommands=false&authSource=otherdb'); - // The above is equivalent to: - mongoose.connect('mongodb://localhost:27017/test', { - connectTimeoutMS: 1000 - // Note that mongoose will **not** pull `bufferCommands` from the query string - }); - ``` - - The disadvantage of putting options in the query string is that query - string options are harder to read. The advantage is that you only need a - single configuration option, the URI, rather than separate options for - `socketTimeoutMS`, `connectTimeoutMS`, etc. Best practice is to put options - that likely differ between development and production, like `replicaSet` - or `ssl`, in the connection string, and options that should remain constant, - like `connectTimeoutMS` or `poolSize`, in the options object. - - The MongoDB docs have a full list of - [supported connection string options](https://docs.mongodb.com/manual/reference/connection-string/). - Below are some options that are often useful to set in the connection string because they - are closely associated with the hostname and authentication information. - - * `authSource` - The database to use when authenticating with `user` and `pass`. In MongoDB, [users are scoped to a database](https://docs.mongodb.com/manual/tutorial/manage-users-and-roles/). If you are getting an unexpected login failure, you may need to set this option. - * `family` - Whether to connect using IPv4 or IPv6. This option passed to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. If you don't specify this option, the MongoDB driver will try IPv6 first and then IPv4 if IPv6 fails. If your `mongoose.connect(uri)` call takes a long time, try `mongoose.connect(uri, { family: 4 })` - -

    Connection Events

    - - Connections inherit from [Node.js' `EventEmitter` class](https://nodejs.org/api/events.html#events_class_eventemitter), - and emit events when something happens to the connection, like losing - connectivity to the MongoDB server. Below is a list of events that a - connection may emit. - - * `connecting`: Emitted when Mongoose starts making its initial connection to the MongoDB server - * `connected`: Emitted when Mongoose successfully makes its initial connection to the MongoDB server, or when Mongoose reconnects after losing connectivity. - * `open`: Equivalent to `connected` - * `disconnecting`: Your app called [`Connection#close()`](api.html#connection_Connection-close) to disconnect from MongoDB - * `disconnected`: Emitted when Mongoose lost connection to the MongoDB server. This event may be due to your code explicitly closing the connection, the database server crashing, or network connectivity issues. - * `close`: Emitted after [`Connection#close()`](api.html#connection_Connection-close) successfully closes the connection. If you call `conn.close()`, you'll get both a 'disconnected' event and a 'close' event. - * `reconnected`: Emitted if Mongoose lost connectivity to MongoDB and successfully reconnected. Mongoose attempts to [automatically reconnect](https://thecodebarbarian.com/managing-connections-with-the-mongodb-node-driver.html) when it loses connection to the database. - * `error`: Emitted if an error occurs on a connection, like a `parseError` due to malformed data or a payload larger than [16MB](https://docs.mongodb.com/manual/reference/limits/#BSON-Document-Size). - * `fullsetup`: Emitted when you're connecting to a replica set and Mongoose has successfully connected to the primary and at least one secondary. - * `all`: Emitted when you're connecting to a replica set and Mongoose has successfully connected to all servers specified in your connection string. - * `reconnectFailed`: Emitted when you're connected to a standalone server and Mongoose has run out of [`reconnectTries`](https://thecodebarbarian.com/managing-connections-with-the-mongodb-node-driver.html#handling-single-server-outages). The [MongoDB driver](http://npmjs.com/package/mongodb) will no longer attempt to reconnect after this event is emitted. This event will never be emitted if you're connected to a replica set. - - When you're connecting to a single MongoDB server (a "standalone"), Mongoose will emit 'disconnected' if it gets - disconnected from the standalone server, and 'connected' if it successfully connects to the standalone. In a - replica set with `useUnifiedTopology = true`, Mongoose will emit 'disconnected' if it loses connectivity to - _every_ server in the replica set, and 'connected' if it manages to reconnect to at least one server in the replica set. - -

    A note about keepAlive

    - - For long running applications, it is often prudent to enable `keepAlive` - with a number of milliseconds. Without it, after some period of time - you may start to see `"connection closed"` errors for what seems like - no reason. If so, after - [reading this](http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html), - you may decide to enable `keepAlive`: - - ```javascript - mongoose.connect(uri, { keepAlive: true, keepAliveInitialDelay: 300000 }); - ``` - - `keepAliveInitialDelay` is the number of milliseconds to wait before initiating `keepAlive` on the socket. - `keepAlive` is true by default since mongoose 5.2.0. - -

    Replica Set Connections

    - - To connect to a replica set you pass a comma delimited list of hosts to - connect to rather than a single host. - - ```javascript - mongoose.connect('mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]' [, options]); - ``` - - For example: - - ```javascript - mongoose.connect('mongodb://user:pw@host1.com:27017,host2.com:27017,host3.com:27017/testdb'); - ``` - - To connect to a single node replica set, specify the `replicaSet` option. - - ```javascript - mongoose.connect('mongodb://host1:port1/?replicaSet=rsName'); - ``` - -

    Server Selection

    - - If you enable the `useUnifiedTopology` option, the underlying MongoDB driver - will use [server selection](https://github.com/mongodb/specifications/blob/master/source/server-selection/server-selection.rst) - to connect to MongoDB and send operations to MongoDB. If the MongoDB - driver can't find a server to send an operation to after `serverSelectionTimeoutMS`, - you'll get the below error: - - ``` - MongoTimeoutError: Server selection timed out after 30000 ms - ``` - - You can configure the timeout using the `serverSelectionTimeoutMS` option - to `mongoose.connect()`: - - ```javascript - mongoose.connect(uri, { - useNewUrlParser: true, - useUnifiedTopology: true, - serverSelectionTimeoutMS: 5000 // Timeout after 5s instead of 30s - }); - ``` - - A `MongoTimeoutError` has a `reason` property that explains why - server selection timed out. For example, if you're connecting to - a standalone server with an incorrect password, `reason` - will contain an "Authentication failed" error. - - ```javascript - const mongoose = require('mongoose'); - - const uri = 'mongodb+srv://username:badpw@cluster0-OMITTED.mongodb.net/' + - 'test?retryWrites=true&w=majority'; - // Prints "MongoError: bad auth Authentication failed." - mongoose.connect(uri, { - useNewUrlParser: true, - useUnifiedTopology: true, - serverSelectionTimeoutMS: 5000 - }).catch(err => console.log(err.reason)); - ``` - -

    Replica Set Host Names

    - - MongoDB replica sets rely on being able to reliably figure out the domain name - for each member. On Linux and OSX, the MongoDB server uses the output of the - [`hostname` command](https://linux.die.net/man/1/hostname) to figure out the - domain name to report to the replica set. This can cause confusing errors - if you're connecting to a remote MongoDB replica set running on a machine that - reports its `hostname` as `localhost`: - - ``` - // Can get this error even if your connection string doesn't include - // `localhost` if `rs.conf()` reports that one replica set member has - // `localhost` as its host name. - failed to connect to server [localhost:27017] on first connect - ``` - - If you're experiencing a similar error, connect to the replica set using the - `mongo` shell and run the - [`rs.conf()`](https://docs.mongodb.com/manual/reference/method/rs.conf/) command to check the host names of each replica set member. Follow - [this page's instructions to change a replica set member's host name](https://docs.mongodb.com/manual/tutorial/change-hostnames-in-a-replica-set/#change-hostnames-while-maintaining-replica-set-availability). - -

    Multi-mongos support

    - - You can also connect to multiple [mongos](https://docs.mongodb.com/manual/reference/program/mongos/) instances - for high availability in a sharded cluster. You do - [not need to pass any special options to connect to multiple mongos](http://mongodb.github.io/node-mongodb-native/3.0/tutorials/connect/#connect-to-sharded-cluster) in mongoose 5.x. - - ```javascript - // Connect to 2 mongos servers - mongoose.connect('mongodb://mongosA:27501,mongosB:27501', cb); - ``` - -

    Multiple connections

    - - So far we've seen how to connect to MongoDB using Mongoose's default - connection. Mongoose creates a _default connection_ when you call `mongoose.connect()`. - You can access the default connection using `mongoose.connection`. - - You may need multiple connections to MongoDB for several reasons. - One reason is if you have multiple databases or multiple MongoDB clusters. - Another reason is to work around [slow trains](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). - The `mongoose.createConnection()` function takes the same arguments as - `mongoose.connect()` and returns a new connection. - - ```javascript - const conn = mongoose.createConnection('mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]', options); - ``` - - This [connection](./api.html#connection_Connection) object is then used to - create and retrieve [models](./api.html#model_Model). Models are - **always** scoped to a single connection. - - ```javascript - const UserModel = conn.model('User', userSchema); - ``` - - If you use multiple connections, you should make sure you export schemas, - **not** models. Exporting a model from a file is called the _export model pattern_. - The export model pattern is limited because you can only use one connection. - - ```javascript - const userSchema = new Schema({ name: String, email: String }); - - // The alternative to the export model pattern is the export schema pattern. - module.exports = userSchema; - - // Because if you export a model as shown below, the model will be scoped - // to Mongoose's default connection. - // module.exports = mongoose.model('User', userSchema); - ``` - - If you use the export schema pattern, you still need to create models - somewhere. There are two common patterns. First is to export a connection - and register the models on the connection in the file: - - ```javascript - // connections/fast.js - const mongoose = require('mongoose'); - - const conn = mongoose.createConnection(process.env.MONGODB_URI); - conn.model('User', require('../schemas/user')); - - module.exports = conn; - - // connections/slow.js - const mongoose = require('mongoose'); - - const conn = mongoose.createConnection(process.env.MONGODB_URI); - conn.model('User', require('../schemas/user')); - conn.model('PageView', require('../schemas/pageView')); - - module.exports = conn; - ``` - - Another alternative is to register connections with a dependency injector - or another [inversion of control (IOC) pattern](https://thecodebarbarian.com/using-ramda-as-a-dependency-injector). - - ```javascript - const mongoose = require('mongoose'); - - module.exports = function connectionFactory() { - const conn = mongoose.createConnection(process.env.MONGODB_URI); - - conn.model('User', require('../schemas/user')); - conn.model('PageView', require('../schemas/pageView')); - - return conn; - }; - ``` - -

    Connection Pools

    - - Each `connection`, whether created with `mongoose.connect` or - `mongoose.createConnection` are all backed by an internal configurable - connection pool defaulting to a maximum size of 5. Adjust the pool size - using your connection options: - - ```javascript - // With object options - mongoose.createConnection(uri, { poolSize: 4 }); - - const uri = 'mongodb://localhost:27017/test?poolSize=4'; - mongoose.createConnection(uri); - ``` - -

    Option Changes in v5.x

    - - You may see the following deprecation warning if upgrading from 4.x to 5.x - and you didn't use the `useMongoClient` option in 4.x: - - ``` - the server/replset/mongos options are deprecated, all their options are supported at the top level of the options object - ``` - - In older version of the MongoDB driver you had to specify distinct options - for server connections, replica set connections, and mongos connections: - - ```javascript - mongoose.connect(myUri, { - server: { - socketOptions: { - socketTimeoutMS: 0, - keepAlive: true - }, - reconnectTries: 30 - }, - replset: { - socketOptions: { - socketTimeoutMS: 0, - keepAlive: true - }, - reconnectTries: 30 - }, - mongos: { - socketOptions: { - socketTimeoutMS: 0, - keepAlive: true - }, - reconnectTries: 30 - } - }); - ``` - - In mongoose v5.x you can instead declare these options at the top level, - without all that extra nesting. - [Here's the list of all supported options](http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html). - - ```javascript - // Equivalent to the above code - mongoose.connect(myUri, { - socketTimeoutMS: 0, - keepAlive: true, - reconnectTries: 30 - }); - ``` - -

    Next Up

    - - Now that we've covered connections, let's take a look at [models](/docs/models.html). diff --git a/docs/contributing.html b/docs/contributing.html deleted file mode 100644 index 5201142cd0d..00000000000 --- a/docs/contributing.html +++ /dev/null @@ -1,55 +0,0 @@ -Mongoose v5.6.0: Contributing

    Contributing

    -

    Please read all about contributing here.

    -
    \ No newline at end of file diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 00000000000..24d8f1d78a1 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,3 @@ +## Contributing + +Please read all about contributing [here](https://github.com/Automattic/mongoose/blob/master/CONTRIBUTING.md). \ No newline at end of file diff --git a/docs/contributing.pug b/docs/contributing.pug deleted file mode 100644 index 1e5e96af214..00000000000 --- a/docs/contributing.pug +++ /dev/null @@ -1,7 +0,0 @@ -extends layout - -block content - :markdown - ## Contributing - - Please read all about contributing [here](https://github.com/Automattic/mongoose/blob/master/CONTRIBUTING.md). diff --git a/docs/customschematypes.html b/docs/customschematypes.html deleted file mode 100644 index 74308395820..00000000000 --- a/docs/customschematypes.html +++ /dev/null @@ -1,99 +0,0 @@ -Mongoose v5.6.0: Custom Schema Types

    Creating a Basic Custom Schema Type

    New in Mongoose 4.4.0: Mongoose supports custom types. Before you -reach for a custom type, however, know that a custom type is overkill -for most use cases. You can do most basic tasks with -custom getters/setters, -virtuals, and -single embedded docs.

    -

    Let's take a look at an example of a basic schema type: a 1-byte integer. -To create a new schema type, you need to inherit from mongoose.SchemaType -and add the corresponding property to mongoose.Schema.Types. The one -method you need to implement is the cast() method.

    - -
    function Int8(key, options) {
    -  mongoose.SchemaType.call(this, key, options, 'Int8');
    -}
    -Int8.prototype = Object.create(mongoose.SchemaType.prototype);
    -
    -// `cast()` takes a parameter that can be anything. You need to
    -// validate the provided `val` and throw a `CastError` if you
    -// can't convert it.
    -Int8.prototype.cast = function(val) {
    -  var _val = Number(val);
    -  if (isNaN(_val)) {
    -    throw new Error('Int8: ' + val + ' is not a number');
    -  }
    -  _val = Math.round(_val);
    -  if (_val < -0x80 || _val > 0x7F) {
    -    throw new Error('Int8: ' + val +
    -      ' is outside of the range of valid 8-bit ints');
    -  }
    -  return _val;
    -};
    -
    -// Don't forget to add `Int8` to the type registry
    -mongoose.Schema.Types.Int8 = Int8;
    -
    -var testSchema = new Schema({ test: Int8 });
    -var Test = mongoose.model('CustomTypeExample', testSchema);
    -
    -var t = new Test();
    -t.test = 'abc';
    -assert.ok(t.validateSync());
    -assert.equal(t.validateSync().errors['test'].name, 'CastError');
    -assert.equal(t.validateSync().errors['test'].message,
    -  'Cast to Int8 failed for value "abc" at path "test"');
    -assert.equal(t.validateSync().errors['test'].reason.message,
    -  'Int8: abc is not a number');
    -
    \ No newline at end of file diff --git a/docs/customschematypes.md b/docs/customschematypes.md new file mode 100644 index 00000000000..dbc71d3f9d2 --- /dev/null +++ b/docs/customschematypes.md @@ -0,0 +1,19 @@ +## Custom Schema Types + +### Creating a Basic Custom Schema Type + +_New in Mongoose 4.4.0:_ Mongoose supports custom types. Before you +reach for a custom type, however, know that a custom type is overkill +for most use cases. You can do most basic tasks with +[custom getters/setters](http://mongoosejs.com/docs/2.7.x/docs/getters-setters.html), +[virtuals](http://mongoosejs.com/docs/guide.html#virtuals), and +[single embedded docs](http://mongoosejs.com/docs/subdocs.html#single-embedded). + +Let's take a look at an example of a basic schema type: a 1-byte integer. +To create a new schema type, you need to inherit from `mongoose.SchemaType` +and add the corresponding property to `mongoose.Schema.Types`. The one +method you need to implement is the `cast()` method. + +```javascript +[require:Creating a Basic Custom Schema Type] +``` \ No newline at end of file diff --git a/docs/defaults.html b/docs/defaults.html deleted file mode 100644 index 32b886c0fd2..00000000000 --- a/docs/defaults.html +++ /dev/null @@ -1,168 +0,0 @@ -Mongoose v5.6.0: Defaults

    Declaring defaults in your schema

    Your schemas can define default values for certain paths. If you create -a new document without that path set, the default will kick in.

    -

    Note: Mongoose only applies a default if the value of the path is -strictly undefined.

    - -
    var schema = new Schema({
    -  name: String,
    -  role: { type: String, default: 'guitarist' }
    -});
    -
    -var Person = db.model('Person', schema);
    -
    -var axl = new Person({ name: 'Axl Rose', role: 'singer' });
    -assert.equal(axl.role, 'singer');
    -
    -var slash = new Person({ name: 'Slash' });
    -assert.equal(slash.role, 'guitarist');
    -
    -var izzy = new Person({ name: 'Izzy', role: undefined });
    -assert.equal(izzy.role, 'guitarist');
    -
    -// Defaults do **not** run on `null`, `''`, or value other than `undefined`.
    -var foo = new Person({ name: 'Bar', role: null });
    -assert.strictEqual(foo.role, null);
    -
    -Person.create(axl, slash, function(error) {
    -  assert.ifError(error);
    -  Person.find({ role: 'guitarist' }, function(error, docs) {
    -    assert.ifError(error);
    -    assert.equal(docs.length, 1);
    -    assert.equal(docs[0].name, 'Slash');
    -  });
    -});
    -

    Default functions

    You can also set the default schema option to a function. Mongoose will -execute that function and use the return value as the default.

    - -
    var schema = new Schema({
    -  title: String,
    -  date: {
    -    type: Date,
    -    // `Date.now()` returns the current unix timestamp as a number
    -    default: Date.now
    -  }
    -});
    -
    -var BlogPost = db.model('BlogPost', schema);
    -
    -var post = new BlogPost({title: '5 Best Arnold Schwarzenegger Movies'});
    -
    -// The post has a default Date set to now
    -assert.ok(post.date.getTime() >= Date.now() - 1000);
    -assert.ok(post.date.getTime() <= Date.now());
    -

    The setDefaultsOnInsert option

    By default, mongoose only applies defaults when you create a new document. -It will not set defaults if you use update() and -findOneAndUpdate(). However, mongoose 4.x lets you opt-in to this -behavior using the setDefaultsOnInsert option.

    -

    Important

    -

    The setDefaultsOnInsert option relies on the -MongoDB $setOnInsert operator. -The $setOnInsert operator was introduced in MongoDB 2.4. If you're -using MongoDB server < 2.4.0, do not use setDefaultsOnInsert.

    - -
    var schema = new Schema({
    -  title: String,
    -  genre: {type: String, default: 'Action'}
    -});
    -
    -var Movie = db.model('Movie', schema);
    -
    -var query = {};
    -var update = {title: 'The Terminator'};
    -var options = {
    -  // Return the document after updates are applied
    -  new: true,
    -  // Create a document if one isn't found. Required
    -  // for `setDefaultsOnInsert`
    -  upsert: true,
    -  setDefaultsOnInsert: true
    -};
    -
    -Movie.
    -  findOneAndUpdate(query, update, options, function (error, doc) {
    -    assert.ifError(error);
    -    assert.equal(doc.title, 'The Terminator');
    -    assert.equal(doc.genre, 'Action');
    -  });
    -

    Default functions and this

    Unless it is running on a query with setDefaultsOnInsert, a default -function's this refers to the document.

    - -
    const schema = new Schema({
    -  title: String,
    -  released: Boolean,
    -  releaseDate: {
    -    type: Date,
    -    default: function() {
    -      if (this.released) {
    -        return Date.now();
    -      }
    -      return null;
    -    }
    -  }
    -});
    -
    -const Movie = db.model('Movie', schema);
    -
    -const movie1 = new Movie({ title: 'The Terminator', released: true });
    -
    -// The post has a default Date set to now
    -assert.ok(movie1.releaseDate.getTime() >= Date.now() - 1000);
    -assert.ok(movie1.releaseDate.getTime() <= Date.now());
    -
    -const movie2 = new Movie({ title: 'The Legend of Conan', released: false });
    -
    -// Since `released` is false, the default function will return null
    -assert.strictEqual(movie2.releaseDate, null);
    -
    \ No newline at end of file diff --git a/docs/defaults.md b/docs/defaults.md new file mode 100644 index 00000000000..0dcc8d762c7 --- /dev/null +++ b/docs/defaults.md @@ -0,0 +1,49 @@ +## Defaults + +### Declaring Defaults in Your Schema + +Your schemas can define default values for certain paths. If you create +a new document without that path set, the default will kick in. + +Note: Mongoose only applies a default if the value of the path is +strictly `undefined`. + +```javascript +[require:Declaring defaults in your schema] +``` + +### Default Functions + +You can also set the `default` schema option to a function. Mongoose will +execute that function and use the return value as the default. + +```javascript +[require:Default functions] +``` + +### The `setDefaultsOnInsert` Option + +By default, mongoose only applies defaults when you create a new document. +It will **not** set defaults if you use `update()` and +`findOneAndUpdate()`. However, mongoose 4.x lets you opt-in to this +behavior using the `setDefaultsOnInsert` option. + +#### Important + +The `setDefaultsOnInsert` option relies on the +[MongoDB `$setOnInsert` operator](https://docs.mongodb.org/manual/reference/operator/update/setOnInsert/). +The `$setOnInsert` operator was introduced in MongoDB 2.4. If you're +using MongoDB server < 2.4.0, do **not** use `setDefaultsOnInsert`. + +```javascript +[require:The `setDefaultsOnInsert` option] +``` + +### Default functions and `this` + +Unless it is running on a query with `setDefaultsOnInsert`, a default +function's `this` refers to the document. + +```javascript +[require:Default functions and `this`] +``` \ No newline at end of file diff --git a/docs/deprecations.html b/docs/deprecations.html deleted file mode 100644 index 6e397c78a2c..00000000000 --- a/docs/deprecations.html +++ /dev/null @@ -1,226 +0,0 @@ -Mongoose v5.6.0: Deprecation Warnings

    Deprecation Warnings

    - - - - -

    There are several deprecations in the MongoDB Node.js driver -that Mongoose users should be aware of. Mongoose provides options to work -around these deprecation warnings, but you need to test whether these options -cause any problems for your application. Please report any issues on GitHub.

    -

    Summary

    -

    To fix all deprecation warnings, follow the below steps:

    -
      -
    • mongoose.set('useNewUrlParser', true);
    • -
    • mongoose.set('useFindAndModify', false);
    • -
    • mongoose.set('useCreateIndex', true);
    • -
    • Replace update() with updateOne(), updateMany(), or replaceOne()
    • -
    • Replace remove() with deleteOne() or deleteMany().
    • -
    • Replace count() with countDocuments(), unless you want to count how many documents are in the whole collection (no filter). In the latter case, use estimatedDocumentCount().
    • -
    -

    Read below for more a more detailed description of each deprecation warning.

    -

    The useNewUrlParser Option

    -

    By default, mongoose.connect() will print out the below warning:

    -
    DeprecationWarning: current URL string parser is deprecated, and will be
    -removed in a future version. To use the new parser, pass option
    -{ useNewUrlParser: true } to MongoClient.connect.

    The MongoDB Node.js driver rewrote the tool it uses to parse MongoDB connection strings. -Because this is such a big change, they put the new connection string parser -behind a flag. To turn on this option, pass the useNewUrlParser option to -mongoose.connect() -or mongoose.createConnection().

    -
    mongoose.connect(uri, { useNewUrlParser: true });
    -mongoose.createConnection(uri, { useNewUrlParser: true });
    -

    You can also set the global useNewUrlParser option -to turn on useNewUrlParser for every connection by default.

    -
    // Optional. Use this if you create a lot of connections and don't want
    -// to copy/paste `{ useNewUrlParser: true }`.
    -mongoose.set('useNewUrlParser', true);
    -

    To test your app with { useNewUrlParser: true }, you only need to check -whether your app successfully connects. Once Mongoose has successfully -connected, the URL parser is no longer important. If you can't connect -with { useNewUrlParser: true }, please open an issue on GitHub.

    -

    findAndModify()

    -

    If you use Model.findOneAndUpdate(), -by default you'll see one of the below deprecation warnings.

    -
    DeprecationWarning: Mongoose: `findOneAndUpdate()` and `findOneAndDelete()` without the `useFindAndModify` option set to false are deprecated. See: https://mongoosejs.com/docs/deprecations.html#-findandmodify-
    -DeprecationWarning: collection.findAndModify is deprecated. Use findOneAndUpdate, findOneAndReplace or findOneAndDelete instead.

    Mongoose's findOneAndUpdate() long pre-dates the MongoDB driver's findOneAndUpdate() -function, so it uses the MongoDB driver's findAndModify() function -instead. You can opt in to using using the MongoDB driver's findOneAndUpdate() -function using the useFindAndModify global option.

    -
    // Make Mongoose use `findOneAndUpdate()`. Note that this option is `true`
    -// by default, you need to set it to false.
    -mongoose.set('useFindAndModify', false);
    -

    You can also configure useFindAndModify by passing it through the connection options.

    -
    mongoose.connect(uri, { useFindAndModify: false });
    -

    This option affects the following model and query functions. There are -no intentional backwards breaking changes, so you should be able to turn -this option on without any code changes. If you discover any issues, -please open an issue on GitHub.

    - -

    ensureIndex()

    -

    If you define indexes in your Mongoose schemas, you'll see the below -deprecation warning.

    -
    DeprecationWarning: collection.ensureIndex is deprecated. Use createIndexes
    -instead.

    By default, Mongoose 5.x calls the MongoDB driver's ensureIndex() function. -The MongoDB driver deprecated this function in favor of createIndex(). -Set the useCreateIndex global option to opt in to making Mongoose use createIndex() instead.

    -
    mongoose.set('useCreateIndex', true);
    -

    You can also configure useCreateIndex by passing it through the connection options.

    -
    mongoose.connect(uri, { useCreateIndex: true });
    -

    There are no intentional backwards breaking changes with the useCreateIndex -option, so you should be able to turn -this option on without any code changes. If you discover any issues, -please open an issue on GitHub.

    -

    remove()

    -

    The MongoDB driver's remove() function is deprecated in favor of deleteOne() and deleteMany(). This is to comply with -the MongoDB CRUD specification, -which aims to provide a consistent API for CRUD operations across all MongoDB -drivers.

    -
    DeprecationWarning: collection.remove is deprecated. Use deleteOne,
    -deleteMany, or bulkWrite instead.

    To remove this deprecation warning, replace any usage of remove() with -deleteMany(), unless you specify the single option to remove(). The single -option limited remove() to deleting at most one document, so you should -replace remove(filter, { single: true }) with deleteOne(filter).

    -
    // Replace this:
    -MyModel.remove({ foo: 'bar' });
    -// With this:
    -MyModel.deleteMany({ foo: 'bar' });
    -
    -// Replace this:
    -MyModel.remove({ answer: 42 }, { single: true });
    -// With this:
    -MyModel.deleteOne({ answer: 42 });
    -

    update()

    -

    Like remove(), the update() function is deprecated in favor -of the more explicit updateOne(), updateMany(), and replaceOne() functions. You should replace -update() with updateOne(), unless you use the multi or overwrite options.

    -
    collection.update is deprecated. Use updateOne, updateMany, or bulkWrite
    -instead.
    // Replace this:
    -MyModel.update({ foo: 'bar' }, { answer: 42 });
    -// With this:
    -MyModel.updateOne({ foo: 'bar' }, { answer: 42 });
    -
    -// If you use `overwrite: true`, you should use `replaceOne()` instead:
    -MyModel.update(filter, update, { overwrite: true });
    -// Replace with this:
    -MyModel.replaceOne(filter, update);
    -
    -// If you use `multi: true`, you should use `updateMany()` instead:
    -MyModel.update(filter, update, { multi: true });
    -// Replace with this:
    -MyModel.updateMany(filter, update);
    -

    count()

    -

    The MongoDB server has deprecated the count() function in favor of two -separate functions, countDocuments() and -estimatedDocumentCount().

    -
    DeprecationWarning: collection.count is deprecated, and will be removed in a future version. Use collection.countDocuments or collection.estimatedDocumentCount instead

    The difference between the two is countDocuments() can accept a filter -parameter like find(). The estimatedDocumentCount() -function is faster, but can only tell you the total number of documents in -a collection. You cannot pass a filter to estimatedDocumentCount().

    -

    To migrate, replace count() with countDocuments() unless you do not -pass any arguments to count(). If you use count() to count all documents -in a collection as opposed to counting documents that match a query, use -estimatedDocumentCount() instead of countDocuments().

    -
    // Replace this:
    -MyModel.count({ answer: 42 });
    -// With this:
    -MyModel.countDocuments({ answer: 42 });
    -
    -// If you're counting all documents in the collection, use
    -// `estimatedDocumentCount()` instead.
    -MyModel.count();
    -// Replace with:
    -MyModel.estimatedDocumentCount();
    -
    -// Replace this:
    -MyModel.find({ answer: 42 }).count().exec();
    -// With this:
    -MyModel.find({ answer: 42 }).countDocuments().exec();
    -
    -// Replace this:
    -MyModel.find().count().exec();
    -// With this, since there's no filter
    -MyModel.find().estimatedDocumentCount().exec();
    -

    GridStore

    -

    If you're using gridfs-stream, -you'll see the below deprecation warning:

    -
    DeprecationWarning: GridStore is deprecated, and will be removed in a
    -future version. Please use GridFSBucket instead.

    That is because gridfs-stream relies on a deprecated MongoDB driver class. -You should instead use the MongoDB driver's own streaming API.

    -
    // Replace this:
    -const conn = mongoose.createConnection('mongodb://localhost:27017/gfstest');
    -const gfs = require('gridfs-store')(conn.db);
    -const writeStream = gfs.createWriteStream({ filename: 'test.dat' });
    -
    -// With this:
    -const conn = mongoose.createConnection('mongodb://localhost:27017/gfstest');
    -const gridFSBucket = new mongoose.mongo.GridFSBucket(conn.db);
    -const writeStream = gridFSBucket.openUploadStream('test.dat');
    -
    \ No newline at end of file diff --git a/docs/deprecations.md b/docs/deprecations.md new file mode 100644 index 00000000000..b17953659c0 --- /dev/null +++ b/docs/deprecations.md @@ -0,0 +1,293 @@ +# Deprecation Warnings + +There are several deprecations in the [MongoDB Node.js driver](http://npmjs.com/package/mongodb) +that Mongoose users should be aware of. Mongoose provides options to work +around these deprecation warnings, but you need to test whether these options +cause any problems for your application. Please [report any issues on GitHub](https://github.com/Automattic/mongoose/issues/new). + +

    Summary

    + +To fix all deprecation warnings, follow the below steps: + +* `mongoose.set('useNewUrlParser', true);` +* `mongoose.set('useFindAndModify', false);` +* `mongoose.set('useCreateIndex', true);` +* `mongoose.set('useUnifiedTopology', true);` +* Replace `update()` with `updateOne()`, `updateMany()`, or `replaceOne()` +* Replace `remove()` with `deleteOne()` or `deleteMany()`. +* Replace `count()` with `countDocuments()`, unless you want to count how many documents are in the whole collection (no filter). In the latter case, use `estimatedDocumentCount()`. + +Read below for more a more detailed description of each deprecation warning. + +

    The useNewUrlParser Option

    + +By default, `mongoose.connect()` will print out the below warning: + +``` +DeprecationWarning: current URL string parser is deprecated, and will be +removed in a future version. To use the new parser, pass option +{ useNewUrlParser: true } to MongoClient.connect. +``` + +The MongoDB Node.js driver rewrote the tool it uses to parse [MongoDB connection strings](https://docs.mongodb.com/manual/reference/connection-string/). +Because this is such a big change, they put the new connection string parser +behind a flag. To turn on this option, pass the `useNewUrlParser` option to +[`mongoose.connect()`](/docs/api.html#mongoose_Mongoose-connect) +or [`mongoose.createConnection()`](/docs/api.html#mongoose_Mongoose-createConnection). + +```javascript +mongoose.connect(uri, { useNewUrlParser: true }); +mongoose.createConnection(uri, { useNewUrlParser: true }); +``` + +You can also [set the global `useNewUrlParser` option](/docs/api.html#mongoose_Mongoose-set) +to turn on `useNewUrlParser` for every connection by default. + +```javascript +// Optional. Use this if you create a lot of connections and don't want +// to copy/paste `{ useNewUrlParser: true }`. +mongoose.set('useNewUrlParser', true); +``` + +To test your app with `{ useNewUrlParser: true }`, you only need to check +whether your app successfully connects. Once Mongoose has successfully +connected, the URL parser is no longer important. If you can't connect +with `{ useNewUrlParser: true }`, please [open an issue on GitHub](https://github.com/Automattic/mongoose/issues/new). + +

    findAndModify()

    + +If you use [`Model.findOneAndUpdate()`](/docs/api.html#model_Model.findOneAndUpdate), +by default you'll see one of the below deprecation warnings. + +``` +DeprecationWarning: Mongoose: `findOneAndUpdate()` and `findOneAndDelete()` without the `useFindAndModify` option set to false are deprecated. See: https://mongoosejs.com/docs/5.x/docs/deprecations.html#findandmodify +DeprecationWarning: collection.findAndModify is deprecated. Use findOneAndUpdate, findOneAndReplace or findOneAndDelete instead. +``` + +Mongoose's `findOneAndUpdate()` long pre-dates the MongoDB driver's `findOneAndUpdate()` +function, so it uses the MongoDB driver's [`findAndModify()` function](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#findAndModify) +instead. You can opt in to using the MongoDB driver's `findOneAndUpdate()` +function using the [`useFindAndModify` global option](/docs/api.html#mongoose_Mongoose-set). + +```javascript +// Make Mongoose use `findOneAndUpdate()`. Note that this option is `true` +// by default, you need to set it to false. +mongoose.set('useFindAndModify', false); +``` + +You can also configure `useFindAndModify` by passing it through the connection options. + +```javascript +mongoose.connect(uri, { useFindAndModify: false }); +``` + +This option affects the following model and query functions. There are +no intentional backwards breaking changes, so you should be able to turn +this option on without any code changes. If you discover any issues, +please [open an issue on GitHub](https://github.com/Automattic/mongoose/issues/new). + +* [`Model.findByIdAndDelete()`](/docs/api.html#model_Model.findByIdAndDelete) +* [`Model.findByIdAndRemove()`](/docs/api.html#model_Model.findByIdAndRemove) +* [`Model.findByIdAndUpdate()`](/docs/api.html#model_Model.findByIdAndUpdate) +* [`Model.findOneAndDelete()`](/docs/api.html#model_Model.findOneAndDelete) +* [`Model.findOneAndRemove()`](/docs/api.html#model_Model.findOneAndRemove) +* [`Model.findOneAndUpdate()`](/docs/api.html#model_Model.findOneAndUpdate) +* [`Query.findOneAndDelete()`](/docs/api.html#query_Query-findOneAndDelete) +* [`Query.findOneAndRemove()`](/docs/api.html#query_Query-findOneAndRemove) +* [`Query.findOneAndUpdate()`](/docs/api.html#query_Query-findOneAndUpdate) + +You can also safely ignore this warning. Mongoose will not remove the legacy `useFindAndModify: true` +behavior until Mongoose 6.0. + +

    ensureIndex()

    + +If you define [indexes in your Mongoose schemas](https://mongoosejs.com/docs/guide.html#indexes), you'll see the below +deprecation warning. + +``` +DeprecationWarning: collection.ensureIndex is deprecated. Use createIndexes +instead. +``` + +By default, Mongoose 5.x calls the [MongoDB driver's `ensureIndex()` function](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#ensureIndex). +The MongoDB driver deprecated this function in favor of [`createIndex()`](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#createIndex). +Set the [`useCreateIndex` global option](/docs/api.html#mongoose_Mongoose-set) to opt in to making Mongoose use `createIndex()` instead. + +```javascript +mongoose.set('useCreateIndex', true); +``` + +You can also configure `useCreateIndex` by passing it through the connection options. + +```javascript +mongoose.connect(uri, { useCreateIndex: true }); +``` + +There are no intentional backwards breaking changes with the `useCreateIndex` +option, so you should be able to turn +this option on without any code changes. If you discover any issues, +please [open an issue on GitHub](https://github.com/Automattic/mongoose/issues/new). + +You can also safely ignore this warning. Mongoose will not remove the legacy `useCreateIndex: false` +behavior until Mongoose 6.0. + +

    remove()

    + +The MongoDB driver's [`remove()` function](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#remove) is deprecated in favor of `deleteOne()` and `deleteMany()`. This is to comply with +the [MongoDB CRUD specification](https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst), +which aims to provide a consistent API for CRUD operations across all MongoDB +drivers. + +``` +DeprecationWarning: collection.remove is deprecated. Use deleteOne, +deleteMany, or bulkWrite instead. +``` + +To remove this deprecation warning, replace any usage of `remove()` with +`deleteMany()`, _unless_ you specify the [`single` option to `remove()`](/docs/api.html#model_Model.remove). The `single` +option limited `remove()` to deleting at most one document, so you should +replace `remove(filter, { single: true })` with `deleteOne(filter)`. + +```javascript +// Replace this: +MyModel.remove({ foo: 'bar' }); +// With this: +MyModel.deleteMany({ foo: 'bar' }); + +// Replace this: +MyModel.remove({ answer: 42 }, { single: true }); +// With this: +MyModel.deleteOne({ answer: 42 }); +``` + +

    useUnifiedTopology

    + +By default, `mongoose.connect()` will print out the below warning: + +``` +DeprecationWarning: current Server Discovery and Monitoring engine is +deprecated, and will be removed in a future version. To use the new Server +Discover and Monitoring engine, pass option { useUnifiedTopology: true } to +the MongoClient constructor. +``` + +Mongoose 5.7 uses MongoDB driver 3.3.x, which introduced a significant +refactor of how it handles monitoring all the servers in a replica set +or sharded cluster. In MongoDB parlance, this is known as +[server discovery and monitoring](https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst). + +To opt in to using the new topology engine, use the below line: + +```javascript +mongoose.set('useUnifiedTopology', true); +``` + +The `useUnifiedTopology` option removes support for several +[connection options](/docs/connections.html#options) that are +no longer relevant with the new topology engine: + +- `autoReconnect` +- `reconnectTries` +- `reconnectInterval` + +When you enable `useUnifiedTopology`, please remove those options +from your [`mongoose.connect()`](/docs/api/mongoose.html#mongoose_Mongoose-connect) or +[`createConnection()`](/docs/api/mongoose.html#mongoose_Mongoose-createConnection) calls. + +If you find any unexpected behavior, please [open up an issue on GitHub](https://github.com/Automattic/mongoose/issues/new). + +

    update()

    + +Like `remove()`, the [`update()` function](/docs/api.html#model_Model.update) is deprecated in favor +of the more explicit [`updateOne()`](/docs/api.html#model_Model.updateOne), [`updateMany()`](/docs/api.html#model_Model.updateMany), and [`replaceOne()`](/docs/api.html#model_Model.replaceOne) functions. You should replace +`update()` with `updateOne()`, unless you use the [`multi` or `overwrite` options](/docs/api.html#model_Model.update). + +``` +collection.update is deprecated. Use updateOne, updateMany, or bulkWrite +instead. +``` + +```javascript +// Replace this: +MyModel.update({ foo: 'bar' }, { answer: 42 }); +// With this: +MyModel.updateOne({ foo: 'bar' }, { answer: 42 }); + +// If you use `overwrite: true`, you should use `replaceOne()` instead: +MyModel.update(filter, update, { overwrite: true }); +// Replace with this: +MyModel.replaceOne(filter, update); + +// If you use `multi: true`, you should use `updateMany()` instead: +MyModel.update(filter, update, { multi: true }); +// Replace with this: +MyModel.updateMany(filter, update); +``` + +

    count()

    + +The MongoDB server has deprecated the `count()` function in favor of two +separate functions, [`countDocuments()`](#query_Query-countDocuments) and +[`estimatedDocumentCount()`](#query_Query-estimatedDocumentCount). + +``` +DeprecationWarning: collection.count is deprecated, and will be removed in a future version. Use collection.countDocuments or collection.estimatedDocumentCount instead +``` + +The difference between the two is `countDocuments()` can accept a filter +parameter like [`find()`](#query_Query-find). The `estimatedDocumentCount()` +function is faster, but can only tell you the total number of documents in +a collection. You cannot pass a `filter` to `estimatedDocumentCount()`. + +To migrate, replace `count()` with `countDocuments()` _unless_ you do not +pass any arguments to `count()`. If you use `count()` to count all documents +in a collection as opposed to counting documents that match a query, use +`estimatedDocumentCount()` instead of `countDocuments()`. + +```javascript +// Replace this: +MyModel.count({ answer: 42 }); +// With this: +MyModel.countDocuments({ answer: 42 }); + +// If you're counting all documents in the collection, use +// `estimatedDocumentCount()` instead. +MyModel.count(); +// Replace with: +MyModel.estimatedDocumentCount(); + +// Replace this: +MyModel.find({ answer: 42 }).count().exec(); +// With this: +MyModel.find({ answer: 42 }).countDocuments().exec(); + +// Replace this: +MyModel.find().count().exec(); +// With this, since there's no filter +MyModel.find().estimatedDocumentCount().exec(); +``` + +

    GridStore

    + +If you're using [gridfs-stream](https://www.npmjs.com/package/gridfs-stream), +you'll see the below deprecation warning: + +``` +DeprecationWarning: GridStore is deprecated, and will be removed in a +future version. Please use GridFSBucket instead. +``` + +That is because gridfs-stream relies on a [deprecated MongoDB driver class](http://mongodb.github.io/node-mongodb-native/3.1/api/GridStore.html). +You should instead use the [MongoDB driver's own streaming API](https://thecodebarbarian.com/mongodb-gridfs-stream). + +```javascript +// Replace this: +const conn = mongoose.createConnection('mongodb://localhost:27017/gfstest'); +const gfs = require('gridfs-store')(conn.db); +const writeStream = gfs.createWriteStream({ filename: 'test.dat' }); + +// With this: +const conn = mongoose.createConnection('mongodb://localhost:27017/gfstest'); +const gridFSBucket = new mongoose.mongo.GridFSBucket(conn.db); +const writeStream = gridFSBucket.openUploadStream('test.dat'); +``` \ No newline at end of file diff --git a/docs/deprecations.pug b/docs/deprecations.pug deleted file mode 100644 index dea93a31b55..00000000000 --- a/docs/deprecations.pug +++ /dev/null @@ -1,307 +0,0 @@ -extends layout - -append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown - ## Deprecation Warnings - - - - - - There are several deprecations in the [MongoDB Node.js driver](http://npmjs.com/package/mongodb) - that Mongoose users should be aware of. Mongoose provides options to work - around these deprecation warnings, but you need to test whether these options - cause any problems for your application. Please [report any issues on GitHub](https://github.com/Automattic/mongoose/issues/new). - - ## Summary - - To fix all deprecation warnings, follow the below steps: - - * `mongoose.set('useNewUrlParser', true);` - * `mongoose.set('useFindAndModify', false);` - * `mongoose.set('useCreateIndex', true);` - * `mongoose.set('useUnifiedTopology', true);` - * Replace `update()` with `updateOne()`, `updateMany()`, or `replaceOne()` - * Replace `remove()` with `deleteOne()` or `deleteMany()`. - * Replace `count()` with `countDocuments()`, unless you want to count how many documents are in the whole collection (no filter). In the latter case, use `estimatedDocumentCount()`. - - Read below for more a more detailed description of each deprecation warning. - - ## The `useNewUrlParser` Option - - By default, `mongoose.connect()` will print out the below warning: - - ``` - DeprecationWarning: current URL string parser is deprecated, and will be - removed in a future version. To use the new parser, pass option - { useNewUrlParser: true } to MongoClient.connect. - ``` - - The MongoDB Node.js driver rewrote the tool it uses to parse [MongoDB connection strings](https://docs.mongodb.com/manual/reference/connection-string/). - Because this is such a big change, they put the new connection string parser - behind a flag. To turn on this option, pass the `useNewUrlParser` option to - [`mongoose.connect()`](/docs/api.html#mongoose_Mongoose-connect) - or [`mongoose.createConnection()`](/docs/api.html#mongoose_Mongoose-createConnection). - - ```javascript - mongoose.connect(uri, { useNewUrlParser: true }); - mongoose.createConnection(uri, { useNewUrlParser: true }); - ``` - - You can also [set the global `useNewUrlParser` option](/docs/api.html#mongoose_Mongoose-set) - to turn on `useNewUrlParser` for every connection by default. - - ```javascript - // Optional. Use this if you create a lot of connections and don't want - // to copy/paste `{ useNewUrlParser: true }`. - mongoose.set('useNewUrlParser', true); - ``` - - To test your app with `{ useNewUrlParser: true }`, you only need to check - whether your app successfully connects. Once Mongoose has successfully - connected, the URL parser is no longer important. If you can't connect - with `{ useNewUrlParser: true }`, please [open an issue on GitHub](https://github.com/Automattic/mongoose/issues/new). - - ## `findAndModify()` - - If you use [`Model.findOneAndUpdate()`](/docs/api.html#model_Model.findOneAndUpdate), - by default you'll see one of the below deprecation warnings. - - ``` - DeprecationWarning: Mongoose: `findOneAndUpdate()` and `findOneAndDelete()` without the `useFindAndModify` option set to false are deprecated. See: https://mongoosejs.com/docs/deprecations.html#findandmodify - DeprecationWarning: collection.findAndModify is deprecated. Use findOneAndUpdate, findOneAndReplace or findOneAndDelete instead. - ``` - - Mongoose's `findOneAndUpdate()` long pre-dates the MongoDB driver's `findOneAndUpdate()` - function, so it uses the MongoDB driver's [`findAndModify()` function](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#findAndModify) - instead. You can opt in to using the MongoDB driver's `findOneAndUpdate()` - function using the [`useFindAndModify` global option](/docs/api.html#mongoose_Mongoose-set). - - ```javascript - // Make Mongoose use `findOneAndUpdate()`. Note that this option is `true` - // by default, you need to set it to false. - mongoose.set('useFindAndModify', false); - ``` - - You can also configure `useFindAndModify` by passing it through the connection options. - ```javascript - mongoose.connect(uri, { useFindAndModify: false }); - ``` - - This option affects the following model and query functions. There are - no intentional backwards breaking changes, so you should be able to turn - this option on without any code changes. If you discover any issues, - please [open an issue on GitHub](https://github.com/Automattic/mongoose/issues/new). - - * [`Model.findByIdAndDelete()`](/docs/api.html#model_Model.findByIdAndDelete) - * [`Model.findByIdAndRemove()`](/docs/api.html#model_Model.findByIdAndRemove) - * [`Model.findByIdAndUpdate()`](/docs/api.html#model_Model.findByIdAndUpdate) - * [`Model.findOneAndDelete()`](/docs/api.html#model_Model.findOneAndDelete) - * [`Model.findOneAndRemove()`](/docs/api.html#model_Model.findOneAndRemove) - * [`Model.findOneAndUpdate()`](/docs/api.html#model_Model.findOneAndUpdate) - * [`Query.findOneAndDelete()`](/docs/api.html#query_Query-findOneAndDelete) - * [`Query.findOneAndRemove()`](/docs/api.html#query_Query-findOneAndRemove) - * [`Query.findOneAndUpdate()`](/docs/api.html#query_Query-findOneAndUpdate) - - ## `ensureIndex()` - - If you define [indexes in your Mongoose schemas](https://mongoosejs.com/docs/guide.html#indexes), you'll see the below - deprecation warning. - - ``` - DeprecationWarning: collection.ensureIndex is deprecated. Use createIndexes - instead. - ``` - - By default, Mongoose 5.x calls the [MongoDB driver's `ensureIndex()` function](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#ensureIndex). - The MongoDB driver deprecated this function in favor of [`createIndex()`](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#createIndex). - Set the [`useCreateIndex` global option](/docs/api.html#mongoose_Mongoose-set) to opt in to making Mongoose use `createIndex()` instead. - - ```javascript - mongoose.set('useCreateIndex', true); - ``` - - You can also configure `useCreateIndex` by passing it through the connection options. - ```javascript - mongoose.connect(uri, { useCreateIndex: true }); - ``` - - There are no intentional backwards breaking changes with the `useCreateIndex` - option, so you should be able to turn - this option on without any code changes. If you discover any issues, - please [open an issue on GitHub](https://github.com/Automattic/mongoose/issues/new). - - ## `remove()` - - The MongoDB driver's [`remove()` function](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#remove) is deprecated in favor of `deleteOne()` and `deleteMany()`. This is to comply with - the [MongoDB CRUD specification](https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst), - which aims to provide a consistent API for CRUD operations across all MongoDB - drivers. - - ``` - DeprecationWarning: collection.remove is deprecated. Use deleteOne, - deleteMany, or bulkWrite instead. - ``` - - To remove this deprecation warning, replace any usage of `remove()` with - `deleteMany()`, _unless_ you specify the [`single` option to `remove()`](/docs/api.html#model_Model.remove). The `single` - option limited `remove()` to deleting at most one document, so you should - replace `remove(filter, { single: true })` with `deleteOne(filter)`. - - ```javascript - // Replace this: - MyModel.remove({ foo: 'bar' }); - // With this: - MyModel.deleteMany({ foo: 'bar' }); - - // Replace this: - MyModel.remove({ answer: 42 }, { single: true }); - // With this: - MyModel.deleteOne({ answer: 42 }); - ``` - - ## `useUnifiedTopology` - - By default, `mongoose.connect()` will print out the below warning: - - ``` - DeprecationWarning: current Server Discovery and Monitoring engine is - deprecated, and will be removed in a future version. To use the new Server - Discover and Monitoring engine, pass option { useUnifiedTopology: true } to - the MongoClient constructor. - ``` - - Mongoose 5.7 uses MongoDB driver 3.3.x, which introduced a significant - refactor of how it handles monitoring all the servers in a replica set - or sharded cluster. In MongoDB parlance, this is known as - [server discovery and monitoring](https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst). - - To opt in to using the new topology engine, use the below line: - - ```javascript - mongoose.set('useUnifiedTopology', true); - ``` - - The `useUnifiedTopology` option removes support for several - [connection options](/docs/connections.html#options) that are - no longer relevant with the new topology engine: - - - `autoReconnect` - - `reconnectTries` - - `reconnectInterval` - - When you enable `useUnifiedTopology`, please remove those options - from your [`mongoose.connect()`](/docs/api/mongoose.html#mongoose_Mongoose-connect) or - [`createConnection()`](/docs/api/mongoose.html#mongoose_Mongoose-createConnection) calls. - - If you find any unexpected behavior, please [open up an issue on GitHub](https://github.com/Automattic/mongoose/issues/new). - - ## `update()` - - Like `remove()`, the [`update()` function](/docs/api.html#model_Model.update) is deprecated in favor - of the more explicit [`updateOne()`](/docs/api.html#model_Model.updateOne), [`updateMany()`](/docs/api.html#model_Model.updateMany), and [`replaceOne()`](/docs/api.html#model_Model.replaceOne) functions. You should replace - `update()` with `updateOne()`, unless you use the [`multi` or `overwrite` options](/docs/api.html#model_Model.update). - - ``` - collection.update is deprecated. Use updateOne, updateMany, or bulkWrite - instead. - ``` - - ```javascript - // Replace this: - MyModel.update({ foo: 'bar' }, { answer: 42 }); - // With this: - MyModel.updateOne({ foo: 'bar' }, { answer: 42 }); - - // If you use `overwrite: true`, you should use `replaceOne()` instead: - MyModel.update(filter, update, { overwrite: true }); - // Replace with this: - MyModel.replaceOne(filter, update); - - // If you use `multi: true`, you should use `updateMany()` instead: - MyModel.update(filter, update, { multi: true }); - // Replace with this: - MyModel.updateMany(filter, update); - ``` - - ## `count()` - - The MongoDB server has deprecated the `count()` function in favor of two - separate functions, [`countDocuments()`](#query_Query-countDocuments) and - [`estimatedDocumentCount()`](#query_Query-estimatedDocumentCount). - - ``` - DeprecationWarning: collection.count is deprecated, and will be removed in a future version. Use collection.countDocuments or collection.estimatedDocumentCount instead - ``` - - The difference between the two is `countDocuments()` can accept a filter - parameter like [`find()`](#query_Query-find). The `estimatedDocumentCount()` - function is faster, but can only tell you the total number of documents in - a collection. You cannot pass a `filter` to `estimatedDocumentCount()`. - - To migrate, replace `count()` with `countDocuments()` _unless_ you do not - pass any arguments to `count()`. If you use `count()` to count all documents - in a collection as opposed to counting documents that match a query, use - `estimatedDocumentCount()` instead of `countDocuments()`. - - ```javascript - // Replace this: - MyModel.count({ answer: 42 }); - // With this: - MyModel.countDocuments({ answer: 42 }); - - // If you're counting all documents in the collection, use - // `estimatedDocumentCount()` instead. - MyModel.count(); - // Replace with: - MyModel.estimatedDocumentCount(); - - // Replace this: - MyModel.find({ answer: 42 }).count().exec(); - // With this: - MyModel.find({ answer: 42 }).countDocuments().exec(); - - // Replace this: - MyModel.find().count().exec(); - // With this, since there's no filter - MyModel.find().estimatedDocumentCount().exec(); - ``` - - ## `GridStore` - - If you're using [gridfs-stream](https://www.npmjs.com/package/gridfs-stream), - you'll see the below deprecation warning: - - ``` - DeprecationWarning: GridStore is deprecated, and will be removed in a - future version. Please use GridFSBucket instead. - ``` - - That is because gridfs-stream relies on a [deprecated MongoDB driver class](http://mongodb.github.io/node-mongodb-native/3.1/api/GridStore.html). - You should instead use the [MongoDB driver's own streaming API](https://thecodebarbarian.com/mongodb-gridfs-stream). - - ```javascript - // Replace this: - const conn = mongoose.createConnection('mongodb://localhost:27017/gfstest'); - const gfs = require('gridfs-store')(conn.db); - const writeStream = gfs.createWriteStream({ filename: 'test.dat' }); - - // With this: - const conn = mongoose.createConnection('mongodb://localhost:27017/gfstest'); - const gridFSBucket = new mongoose.mongo.GridFSBucket(conn.db); - const writeStream = gridFSBucket.openUploadStream('test.dat'); - ``` diff --git a/docs/discriminators.html b/docs/discriminators.html deleted file mode 100644 index 55ebe269735..00000000000 --- a/docs/discriminators.html +++ /dev/null @@ -1,352 +0,0 @@ -Mongoose v5.6.0: Discriminators

    The model.discriminator() function

    Discriminators are a schema inheritance mechanism. They enable -you to have multiple models with overlapping schemas on top of the -same underlying MongoDB collection.

    -

    Suppose you wanted to track different types of events in a single -collection. Every event will have a timestamp, but events that -represent clicked links should have a URL. You can achieve this -using the model.discriminator() function. This function takes -3 parameters, a model name, a discriminator schema and an optional -key (defaults to the model name). It returns a model whose schema -is the union of the base schema and the discriminator schema.

    - -
    var options = {discriminatorKey: 'kind'};
    -
    -var eventSchema = new mongoose.Schema({time: Date}, options);
    -var Event = mongoose.model('Event', eventSchema);
    -
    -// ClickedLinkEvent is a special type of Event that has
    -// a URL.
    -var ClickedLinkEvent = Event.discriminator('ClickedLink',
    -  new mongoose.Schema({url: String}, options));
    -
    -// When you create a generic event, it can't have a URL field...
    -var genericEvent = new Event({time: Date.now(), url: 'google.com'});
    -assert.ok(!genericEvent.url);
    -
    -// But a ClickedLinkEvent can
    -var clickedEvent =
    -  new ClickedLinkEvent({time: Date.now(), url: 'google.com'});
    -assert.ok(clickedEvent.url);
    -

    Discriminators save to the Event model's collection

    Suppose you created another discriminator to track events where -a new user registered. These SignedUpEvent instances will be -stored in the same collection as generic events and ClickedLinkEvent -instances.

    - -
    var event1 = new Event({time: Date.now()});
    -var event2 = new ClickedLinkEvent({time: Date.now(), url: 'google.com'});
    -var event3 = new SignedUpEvent({time: Date.now(), user: 'testuser'});
    -
    -var save = function (doc, callback) {
    -  doc.save(function (error, doc) {
    -    callback(error, doc);
    -  });
    -};
    -
    -async.map([event1, event2, event3], save, function (error) {
    -
    -  Event.countDocuments({}, function (error, count) {
    -    assert.equal(count, 3);
    -  });
    -});
    -

    Discriminator keys

    The way mongoose tells the difference between the different -discriminator models is by the 'discriminator key', which is -__t by default. Mongoose adds a String path called __t -to your schemas that it uses to track which discriminator -this document is an instance of.

    - -
    var event1 = new Event({time: Date.now()});
    -var event2 = new ClickedLinkEvent({time: Date.now(), url: 'google.com'});
    -var event3 = new SignedUpEvent({time: Date.now(), user: 'testuser'});
    -
    -assert.ok(!event1.__t);
    -assert.equal(event2.__t, 'ClickedLink');
    -assert.equal(event3.__t, 'SignedUp');
    -

    Discriminators add the discriminator key to queries

    Discriminator models are special; they attach the discriminator key -to queries. In other words, find(), count(), aggregate(), etc. -are smart enough to account for discriminators.

    - -
    var event1 = new Event({time: Date.now()});
    -var event2 = new ClickedLinkEvent({time: Date.now(), url: 'google.com'});
    -var event3 = new SignedUpEvent({time: Date.now(), user: 'testuser'});
    -
    -var save = function (doc, callback) {
    -  doc.save(function (error, doc) {
    -    callback(error, doc);
    -  });
    -};
    -
    -async.map([event1, event2, event3], save, function (error) {
    -
    -  ClickedLinkEvent.find({}, function (error, docs) {
    -    assert.equal(docs.length, 1);
    -    assert.equal(docs[0]._id.toString(), event2._id.toString());
    -    assert.equal(docs[0].url, 'google.com');
    -  });
    -});
    -

    Discriminators copy pre and post hooks

    Discriminators also take their base schema's pre and post middleware. -However, you can also attach middleware to the discriminator schema -without affecting the base schema.

    - -
    var options = {discriminatorKey: 'kind'};
    -
    -var eventSchema = new mongoose.Schema({time: Date}, options);
    -var eventSchemaCalls = 0;
    -eventSchema.pre('validate', function (next) {
    -  ++eventSchemaCalls;
    -  next();
    -});
    -var Event = mongoose.model('GenericEvent', eventSchema);
    -
    -var clickedLinkSchema = new mongoose.Schema({url: String}, options);
    -var clickedSchemaCalls = 0;
    -clickedLinkSchema.pre('validate', function (next) {
    -  ++clickedSchemaCalls;
    -  next();
    -});
    -var ClickedLinkEvent = Event.discriminator('ClickedLinkEvent',
    -  clickedLinkSchema);
    -
    -var event1 = new ClickedLinkEvent();
    -event1.validate(function() {
    -  assert.equal(eventSchemaCalls, 1);
    -  assert.equal(clickedSchemaCalls, 1);
    -
    -  var generic = new Event();
    -  generic.validate(function() {
    -    assert.equal(eventSchemaCalls, 2);
    -    assert.equal(clickedSchemaCalls, 1);
    -  });
    -});
    -

    Handling custom _id fields

    A discriminator's fields are the union of the base schema's fields and -the discriminator schema's fields, and the discriminator schema's fields -take precedence. There is one exception: the default _id field.

    -

    You can work around this by setting the _id option to false in the -discriminator schema as shown below.

    - -
    var options = {discriminatorKey: 'kind'};
    -
    -// Base schema has a String `_id` and a Date `time`...
    -var eventSchema = new mongoose.Schema({_id: String, time: Date},
    -  options);
    -var Event = mongoose.model('BaseEvent', eventSchema);
    -
    -var clickedLinkSchema = new mongoose.Schema({
    -  url: String,
    -  time: String
    -}, options);
    -// But the discriminator schema has a String `time`, and an implicitly added
    -// ObjectId `_id`.
    -assert.ok(clickedLinkSchema.path('_id'));
    -assert.equal(clickedLinkSchema.path('_id').instance, 'ObjectID');
    -var ClickedLinkEvent = Event.discriminator('ChildEventBad',
    -  clickedLinkSchema);
    -
    -var event1 = new ClickedLinkEvent({ _id: 'custom id', time: '4pm' });
    -// Woops, clickedLinkSchema overwrites the `time` path, but **not**
    -// the `_id` path because that was implicitly added.
    -assert.ok(typeof event1._id === 'string');
    -assert.ok(typeof event1.time === 'string');
    -

    Using discriminators with Model.create()

    When you use Model.create(), mongoose will pull the correct type from -the discriminator key for you.

    - -
    var Schema = mongoose.Schema;
    -var shapeSchema = new Schema({
    -  name: String
    -}, { discriminatorKey: 'kind' });
    -
    -var Shape = db.model('Shape', shapeSchema);
    -
    -var Circle = Shape.discriminator('Circle',
    -  new Schema({ radius: Number }));
    -var Square = Shape.discriminator('Square',
    -  new Schema({ side: Number }));
    -
    -var shapes = [
    -  { name: 'Test' },
    -  { kind: 'Circle', radius: 5 },
    -  { kind: 'Square', side: 10 }
    -];
    -Shape.create(shapes, function(error, shapes) {
    -  assert.ifError(error);
    -  assert.ok(shapes[0] instanceof Shape);
    -  assert.ok(shapes[1] instanceof Circle);
    -  assert.equal(shapes[1].radius, 5);
    -  assert.ok(shapes[2] instanceof Square);
    -  assert.equal(shapes[2].side, 10);
    -});
    -

    Embedded discriminators in arrays

    You can also define discriminators on embedded document arrays. -Embedded discriminators are different because the different discriminator -types are stored in the same document array (within a document) rather -than the same collection. In other words, embedded discriminators let -you store subdocuments matching different schemas in the same array.

    -

    As a general best practice, make sure you declare any hooks on your -schemas before you use them. You should not call pre() or -post() after calling discriminator()

    - -
    var eventSchema = new Schema({ message: String },
    -  { discriminatorKey: 'kind', _id: false });
    -
    -var batchSchema = new Schema({ events: [eventSchema] });
    -
    -// `batchSchema.path('events')` gets the mongoose `DocumentArray`
    -var docArray = batchSchema.path('events');
    -
    -// The `events` array can contain 2 different types of events, a
    -// 'clicked' event that requires an element id that was clicked...
    -var clickedSchema = new Schema({
    -  element: {
    -    type: String,
    -    required: true
    -  }
    -}, { _id: false });
    -// Make sure to attach any hooks to `eventSchema` and `clickedSchema`
    -// **before** calling `discriminator()`.
    -var Clicked = docArray.discriminator('Clicked', clickedSchema);
    -
    -// ... and a 'purchased' event that requires the product that was purchased.
    -var Purchased = docArray.discriminator('Purchased', new Schema({
    -  product: {
    -    type: String,
    -    required: true
    -  }
    -}, { _id: false }));
    -
    -var Batch = db.model('EventBatch', batchSchema);
    -
    -// Create a new batch of events with different kinds
    -var batch = {
    -  events: [
    -    { kind: 'Clicked', element: '#hero', message: 'hello' },
    -    { kind: 'Purchased', product: 'action-figure-1', message: 'world' }
    -  ]
    -};
    -
    -Batch.create(batch).
    -  then(function(doc) {
    -    assert.equal(doc.events.length, 2);
    -
    -    assert.equal(doc.events[0].element, '#hero');
    -    assert.equal(doc.events[0].message, 'hello');
    -    assert.ok(doc.events[0] instanceof Clicked);
    -
    -    assert.equal(doc.events[1].product, 'action-figure-1');
    -    assert.equal(doc.events[1].message, 'world');
    -    assert.ok(doc.events[1] instanceof Purchased);
    -
    -    doc.events.push({ kind: 'Purchased', product: 'action-figure-2' });
    -    return doc.save();
    -  }).
    -  then(function(doc) {
    -    assert.equal(doc.events.length, 3);
    -
    -    assert.equal(doc.events[2].product, 'action-figure-2');
    -    assert.ok(doc.events[2] instanceof Purchased);
    -
    -    done();
    -  }).
    -  catch(done);
    -

    Recursive embedded discriminators in arrays

    You can also define embedded discriminators on embedded discriminators. -In the below example, sub_events is an embedded discriminator, and -for sub_event keys with value 'SubEvent', sub_events.events is an -embedded discriminator.

    - -
    var singleEventSchema = new Schema({ message: String },
    -  { discriminatorKey: 'kind', _id: false });
    -
    -var eventListSchema = new Schema({ events: [singleEventSchema] });
    -
    -var subEventSchema = new Schema({
    -   sub_events: [singleEventSchema]
    -}, { _id: false });
    -
    -var SubEvent = subEventSchema.path('sub_events').
    -  discriminator('SubEvent', subEventSchema);
    -eventListSchema.path('events').discriminator('SubEvent', subEventSchema);
    -
    -var Eventlist = db.model('EventList', eventListSchema);
    -
    -// Create a new batch of events with different kinds
    -var list = {
    -  events: [
    -    { kind: 'SubEvent', sub_events: [{kind:'SubEvent', sub_events:[], message:'test1'}], message: 'hello' },
    -    { kind: 'SubEvent', sub_events: [{kind:'SubEvent', sub_events:[{kind:'SubEvent', sub_events:[], message:'test3'}], message:'test2'}], message: 'world' }
    -  ]
    -};
    -
    -Eventlist.create(list).
    -  then(function(doc) {
    -    assert.equal(doc.events.length, 2);
    -
    -    assert.equal(doc.events[0].sub_events[0].message, 'test1');
    -    assert.equal(doc.events[0].message, 'hello');
    -    assert.ok(doc.events[0].sub_events[0] instanceof SubEvent);
    -
    -    assert.equal(doc.events[1].sub_events[0].sub_events[0].message, 'test3');
    -    assert.equal(doc.events[1].message, 'world');
    -    assert.ok(doc.events[1].sub_events[0].sub_events[0] instanceof SubEvent);
    -
    -    doc.events.push({kind:'SubEvent', sub_events:[{kind:'SubEvent', sub_events:[], message:'test4'}], message:'pushed'});
    -    return doc.save();
    -  }).
    -  then(function(doc) {
    -    assert.equal(doc.events.length, 3);
    -
    -    assert.equal(doc.events[2].message, 'pushed');
    -    assert.ok(doc.events[2].sub_events[0] instanceof SubEvent);
    -
    -    done();
    -  }).
    -  catch(done);
    -
    \ No newline at end of file diff --git a/docs/discriminators.md b/docs/discriminators.md new file mode 100644 index 00000000000..670fd78118b --- /dev/null +++ b/docs/discriminators.md @@ -0,0 +1,42 @@ +## Discriminators + +### The `model.discriminator()` function + +Discriminators are a schema inheritance mechanism. They enable +you to have multiple models with overlapping schemas on top of the +same underlying MongoDB collection. + +Suppose you wanted to track different types of events in a single +collection. Every event will have a timestamp, but events that +represent clicked links should have a URL. You can achieve this +using the `model.discriminator()` function. This function takes +3 parameters, a model name, a discriminator schema and an optional +key (defaults to the model name). It returns a model whose schema +is the union of the base schema and the discriminator schema. + +```javascript +[require:The `model.discriminator\(\)` function] +``` + +### Discriminators save to the Event model's collection + +Suppose you created another discriminator to track events where +a new user registered. These `SignedUpEvent` instances will be +stored in the same collection as generic events and `ClickedLinkEvent` +instances. + +```javascript +[require:Discriminators save to the Event model's collection] +``` + +### Discriminator keys + +The way mongoose tells the difference between the different +discriminator models is by the 'discriminator key', which is +`__t` by default. Mongoose adds a String path called `__t` +to your schemas that it uses to track which discriminator +this document is an instance of. + +```javascript +[require:Discriminator keys] +``` \ No newline at end of file diff --git a/docs/documents.html b/docs/documents.html deleted file mode 100644 index e21e36fbc7e..00000000000 --- a/docs/documents.html +++ /dev/null @@ -1,154 +0,0 @@ -Mongoose v5.6.0: Documents

    Documents

    - - - - -

    Mongoose documents represent a one-to-one mapping -to documents as stored in MongoDB. Each document is an instance of its -Model.

    - - -

    Documents vs Models

    -

    Document and Model are distinct -classes in Mongoose. The Model class is a subclass of the Document class. -When you use the Model constructor, you create a -new document.

    -
    const MyModel = mongoose.model('Test', new Schema({ name: String }));
    -const doc = new MyModel();
    -
    -doc instanceof MyModel; // true
    -doc instanceof mongoose.Model; // true
    -doc instanceof mongoose.Document; // true
    -

    In Mongoose, a "document" generally means an instance of a model. -You should not have to create an instance of the Document class without -going through a model.

    -

    Retrieving

    -

    When you load documents from MongoDB using model functions like findOne(), -you get a Mongoose document back.

    -
    const doc = await MyModel.findOne();
    -
    -doc instanceof MyModel; // true
    -doc instanceof mongoose.Model; // true
    -doc instanceof mongoose.Document; // true
    -

    Updating

    -

    Mongoose documents track changes. You can modify a document using vanilla -JavaScript assignments and Mongoose will convert it into MongoDB update operators.

    -
    doc.name = 'foo';
    -
    -// Mongoose sends a `updateOne({ _id: doc._id }, { $set: { name: 'foo' } })`
    -// to MongoDB.
    -await doc.save();
    -

    If the document with the corresponding _id is not found, Mongoose will -report a DocumentNotFoundError:

    -
    const doc = await MyModel.findOne();
    -
    -// Delete the document so Mongoose won't be able to save changes
    -await MyModel.deleteOne({ _id: doc._id });
    -
    -doc.name = 'foo';
    -await doc.save(); // Throws DocumentNotFoundError
    -

    The save() function is generally the right -way to update a document with Mongoose. With save(), you get full -validation and middleware.

    -

    For cases when save() isn't flexible enough, Mongoose lets you create -your own MongoDB updates -with casting, middleware, and limited validation.

    -
    // Update all documents in the `mymodels` collection
    -await MyModel.updateMany({}, { $set: { name: 'foo' } });
    -

    Note that update(), updateMany(), findOneAndUpdate(), etc. do not -execute save() middleware. If you need save middleware and full validation, -first query for the document and then save() it.

    -

    Validating

    -

    Documents are casted validated before they are saved. Mongoose first casts -values to the specified type and then validates them. Internally, Mongoose -calls the document's validate() method -before saving.

    -
    const schema = new Schema({ name: String, age: { type: Number, min: 0 } });
    -const Person = mongoose.model('Person', schema);
    -
    -let p = new Person({ name: 'foo', age: 'bar' });
    -// Cast to Number failed for value "bar" at path "age"
    -await p.validate();
    -
    -let p2 = new Person({ name: 'foo', age: -1 });
    -// Path `age` (-1) is less than minimum allowed value (0).
    -await p2.validate();
    -

    Mongoose also supports limited validation on updates using the runValidators option. -Mongoose casts parameters to query functions like findOne(), updateOne() -by default. However, Mongoose does not run validation on query function -parameters by default. You need to set runValidators: true for Mongoose -to validate.

    -
    // Cast to number failed for value "bar" at path "age"
    -await Person.updateOne({}, { age: 'bar' });
    -
    -// Path `age` (-1) is less than minimum allowed value (0).
    -await Person.updateOne({}, { age: -1 }, { runValidators: true });
    -

    Read the validation guide for more details.

    -

    Next Up

    -

    Now that we've covered Documents, let's take a look at -Subdocuments.

    -
    \ No newline at end of file diff --git a/docs/documents.md b/docs/documents.md new file mode 100644 index 00000000000..5c762adc7f9 --- /dev/null +++ b/docs/documents.md @@ -0,0 +1,164 @@ +## Documents + +Mongoose [documents](./api/document.html) represent a one-to-one mapping +to documents as stored in MongoDB. Each document is an instance of its +[Model](./models.html). + + + +

    Documents vs Models

    + +[Document](api.html#Document) and [Model](api.html#Model) are distinct +classes in Mongoose. The Model class is a subclass of the Document class. +When you use the [Model constructor](api.html#Model), you create a +new document. + +```javascript +const MyModel = mongoose.model('Test', new Schema({ name: String })); +const doc = new MyModel(); + +doc instanceof MyModel; // true +doc instanceof mongoose.Model; // true +doc instanceof mongoose.Document; // true +``` + +In Mongoose, a "document" generally means an instance of a model. +You should not have to create an instance of the Document class without +going through a model. + +

    Retrieving

    + +When you load documents from MongoDB using model functions like [`findOne()`](api.html#model_Model.findOne), +you get a Mongoose document back. + +```javascript +const doc = await MyModel.findOne(); + +doc instanceof MyModel; // true +doc instanceof mongoose.Model; // true +doc instanceof mongoose.Document; // true +``` + +

    Updating Using save()

    + +Mongoose documents track changes. You can modify a document using vanilla +JavaScript assignments and Mongoose will convert it into [MongoDB update operators](https://docs.mongodb.com/manual/reference/operator/update/). + +```javascript +doc.name = 'foo'; + +// Mongoose sends an `updateOne({ _id: doc._id }, { $set: { name: 'foo' } })` +// to MongoDB. +await doc.save(); +``` + +The `save()` method returns a promise. If `save()` succeeds, the promise +resolves to the document that was saved. + +```javascript +doc.save().then(savedDoc => { + savedDoc === doc; // true +}); +``` + +If the document with the corresponding `_id` is not found, Mongoose will +report a `DocumentNotFoundError`: + +```javascript +const doc = await MyModel.findOne(); + +// Delete the document so Mongoose won't be able to save changes +await MyModel.deleteOne({ _id: doc._id }); + +doc.name = 'foo'; +await doc.save(); // Throws DocumentNotFoundError +``` + +

    Updating Using Queries

    + +The [`save()`](api.html#model_Model-save) function is generally the right +way to update a document with Mongoose. With `save()`, you get full +[validation](validation.html) and [middleware](middleware.html). + +For cases when `save()` isn't flexible enough, Mongoose lets you create +your own [MongoDB updates](https://docs.mongodb.com/manual/reference/operator/update/) +with casting, [middleware](middleware.html#notes), and [limited validation](validation.html#update-validators). + +```javascript +// Update all documents in the `mymodels` collection +await MyModel.updateMany({}, { $set: { name: 'foo' } }); +``` + +_Note that `update()`, `updateMany()`, `findOneAndUpdate()`, etc. do *not* +execute `save()` middleware. If you need save middleware and full validation, +first query for the document and then `save()` it._ + +

    Validating

    + +Documents are casted and validated before they are saved. Mongoose first casts +values to the specified type and then validates them. Internally, Mongoose +calls the document's [`validate()` method](api.html#document_Document-validate) +before saving. + +```javascript +const schema = new Schema({ name: String, age: { type: Number, min: 0 } }); +const Person = mongoose.model('Person', schema); + +let p = new Person({ name: 'foo', age: 'bar' }); +// Cast to Number failed for value "bar" at path "age" +await p.validate(); + +let p2 = new Person({ name: 'foo', age: -1 }); +// Path `age` (-1) is less than minimum allowed value (0). +await p2.validate(); +``` + +Mongoose also supports limited validation on updates using the [`runValidators` option](validation.html#update-validators). +Mongoose casts parameters to query functions like `findOne()`, `updateOne()` +by default. However, Mongoose does _not_ run validation on query function +parameters by default. You need to set `runValidators: true` for Mongoose +to validate. + +```javascript +// Cast to number failed for value "bar" at path "age" +await Person.updateOne({}, { age: 'bar' }); + +// Path `age` (-1) is less than minimum allowed value (0). +await Person.updateOne({}, { age: -1 }, { runValidators: true }); +``` + +Read the [validation](./validation.html) guide for more details. + +

    Overwriting

    + +There are 2 different ways to overwrite a document (replacing all keys in the +document). One way is to use the +[`Document#overwrite()` function](/docs/api/document.html#document_Document-overwrite) +followed by `save()`. + +```javascript +const doc = await Person.findOne({ _id }); + +// Sets `name` and unsets all other properties +doc.overwrite({ name: 'Jean-Luc Picard' }); +await doc.save(); +``` + +The other way is to use [`Model.replaceOne()`](/docs/api/model.html#model_Model.replaceOne). + +```javascript +// Sets `name` and unsets all other properties +await Person.replaceOne({ _id }, { name: 'Jean-Luc Picard' }); +``` + +### Next Up + +Now that we've covered Documents, let's take a look at +[Subdocuments](/docs/subdocs.html). \ No newline at end of file diff --git a/docs/documents.pug b/docs/documents.pug deleted file mode 100644 index 5ac1d1f7965..00000000000 --- a/docs/documents.pug +++ /dev/null @@ -1,186 +0,0 @@ -extends layout - -append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown - ## Documents - - - - - - Mongoose [documents](./api/document.html) represent a one-to-one mapping - to documents as stored in MongoDB. Each document is an instance of its - [Model](./models.html). - - - - ### Documents vs Models - - [Document](api.html#Document) and [Model](api.html#Model) are distinct - classes in Mongoose. The Model class is a subclass of the Document class. - When you use the [Model constructor](api.html#Model), you create a - new document. - - ```javascript - const MyModel = mongoose.model('Test', new Schema({ name: String })); - const doc = new MyModel(); - - doc instanceof MyModel; // true - doc instanceof mongoose.Model; // true - doc instanceof mongoose.Document; // true - ``` - - In Mongoose, a "document" generally means an instance of a model. - You should not have to create an instance of the Document class without - going through a model. - - ### Retrieving - - When you load documents from MongoDB using model functions like [`findOne()`](api.html#model_Model.findOne), - you get a Mongoose document back. - - ```javascript - const doc = await MyModel.findOne(); - - doc instanceof MyModel; // true - doc instanceof mongoose.Model; // true - doc instanceof mongoose.Document; // true - ``` - - ### Updating Using `save()` - - Mongoose documents track changes. You can modify a document using vanilla - JavaScript assignments and Mongoose will convert it into [MongoDB update operators](https://docs.mongodb.com/manual/reference/operator/update/). - - ```javascript - doc.name = 'foo'; - - // Mongoose sends an `updateOne({ _id: doc._id }, { $set: { name: 'foo' } })` - // to MongoDB. - await doc.save(); - ``` - - The `save()` method returns a promise. If `save()` succeeds, the promise - resolves to the document that was saved. - - ```javascript - doc.save().then(savedDoc => { - savedDoc === doc; // true - }); - ``` - - If the document with the corresponding `_id` is not found, Mongoose will - report a `DocumentNotFoundError`: - - ```javascript - const doc = await MyModel.findOne(); - - // Delete the document so Mongoose won't be able to save changes - await MyModel.deleteOne({ _id: doc._id }); - - doc.name = 'foo'; - await doc.save(); // Throws DocumentNotFoundError - ``` - - ### Updating Using Queries - - The [`save()`](api.html#model_Model-save) function is generally the right - way to update a document with Mongoose. With `save()`, you get full - [validation](validation.html) and [middleware](middleware.html). - - For cases when `save()` isn't flexible enough, Mongoose lets you create - your own [MongoDB updates](https://docs.mongodb.com/manual/reference/operator/update/) - with casting, [middleware](middleware.html#notes), and [limited validation](validation.html#update-validators). - - ```javascript - // Update all documents in the `mymodels` collection - await MyModel.updateMany({}, { $set: { name: 'foo' } }); - ``` - - _Note that `update()`, `updateMany()`, `findOneAndUpdate()`, etc. do *not* - execute `save()` middleware. If you need save middleware and full validation, - first query for the document and then `save()` it._ - - ### Validating - - Documents are casted and validated before they are saved. Mongoose first casts - values to the specified type and then validates them. Internally, Mongoose - calls the document's [`validate()` method](api.html#document_Document-validate) - before saving. - - ```javascript - const schema = new Schema({ name: String, age: { type: Number, min: 0 } }); - const Person = mongoose.model('Person', schema); - - let p = new Person({ name: 'foo', age: 'bar' }); - // Cast to Number failed for value "bar" at path "age" - await p.validate(); - - let p2 = new Person({ name: 'foo', age: -1 }); - // Path `age` (-1) is less than minimum allowed value (0). - await p2.validate(); - ``` - - Mongoose also supports limited validation on updates using the [`runValidators` option](validation.html#update-validators). - Mongoose casts parameters to query functions like `findOne()`, `updateOne()` - by default. However, Mongoose does _not_ run validation on query function - parameters by default. You need to set `runValidators: true` for Mongoose - to validate. - - ```javascript - // Cast to number failed for value "bar" at path "age" - await Person.updateOne({}, { age: 'bar' }); - - // Path `age` (-1) is less than minimum allowed value (0). - await Person.updateOne({}, { age: -1 }, { runValidators: true }); - ``` - - Read the [validation](./validation.html) guide for more details. - - ### Overwriting - - There are 2 different ways to overwrite a document (replacing all keys in the - document). One way is to use the - [`Document#overwrite()` function](/docs/api/document.html#document_Document-overwrite) - followed by `save()`. - - ```javascript - const doc = await Person.findOne({ _id }); - - // Sets `name` and unsets all other properties - doc.overwrite({ name: 'Jean-Luc Picard' }); - await doc.save(); - ``` - - The other way is to use [`Model.replaceOne()`](/docs/api/model.html#model_Model.replaceOne). - - ```javascript - // Sets `name` and unsets all other properties - await Person.replaceOne({ _id }, { name: 'Jean-Luc Picard' }); - ``` - - ### Next Up - - Now that we've covered Documents, let's take a look at - [Subdocuments](/docs/subdocs.html). diff --git a/docs/enterprise.md b/docs/enterprise.md new file mode 100644 index 00000000000..3459a8b8933 --- /dev/null +++ b/docs/enterprise.md @@ -0,0 +1,71 @@ +# Mongoose for Enterprise + +### Available as part of the Tidelift Subscription + +Tidelift is working with the maintainers of Mongoose and thousands of other +open source projects to deliver commercial support and maintenance for the +open source dependencies you use to build your applications. Save time, +reduce risk, and improve code health, while paying the maintainers of the +exact dependencies you use. + + + + + + + + +### Enterprise-ready open source software—managed for you + +The Tidelift Subscription is a managed open source subscription for application +dependencies covering millions of open source projects across JavaScript, +Python, Java, PHP, Ruby, .NET, and more. + +Your subscription includes: + +- Security updates + +Tidelift’s security response team coordinates patches for new breaking security +vulnerabilities and alerts immediately through a private channel, so your +software supply chain is always secure. + +- Licensing verification and indemnification + +Tidelift verifies license information to enable easy policy enforcement and +adds intellectual property indemnification to cover creators and users in case +something goes wrong. You always have a 100% up-to-date bill of materials for +your dependencies to share with your legal team, customers, or partners. + +- Maintenance and code improvement + +Tidelift ensures the software you rely on keeps working as long as you need it +to work. Your managed dependencies are actively maintained and we recruit +additional maintainers where required. + +- Package selection and version guidance + +We help you choose the best open source packages from the start—and then +guide you through updates to stay on the best releases as new issues arise. + +- Roadmap input + +Take a seat at the table with the creators behind the software you use. +Tidelift’s participating maintainers earn more income as their software is +used by more subscribers, so they’re interested in knowing what you need. + +- Tooling and cloud integration + +Tidelift works with GitHub, GitLab, BitBucket, and more. We support every +cloud platform (and other deployment targets, too). + +The end result? All of the capabilities you expect from commercial-grade +software, for the full breadth of open source you use. That means less time +grappling with esoteric open source trivia, and more time building your own +applications—and your business. + + + + + + + \ No newline at end of file diff --git a/docs/enterprise.pug b/docs/enterprise.pug deleted file mode 100644 index 13df7b14d54..00000000000 --- a/docs/enterprise.pug +++ /dev/null @@ -1,108 +0,0 @@ -extends layout - -append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - - style. - button { - border-radius: 3px; - padding: 3px; - padding-left: 30px; - padding-right: 30px; - } - - button.btn-white { - border: 1px solid #800; - color: #800; - } - - button.btn-color { - border: 1px solid transparent; - color: white; - background-color: #800; - } - -block content - :markdown - ## Mongoose for Enterprise - - - - - - ### Available as part of the Tidelift Subscription - - Tidelift is working with the maintainers of Mongoose and thousands of other - open source projects to deliver commercial support and maintenance for the - open source dependencies you use to build your applications. Save time, - reduce risk, and improve code health, while paying the maintainers of the - exact dependencies you use. - - - - - - - - - ### Enterprise-ready open source software—managed for you - - The Tidelift Subscription is a managed open source subscription for application - dependencies covering millions of open source projects across JavaScript, - Python, Java, PHP, Ruby, .NET, and more. - - Your subscription includes: - - - Security updates - - Tidelift’s security response team coordinates patches for new breaking security - vulnerabilities and alerts immediately through a private channel, so your - software supply chain is always secure. - - - Licensing verification and indemnification - - Tidelift verifies license information to enable easy policy enforcement and - adds intellectual property indemnification to cover creators and users in case - something goes wrong. You always have a 100% up-to-date bill of materials for - your dependencies to share with your legal team, customers, or partners. - - - Maintenance and code improvement - - Tidelift ensures the software you rely on keeps working as long as you need it - to work. Your managed dependencies are actively maintained and we recruit - additional maintainers where required. - - - Package selection and version guidance - - We help you choose the best open source packages from the start—and then - guide you through updates to stay on the best releases as new issues arise. - - - Roadmap input - - Take a seat at the table with the creators behind the software you use. - Tidelift’s participating maintainers earn more income as their software is - used by more subscribers, so they’re interested in knowing what you need. - - - Tooling and cloud integration - - Tidelift works with GitHub, GitLab, BitBucket, and more. We support every - cloud platform (and other deployment targets, too). - - The end result? All of the capabilities you expect from commercial-grade - software, for the full breadth of open source you use. That means less time - grappling with esoteric open source trivia, and more time building your own - applications—and your business. - - - - - - - diff --git a/docs/faq.html b/docs/faq.html deleted file mode 100644 index 67cf803bf43..00000000000 --- a/docs/faq.html +++ /dev/null @@ -1,372 +0,0 @@ -Mongoose v5.6.0: FAQ

    FAQ

    - - - - -

    Q. Why don't my changes to arrays get saved when I update an element -directly?

    -
    doc.array[3] = 'changed';
    -doc.save();
    -

    A. Mongoose doesn't create getters/setters for array indexes; without -them mongoose never gets notified of the change and so doesn't know to -persist the new value. The work-around is to use -MongooseArray#set available -in Mongoose >= 3.2.0.

    -
    // 3.2.0
    -doc.array.set(3, 'changed');
    -doc.save();
    -
    -// if running a version less than 3.2.0, you must mark the array modified before saving.
    -doc.array[3] = 'changed';
    -doc.markModified('array');
    -doc.save();
    -
    - -

    Q. I declared a schema property as unique but I can still save -duplicates. What gives?

    -

    A. Mongoose doesn't handle unique on its own: { name: { type: String, unique: true } } -is just a shorthand for creating a MongoDB unique index on name. -For example, if MongoDB doesn't already have a unique index on name, the below code will not error despite the fact that unique is true.

    -
    var schema = new mongoose.Schema({
    -  name: { type: String, unique: true }
    -});
    -var Model = db.model('Test', schema);
    -
    -Model.create([{ name: 'Val' }, { name: 'Val' }], function(err) {
    -  console.log(err); // No error, unless index was already built
    -});
    -

    However, if you wait for the index to build using the Model.on('index') event, attempts to save duplicates will correctly error.

    -
    var schema = new mongoose.Schema({
    -  name: { type: String, unique: true }
    -});
    -var Model = db.model('Test', schema);
    -
    -Model.on('index', function(err) { // <-- Wait for model's indexes to finish
    -  assert.ifError(err);
    -  Model.create([{ name: 'Val' }, { name: 'Val' }], function(err) {
    -    console.log(err);
    -  });
    -});
    -
    -// Promise based alternative. `init()` returns a promise that resolves
    -// when the indexes have finished building successfully. The `init()`
    -// function is idempotent, so don't worry about triggering an index rebuild.
    -Model.init().then(function() {
    -  assert.ifError(err);
    -  Model.create([{ name: 'Val' }, { name: 'Val' }], function(err) {
    -    console.log(err);
    -  });
    -});
    -

    MongoDB persists indexes, so you only need to rebuild indexes if you're starting -with a fresh database or you ran db.dropDatabase(). In a production environment, -you should create your indexes using the MongoDB shell -rather than relying on mongoose to do it for you. The unique option for schemas is -convenient for development and documentation, but mongoose is not an index management solution.

    -
    - -

    Q. When I have a nested property in a schema, mongoose adds empty objects by default. Why?

    -
    var schema = new mongoose.Schema({
    -  nested: {
    -    prop: String
    -  }
    -});
    -var Model = db.model('Test', schema);
    -
    -// The below prints `{ _id: /* ... */, nested: {} }`, mongoose assigns
    -// `nested` to an empty object `{}` by default.
    -console.log(new Model());
    -

    A. This is a performance optimization. These empty objects are not saved -to the database, nor are they in the result toObject(), nor do they show -up in JSON.stringify() output unless you turn off the minimize option.

    -

    The reason for this behavior is that Mongoose's change detection -and getters/setters are based on Object.defineProperty(). -In order to support change detection on nested properties without incurring -the overhead of running Object.defineProperty() every time a document is created, -mongoose defines properties on the Model prototype when the model is compiled. -Because mongoose needs to define getters and setters for nested.prop, nested -must always be defined as an object on a mongoose document, even if nested -is undefined on the underlying POJO.

    -
    - -

    Q. When I use named imports like import { set } from 'mongoose', I -get a TypeError. What causes this issue and how can I fix it?

    -

    A. The only import syntax Mongoose supports is import mongoose from 'mongoose'. -Syntaxes like import * from 'mongoose' or import { model } from 'mongoose' do not work. -The global Mongoose object stores types, global options, and other important -properties that Mongoose needs. When you do import { model } from 'mongoose', the -this value in model() is not the Mongoose global.

    -
    // file1.js
    -exports.answer = 42;
    -exports.foo = function() { console.log(this.answer); };
    -
    -// file2.js
    -const obj = require('./file1');
    -obj.foo(); // "42"
    -
    -// file3.js
    -const { foo } = require('./file1');
    -foo(); // "undefined"
    -
    - -

    Q. I'm using an arrow function for a virtual, middleware, getter/setter, or method and the value of this is wrong.

    -

    A. Arrow functions handle the this keyword much differently than conventional functions. -Mongoose getters/setters depend on this to give you access to the document that you're writing to, but this functionality does not work with arrow functions. Do not use arrow functions for mongoose getters/setters unless do not intend to access the document in the getter/setter.

    -
    // Do **NOT** use arrow functions as shown below unless you're certain
    -// that's what you want. If you're reading this FAQ, odds are you should
    -// just be using a conventional function.
    -var schema = new mongoose.Schema({
    -  propWithGetter: {
    -    type: String,
    -    get: v => {
    -      // Will **not** be the doc, do **not** use arrow functions for getters/setters
    -      console.log(this);
    -      return v;
    -    }
    -  }
    -});
    -
    -// `this` will **not** be the doc, do **not** use arrow functions for methods
    -schema.method.arrowMethod = () => this;
    -schema.virtual('virtualWithArrow').get(() => {
    -  // `this` will **not** be the doc, do **not** use arrow functions for virtuals
    -  console.log(this);
    -});
    -
    - -

    Q. I have an embedded property named type like this:

    -
    const holdingSchema = new Schema({
    -  // You might expect `asset` to be an object that has 2 properties,
    -  // but unfortunately `type` is special in mongoose so mongoose
    -  // interprets this schema to mean that `asset` is a string
    -  asset: {
    -    type: String,
    -    ticker: String
    -  }
    -});
    -

    But mongoose gives me a CastError telling me that it can't cast an object -to a string when I try to save a Holding with an asset object. Why -is this?

    -
    Holding.create({ asset: { type: 'stock', ticker: 'MDB' } }).catch(error => {
    -  // Cast to String failed for value "{ type: 'stock', ticker: 'MDB' }" at path "asset"
    -  console.error(error);
    -});
    -

    A. The type property is special in mongoose, so when you say -type: String, mongoose interprets it as a type declaration. In the -above schema, mongoose thinks asset is a string, not an object. Do -this instead:

    -
    const holdingSchema = new Schema({
    -  // This is how you tell mongoose you mean `asset` is an object with
    -  // a string property `type`, as opposed to telling mongoose that `asset`
    -  // is a string.
    -  asset: {
    -    type: { type: String },
    -    ticker: String
    -  }
    -});
    -
    - -

    Q. I'm populating a nested property under an array like the below code:

    -
      new Schema({
    -    arr: [{
    -      child: { ref: 'OtherModel', type: Schema.Types.ObjectId }
    -    }]
    -  });
    -

    .populate({ path: 'arr.child', options: { sort: 'name' } }) won't sort -by arr.child.name?

    -

    A. See this GitHub issue. It's a known issue but one that's exceptionally difficult to fix.

    -
    - -

    Q. All function calls on my models hang, what am I doing wrong?

    -

    A. By default, mongoose will buffer your function calls until it can -connect to MongoDB. Read the buffering section of the connection docs -for more information.

    -
    - -

    Q. How can I enable debugging?

    -

    A. Set the debug option to true:

    -
    mongoose.set('debug', true)
    -

    All executed collection methods will log output of their arguments to your -console.

    -
    - -

    Q. My save() callback never executes. What am I doing wrong?

    -

    A. All collection actions (insert, remove, queries, etc.) are queued -until the connection opens. It is likely that an error occurred while -attempting to connect. Try adding an error handler to your connection.

    -
    // if connecting on the default mongoose connection
    -mongoose.connect(..);
    -mongoose.connection.on('error', handleError);
    -
    -// if connecting on a separate connection
    -var conn = mongoose.createConnection(..);
    -conn.on('error', handleError);
    -

    If you want to opt out of mongoose's buffering mechanism across your entire -application, set the global bufferCommands option to false:

    -
    mongoose.set('bufferCommands', false);
    -
    - -

    Q. Should I create/destroy a new connection for each database operation?

    -

    A. No. Open your connection when your application starts up and leave -it open until the application shuts down.

    -
    - -

    Q. Why do I get "OverwriteModelError: Cannot overwrite .. model once -compiled" when I use nodemon / a testing framework?

    -

    A. mongoose.model('ModelName', schema) requires 'ModelName' to be -unique, so you can access the model by using mongoose.model('ModelName'). -If you put mongoose.model('ModelName', schema); in a -mocha beforeEach() hook, this code will -attempt to create a new model named 'ModelName' before every test, -and so you will get an error. Make sure you only create a new model with -a given name once. If you need to create multiple models with the -same name, create a new connection and bind the model to the connection.

    -
    var mongoose = require('mongoose');
    -var connection = mongoose.createConnection(..);
    -
    -// use mongoose.Schema
    -var kittySchema = mongoose.Schema({ name: String });
    -
    -// use connection.model
    -var Kitten = connection.model('Kitten', kittySchema);
    -
    - -

    Q. How can I change mongoose's default behavior of initializing an array -path to an empty array so that I can require real data on document creation?

    -

    A. You can set the default of the array to a function that returns undefined.

    -
    const CollectionSchema = new Schema({
    -  field1: {
    -    type: [String],
    -    default: void 0
    -  }
    -});
    -

    Q. How can I initialize an array path to null?

    -

    A. You can set the default of the array to a function that returns null.

    -
    const CollectionSchema = new Schema({
    -  field1: {
    -    type: [String],
    -    default: () => { return null; }
    -  }
    -});
    -
    - -

    Q. Why does my aggregate $match fail to return the document that my find query -returns when working with dates?

    -

    A. Mongoose does not cast aggregation pipeline stages because with $project, -$group, etc. the type of a property may change during the aggregation. If you want -to query by date using the aggregation framework, you're responsible for ensuring -that you're passing in a valid date.

    -
    - -

    Q. Why don't in-place modifications to date objects -(e.g. date.setMonth(1);) get saved?

    -
    doc.createdAt.setDate(2011, 5, 1);
    -doc.save(); // createdAt changes won't get saved!
    -

    A. Mongoose currently doesn't watch for in-place updates to date -objects. If you have need for this feature, feel free to discuss on -this GitHub issue. -There are several workarounds:

    -
    doc.createdAt.setDate(2011, 5, 1);
    -doc.markModified('createdAt');
    -doc.save(); // Works
    -
    -doc.createdAt = new Date(2011, 5, 1).setHours(4);
    -doc.save(); // Works
    -
    - -

    Q. Why does calling save() multiple times on the same document in parallel only let -the first save call succeed and return ParallelSaveErrors for the rest?

    -

    A. Due to the asynchronous nature of validation and middleware in general, calling -save() multiple times in parallel on the same doc could result in conflicts. For example, -validating, and then subsequently invalidating the same path.

    -
    - -

    Q. Why is any 12 character string successfully cast to an ObjectId?

    -

    A. Technically, any 12 character string is a valid ObjectId. -Consider using a regex like /^[a-f0-9]{24}$/ to test whether a string is exactly 24 hex characters.

    -
    - -

    Q. I'm connecting to localhost and it takes me nearly 1 second to connect. How do I fix this?

    -

    A. The underlying MongoDB driver defaults to looking for IPv6 addresses, so the most likely cause is that your localhost DNS mapping isn't configured to handle IPv6. Use 127.0.0.1 instead of localhost or use the family option as shown in the connection docs.

    -
    // One alternative is to bypass 'localhost'
    -mongoose.connect('mongodb://127.0.0.1:27017/test');
    -// Another option is to specify the `family` option, which tells the
    -// MongoDB driver to only look for IPv4 addresses rather than IPv6 first.
    -mongoose.connect('mongodb://localhost:27017/test', { family: 4 });
    -
    - -

    Q. Why do keys in Mongoose Maps have to be strings?

    -

    A. Because the Map eventually gets stored in MongoDB where the keys must be strings.

    -
    - -

    Something to add?

    -

    If you'd like to contribute to this page, please -visit it -on github and use the Edit button to send a pull request.

    -
    \ No newline at end of file diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 00000000000..33cc25e3f2e --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,461 @@ +## FAQ + + + +
    + +**Q**. Why don't my changes to arrays get saved when I update an element + directly? + +```javascript +doc.array[3] = 'changed'; +doc.save(); +``` + +**A**. Mongoose doesn't create getters/setters for array indexes; without +them mongoose never gets notified of the change and so doesn't know to +persist the new value. There are two workarounds: +[`MongooseArray#set`](./api.html#types_array_MongooseArray.set) or +[`Document#markModified()`](/docs/api/document.html#document_Document-markModified). + +```javascript +// Saves changes successfully +doc.array.set(3, 'changed'); +doc.save(); + +// Also saves changes successfully +doc.array[3] = 'changed'; +doc.markModified('array'); +doc.save(); +``` + +This **only** affects when you set an array index directly. If you set +a path on a document array element, you do not need to use `markModified()`. + +```javascript +// Saves changes successfully without `markModified()`, because this +// code doesn't set an array index, it sets a path underneath an array index. +doc.docArray[3].name = 'changed'; +doc.save(); + +// Does **not** save changes successfully. You need to use `markModified()` +// or `set()` because this sets an array index. +doc.docArray[3] = { name: 'changed' }; +doc.save(); +``` + +
    + +**Q**. I declared a schema property as `unique` but I can still save duplicates. What gives? + +**A**. Mongoose doesn't handle `unique` on its own: `{ name: { type: String, unique: true } }` +is just a shorthand for creating a [MongoDB unique index on `name`](https://docs.mongodb.com/manual/core/index-unique/). +For example, if MongoDB doesn't already have a unique index on `name`, the below code will not error despite the fact that `unique` is true. + +```javascript +const schema = new mongoose.Schema({ + name: { type: String, unique: true } +}); +const Model = db.model('Test', schema); + +Model.create([{ name: 'Val' }, { name: 'Val' }], function(err) { + console.log(err); // No error, unless index was already built +}); +``` + +However, if you wait for the index to build using the `Model.on('index')` event, attempts to save duplicates will correctly error. + +```javascript +const schema = new mongoose.Schema({ + name: { type: String, unique: true } +}); +const Model = db.model('Test', schema); + +Model.on('index', function(err) { // <-- Wait for model's indexes to finish + assert.ifError(err); + Model.create([{ name: 'Val' }, { name: 'Val' }], function(err) { + console.log(err); + }); +}); + +// Promise based alternative. `init()` returns a promise that resolves +// when the indexes have finished building successfully. The `init()` +// function is idempotent, so don't worry about triggering an index rebuild. +Model.init().then(function() { + Model.create([{ name: 'Val' }, { name: 'Val' }], function(err) { + console.log(err); + }); +}); +``` + +MongoDB persists indexes, so you only need to rebuild indexes if you're starting +with a fresh database or you ran `db.dropDatabase()`. In a production environment, +you should [create your indexes using the MongoDB shell](https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/) +rather than relying on mongoose to do it for you. The `unique` option for schemas is +convenient for development and documentation, but mongoose is *not* an index management solution. + +
    + +**Q**. When I have a nested property in a schema, mongoose adds empty objects by default. Why? + +```javascript +const schema = new mongoose.Schema({ + nested: { + prop: String + } +}); +const Model = db.model('Test', schema); + +// The below prints `{ _id: /* ... */, nested: {} }`, mongoose assigns +// `nested` to an empty object `{}` by default. +console.log(new Model()); +``` + +**A**. This is a performance optimization. These empty objects are not saved +to the database, nor are they in the result `toObject()`, nor do they show +up in `JSON.stringify()` output unless you turn off the [`minimize` option](./guide.html#minimize). + +The reason for this behavior is that Mongoose's change detection +and getters/setters are based on [`Object.defineProperty()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty). +In order to support change detection on nested properties without incurring +the overhead of running `Object.defineProperty()` every time a document is created, +mongoose defines properties on the `Model` prototype when the model is compiled. +Because mongoose needs to define getters and setters for `nested.prop`, `nested` +must always be defined as an object on a mongoose document, even if `nested` +is undefined on the underlying [POJO](./guide.html#minimize). + +
    + +**Q**. When I use named imports like `import { set } from 'mongoose'`, I + get a `TypeError`. What causes this issue and how can I fix it? + +**A**. The only import syntax Mongoose supports is `import mongoose from 'mongoose'`. +Syntaxes like `import * from 'mongoose'` or `import { model } from 'mongoose'` do **not** work. +The global Mongoose object stores types, [global options](/docs/api.html#mongoose_Mongoose-set), and other important +properties that Mongoose needs. When you do `import { model } from 'mongoose'`, the +`this` value in `model()` is not the Mongoose global. + +```javascript +// file1.js +exports.answer = 42; +exports.foo = function() { console.log(this.answer); }; + +// file2.js +const obj = require('./file1'); +obj.foo(); // "42" + +// file3.js +const { foo } = require('./file1'); +foo(); // "undefined" +``` + +
    + +**Q**. I'm using an arrow function for a [virtual](./guide.html#virtuals), [middleware](./middleware.html), [getter](./api.html#schematype_SchemaType-get)/[setter](./api.html#schematype_SchemaType-set), or [method](./guide.html#methods) and the value of `this` is wrong. + +**A**. Arrow functions [handle the `this` keyword much differently than conventional functions](https://masteringjs.io/tutorials/fundamentals/arrow#why-not-arrow-functions). +Mongoose getters/setters depend on `this` to give you access to the document that you're writing to, but this functionality does not work with arrow functions. Do **not** use arrow functions for mongoose getters/setters unless do not intend to access the document in the getter/setter. + +```javascript +// Do **NOT** use arrow functions as shown below unless you're certain +// that's what you want. If you're reading this FAQ, odds are you should +// just be using a conventional function. +const schema = new mongoose.Schema({ + propWithGetter: { + type: String, + get: v => { + // Will **not** be the doc, do **not** use arrow functions for getters/setters + console.log(this); + return v; + } + } +}); + +// `this` will **not** be the doc, do **not** use arrow functions for methods +schema.method.arrowMethod = () => this; +schema.virtual('virtualWithArrow').get(() => { + // `this` will **not** be the doc, do **not** use arrow functions for virtuals + console.log(this); +}); +``` + +
    + +**Q**. I have an embedded property named `type` like this: + +```javascript +const holdingSchema = new Schema({ + // You might expect `asset` to be an object that has 2 properties, + // but unfortunately `type` is special in mongoose so mongoose + // interprets this schema to mean that `asset` is a string + asset: { + type: String, + ticker: String + } +}); +``` + +But mongoose gives me a CastError telling me that it can't cast an object +to a string when I try to save a `Holding` with an `asset` object. Why +is this? + +```javascript +Holding.create({ asset: { type: 'stock', ticker: 'MDB' } }).catch(error => { + // Cast to String failed for value "{ type: 'stock', ticker: 'MDB' }" at path "asset" + console.error(error); +}); +``` + +**A**. The `type` property is special in mongoose, so when you say +`type: String`, mongoose interprets it as a type declaration. In the +above schema, mongoose thinks `asset` is a string, not an object. Do +this instead: + +```javascript +const holdingSchema = new Schema({ + // This is how you tell mongoose you mean `asset` is an object with + // a string property `type`, as opposed to telling mongoose that `asset` + // is a string. + asset: { + type: { type: String }, + ticker: String + } +}); +``` + +
    + +**Q**. I'm populating a nested property under an array like the below code: + +```javascript +new Schema({ + arr: [{ + child: { ref: 'OtherModel', type: Schema.Types.ObjectId } + }] +}); +``` + +`.populate({ path: 'arr.child', options: { sort: 'name' } })` won't sort by `arr.child.name`? + +**A**. See [this GitHub issue](https://github.com/Automattic/mongoose/issues/2202). It's a known issue but one that's exceptionally difficult to fix. + +
    + +**Q**. All function calls on my models hang, what am I doing wrong? + +**A**. By default, mongoose will buffer your function calls until it can +connect to MongoDB. Read the [buffering section of the connection docs](./connections.html#buffering) +for more information. + +
    + +**Q**. How can I enable debugging? + +**A**. Set the `debug` option: + +```javascript +// all executed methods log output to console +mongoose.set('debug', true) + +// disable colors in debug mode +mongoose.set('debug', { color: false }) + +// get mongodb-shell friendly output (ISODate) +mongoose.set('debug', { shell: true }) +``` + +For more debugging options (streams, callbacks), see the ['debug' option under `.set()`](./api.html#mongoose_Mongoose-set). + +
    + +**Q**. My `save()` callback never executes. What am I doing wrong? + +**A**. All `collection` actions (insert, remove, queries, etc.) are queued +until Mongoose successfully connects to MongoDB. It is likely you haven't called Mongoose's +`connect()` or `createConnection()` function yet. + +In Mongoose 5.11, there is a `bufferTimeoutMS` option (set to 10000 by default) that configures how long +Mongoose will allow an operation to stay buffered before throwing an error. + +If you want to opt out of Mongoose's buffering mechanism across your entire +application, set the global `bufferCommands` option to false: + +```javascript +mongoose.set('bufferCommands', false); +``` + +Instead of opting out of Mongoose's buffering mechanism, you may want to instead reduce `bufferTimeoutMS` +to make Mongoose only buffer for a short time. + +```javascript +// If an operation is buffered for more than 500ms, throw an error. +mongoose.set('bufferTimeoutMS', 500); +``` + +
    + +**Q**. Should I create/destroy a new connection for each database operation? + +**A**. No. Open your connection when your application starts up and leave +it open until the application shuts down. + +
    + +**Q**. Why do I get "OverwriteModelError: Cannot overwrite .. model once +compiled" when I use nodemon / a testing framework? + +**A**. `mongoose.model('ModelName', schema)` requires 'ModelName' to be +unique, so you can access the model by using `mongoose.model('ModelName')`. +If you put `mongoose.model('ModelName', schema);` in a +[mocha `beforeEach()` hook](https://mochajs.org/#hooks), this code will +attempt to create a new model named 'ModelName' before **every** test, +and so you will get an error. Make sure you only create a new model with +a given name **once**. If you need to create multiple models with the +same name, create a new connection and bind the model to the connection. + +```javascript +const mongoose = require('mongoose'); +const connection = mongoose.createConnection(..); + +// use mongoose.Schema +const kittySchema = mongoose.Schema({ name: String }); + +// use connection.model +const Kitten = connection.model('Kitten', kittySchema); +``` + +
    + +**Q**. How can I change mongoose's default behavior of initializing an array path to an empty array so that I can require real data on document creation? + +**A**. You can set the default of the array to a function that returns `undefined`. + +```javascript +const CollectionSchema = new Schema({ + field1: { + type: [String], + default: void 0 + } +}); +``` + +
    + +**Q**. How can I initialize an array path to `null`? + +**A**. You can set the default of the array to a function that returns `null`. + +```javascript +const CollectionSchema = new Schema({ + field1: { + type: [String], + default: () => { return null; } + } +}); +``` + +
    + +**Q**. Why does my aggregate $match fail to return the document that my find query returns when working with dates? + +**A**. Mongoose does not cast aggregation pipeline stages because with $project, +$group, etc. the type of a property may change during the aggregation. If you want +to query by date using the aggregation framework, you're responsible for ensuring +that you're passing in a valid date. + +
    + +**Q**. Why don't in-place modifications to date objects +(e.g. `date.setMonth(1);`) get saved? + +```javascript +doc.createdAt.setDate(2011, 5, 1); +doc.save(); // createdAt changes won't get saved! +``` + +**A**. Mongoose currently doesn't watch for in-place updates to date +objects. If you have need for this feature, feel free to discuss on +[this GitHub issue](https://github.com/Automattic/mongoose/issues/3738). +There are several workarounds: + +```javascript +doc.createdAt.setDate(2011, 5, 1); +doc.markModified('createdAt'); +doc.save(); // Works + +doc.createdAt = new Date(2011, 5, 1).setHours(4); +doc.save(); // Works +``` + +
    + +**Q**. Why does calling `save()` multiple times on the same document in parallel only let +the first save call succeed and return ParallelSaveErrors for the rest? + +**A**. Due to the asynchronous nature of validation and middleware in general, calling +`save()` multiple times in parallel on the same doc could result in conflicts. For example, +validating, and then subsequently invalidating the same path. + +
    + +**Q**. Why is **any** 12 character string successfully cast to an ObjectId? + +**A**. Technically, any 12 character string is a valid [ObjectId](https://docs.mongodb.com/manual/reference/bson-types/#objectid). +Consider using a regex like `/^[a-f0-9]{24}$/` to test whether a string is exactly 24 hex characters. + +
    + +**Q**. Why do keys in Mongoose Maps have to be strings? + +**A**. Because the Map eventually gets stored in MongoDB where the keys must be strings. + +
    + +**Q**. I am using `Model.find(...).populate(...)` with the `limit` option, but getting fewer results than the limit. What gives? + +**A**. In order to avoid executing a separate query for each document returned from the `find` query, Mongoose +instead queries using (numDocuments * limit) as the limit. If you need the correct limit, you should use the +[perDocumentLimit](/docs/populate.html#limit-vs-perDocumentLimit) option (new in Mongoose 5.9.0). Just keep in +mind that populate() will execute a separate query for each document. + +
    + +**Q**. My query/update seems to execute twice. Why is this happening? + +**A**. The most common cause of duplicate queries is **mixing callbacks and promises with queries**. +That's because passing a callback to a query function, like `find()` or `updateOne()`, +immediately executes the query, and calling [`then()`](https://masteringjs.io/tutorials/fundamentals/then) +executes the query again. + +Mixing promises and callbacks can lead to duplicate entries in arrays. +For example, the below code inserts 2 entries into the `tags` array, **not* just 1. + +```javascript +const BlogPost = mongoose.model('BlogPost', new Schema({ + title: String, + tags: [String] +})); + +// Because there's both `await` **and** a callback, this `updateOne()` executes twice +// and thus pushes the same string into `tags` twice. +const update = { $push: { tags: ['javascript'] } }; +await BlogPost.updateOne({ title: 'Introduction to Promises' }, update, (err, res) => { + console.log(res); +}); +``` + +
    + +**Something to add?** + +If you'd like to contribute to this page, please [visit it](https://github.com/Automattic/mongoose/tree/master/docs/faq.md) on github and use the [Edit](https://github.com/blog/844-forking-with-the-edit-button) button to send a pull request. \ No newline at end of file diff --git a/docs/faq.pug b/docs/faq.pug deleted file mode 100644 index 54a6636b21d..00000000000 --- a/docs/faq.pug +++ /dev/null @@ -1,501 +0,0 @@ -extends layout - -append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block append style - style. - hr { - display: block; - margin-top: 40px; - margin-bottom: 40px; - border: 0px; - height: 1px; - background-color: #232323; - width: 100%; - } - -block content - - - - - :markdown - ## FAQ - - - - - - -
    - - **Q**. Why don't my changes to arrays get saved when I update an element - directly? - - ```javascript - doc.array[3] = 'changed'; - doc.save(); - ``` - - **A**. Mongoose doesn't create getters/setters for array indexes; without - them mongoose never gets notified of the change and so doesn't know to - persist the new value. There are two workarounds: - [`MongooseArray#set`](./api.html#types_array_MongooseArray.set) or - [`Document#markModified()`](/docs/api/document.html#document_Document-markModified). - - ```javascript - // Saves changes successfully - doc.array.set(3, 'changed'); - doc.save(); - - // Also saves changes successfully - doc.array[3] = 'changed'; - doc.markModified('array'); - doc.save(); - ``` - - This **only** affects when you set an array index directly. If you set - a path on a document array element, you do not need to use `markModified()`. - - ```javascript - // Saves changes successfully without `markModified()`, because this - // code doesn't set an array index, it sets a path underneath an array index. - doc.docArray[3].name = 'changed'; - doc.save(); - - // Does **not** save changes successfully. You need to use `markModified()` - // or `set()` because this sets an array index. - doc.docArray[3] = { name: 'changed' }; - doc.save(); - ``` - - -
    - - **Q**. I declared a schema property as `unique` but I can still save - duplicates. What gives? - - **A**. Mongoose doesn't handle `unique` on its own: `{ name: { type: String, unique: true } }` - is just a shorthand for creating a [MongoDB unique index on `name`](https://docs.mongodb.com/manual/core/index-unique/). - For example, if MongoDB doesn't already have a unique index on `name`, the below code will not error despite the fact that `unique` is true. - - ```javascript - const schema = new mongoose.Schema({ - name: { type: String, unique: true } - }); - const Model = db.model('Test', schema); - - Model.create([{ name: 'Val' }, { name: 'Val' }], function(err) { - console.log(err); // No error, unless index was already built - }); - ``` - - However, if you wait for the index to build using the `Model.on('index')` event, attempts to save duplicates will correctly error. - - ```javascript - const schema = new mongoose.Schema({ - name: { type: String, unique: true } - }); - const Model = db.model('Test', schema); - - Model.on('index', function(err) { // <-- Wait for model's indexes to finish - assert.ifError(err); - Model.create([{ name: 'Val' }, { name: 'Val' }], function(err) { - console.log(err); - }); - }); - - // Promise based alternative. `init()` returns a promise that resolves - // when the indexes have finished building successfully. The `init()` - // function is idempotent, so don't worry about triggering an index rebuild. - Model.init().then(function() { - Model.create([{ name: 'Val' }, { name: 'Val' }], function(err) { - console.log(err); - }); - }); - ``` - - MongoDB persists indexes, so you only need to rebuild indexes if you're starting - with a fresh database or you ran `db.dropDatabase()`. In a production environment, - you should [create your indexes using the MongoDB shell](https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/) - rather than relying on mongoose to do it for you. The `unique` option for schemas is - convenient for development and documentation, but mongoose is *not* an index management solution. - - -
    - - **Q**. When I have a nested property in a schema, mongoose adds empty objects by default. Why? - - ```javascript - const schema = new mongoose.Schema({ - nested: { - prop: String - } - }); - const Model = db.model('Test', schema); - - // The below prints `{ _id: /* ... */, nested: {} }`, mongoose assigns - // `nested` to an empty object `{}` by default. - console.log(new Model()); - ``` - - **A**. This is a performance optimization. These empty objects are not saved - to the database, nor are they in the result `toObject()`, nor do they show - up in `JSON.stringify()` output unless you turn off the [`minimize` option](./guide.html#minimize). - - The reason for this behavior is that Mongoose's change detection - and getters/setters are based on [`Object.defineProperty()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty). - In order to support change detection on nested properties without incurring - the overhead of running `Object.defineProperty()` every time a document is created, - mongoose defines properties on the `Model` prototype when the model is compiled. - Because mongoose needs to define getters and setters for `nested.prop`, `nested` - must always be defined as an object on a mongoose document, even if `nested` - is undefined on the underlying [POJO](./guide.html#minimize). - - -
    - - **Q**. When I use named imports like `import { set } from 'mongoose'`, I - get a `TypeError`. What causes this issue and how can I fix it? - - **A**. The only import syntax Mongoose supports is `import mongoose from 'mongoose'`. - Syntaxes like `import * from 'mongoose'` or `import { model } from 'mongoose'` do **not** work. - The global Mongoose object stores types, [global options](/docs/api.html#mongoose_Mongoose-set), and other important - properties that Mongoose needs. When you do `import { model } from 'mongoose'`, the - `this` value in `model()` is not the Mongoose global. - - ```javascript - // file1.js - exports.answer = 42; - exports.foo = function() { console.log(this.answer); }; - - // file2.js - const obj = require('./file1'); - obj.foo(); // "42" - - // file3.js - const { foo } = require('./file1'); - foo(); // "undefined" - ``` - - -
    - - **Q**. I'm using an arrow function for a [virtual](./guide.html#virtuals), [middleware](./middleware.html), [getter](./api.html#schematype_SchemaType-get)/[setter](./api.html#schematype_SchemaType-set), or [method](./guide.html#methods) and the value of `this` is wrong. - - **A**. Arrow functions [handle the `this` keyword much differently than conventional functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions#No_binding_of_this). - Mongoose getters/setters depend on `this` to give you access to the document that you're writing to, but this functionality does not work with arrow functions. Do **not** use arrow functions for mongoose getters/setters unless do not intend to access the document in the getter/setter. - - ```javascript - // Do **NOT** use arrow functions as shown below unless you're certain - // that's what you want. If you're reading this FAQ, odds are you should - // just be using a conventional function. - const schema = new mongoose.Schema({ - propWithGetter: { - type: String, - get: v => { - // Will **not** be the doc, do **not** use arrow functions for getters/setters - console.log(this); - return v; - } - } - }); - - // `this` will **not** be the doc, do **not** use arrow functions for methods - schema.method.arrowMethod = () => this; - schema.virtual('virtualWithArrow').get(() => { - // `this` will **not** be the doc, do **not** use arrow functions for virtuals - console.log(this); - }); - ``` - - -
    - - **Q**. I have an embedded property named `type` like this: - - ```javascript - const holdingSchema = new Schema({ - // You might expect `asset` to be an object that has 2 properties, - // but unfortunately `type` is special in mongoose so mongoose - // interprets this schema to mean that `asset` is a string - asset: { - type: String, - ticker: String - } - }); - ``` - - But mongoose gives me a CastError telling me that it can't cast an object - to a string when I try to save a `Holding` with an `asset` object. Why - is this? - - ```javascript - Holding.create({ asset: { type: 'stock', ticker: 'MDB' } }).catch(error => { - // Cast to String failed for value "{ type: 'stock', ticker: 'MDB' }" at path "asset" - console.error(error); - }); - ``` - - **A**. The `type` property is special in mongoose, so when you say - `type: String`, mongoose interprets it as a type declaration. In the - above schema, mongoose thinks `asset` is a string, not an object. Do - this instead: - - ```javascript - const holdingSchema = new Schema({ - // This is how you tell mongoose you mean `asset` is an object with - // a string property `type`, as opposed to telling mongoose that `asset` - // is a string. - asset: { - type: { type: String }, - ticker: String - } - }); - ``` - - -
    - - **Q**. I'm populating a nested property under an array like the below code: - - ```javascript - new Schema({ - arr: [{ - child: { ref: 'OtherModel', type: Schema.Types.ObjectId } - }] - }); - ``` - - `.populate({ path: 'arr.child', options: { sort: 'name' } })` won't sort - by `arr.child.name`? - - **A**. See [this GitHub issue](https://github.com/Automattic/mongoose/issues/2202). It's a known issue but one that's exceptionally difficult to fix. - -
    - - **Q**. All function calls on my models hang, what am I doing wrong? - - **A**. By default, mongoose will buffer your function calls until it can - connect to MongoDB. Read the [buffering section of the connection docs](./connections.html#buffering) - for more information. - - -
    - - **Q**. How can I enable debugging? - - **A**. Set the `debug` option to `true`: - - ```javascript - mongoose.set('debug', true) - - // disable colors in debug mode - mongoose.set('debug', { color: false }) - - // get mongodb-shell friendly output (ISODate) - mongoose.set('debug', { shell: true }) - ``` - - All executed collection methods will log output of their arguments to your - console. - - -
    - - **Q**. My `save()` callback never executes. What am I doing wrong? - - **A**. All `collection` actions (insert, remove, queries, etc.) are queued - until Mongoose successfully connects to MongoDB. It is likely you haven't called Mongoose's - `connect()` or `createConnection()` function yet. - - In Mongoose 5.11, there is a `bufferTimeoutMS` option (set to 10000 by default) that configures how long - Mongoose will allow an operation to stay buffered before throwing an error. - - If you want to opt out of Mongoose's buffering mechanism across your entire - application, set the global `bufferCommands` option to false: - - ```javascript - mongoose.set('bufferCommands', false); - ``` - - Instead of opting out of Mongoose's buffering mechanism, you may want to instead reduce `bufferTimeoutMS` - to make Mongoose only buffer for a short time. - - ```javascript - // If an operation is buffered for more than 500ms, throw an error. - mongoose.set('bufferTimeoutMS', 500); - ``` - -
    - - **Q**. Should I create/destroy a new connection for each database operation? - - **A**. No. Open your connection when your application starts up and leave - it open until the application shuts down. - - -
    - - **Q**. Why do I get "OverwriteModelError: Cannot overwrite .. model once - compiled" when I use nodemon / a testing framework? - - **A**. `mongoose.model('ModelName', schema)` requires 'ModelName' to be - unique, so you can access the model by using `mongoose.model('ModelName')`. - If you put `mongoose.model('ModelName', schema);` in a - [mocha `beforeEach()` hook](https://mochajs.org/#hooks), this code will - attempt to create a new model named 'ModelName' before **every** test, - and so you will get an error. Make sure you only create a new model with - a given name **once**. If you need to create multiple models with the - same name, create a new connection and bind the model to the connection. - - ```javascript - const mongoose = require('mongoose'); - const connection = mongoose.createConnection(..); - - // use mongoose.Schema - const kittySchema = mongoose.Schema({ name: String }); - - // use connection.model - const Kitten = connection.model('Kitten', kittySchema); - ``` - - -
    - - **Q**. How can I change mongoose's default behavior of initializing an array - path to an empty array so that I can require real data on document creation? - - **A**. You can set the default of the array to a function that returns `undefined`. - ```javascript - const CollectionSchema = new Schema({ - field1: { - type: [String], - default: void 0 - } - }); - ``` - - -
    - - **Q**. How can I initialize an array path to `null`? - - **A**. You can set the default of the array to a function that returns `null`. - ```javascript - const CollectionSchema = new Schema({ - field1: { - type: [String], - default: () => { return null; } - } - }); - ``` - -
    - - **Q**. Why does my aggregate $match fail to return the document that my find query - returns when working with dates? - - **A**. Mongoose does not cast aggregation pipeline stages because with $project, - $group, etc. the type of a property may change during the aggregation. If you want - to query by date using the aggregation framework, you're responsible for ensuring - that you're passing in a valid date. - - -
    - - **Q**. Why don't in-place modifications to date objects - (e.g. `date.setMonth(1);`) get saved? - - ```javascript - doc.createdAt.setDate(2011, 5, 1); - doc.save(); // createdAt changes won't get saved! - ``` - - **A**. Mongoose currently doesn't watch for in-place updates to date - objects. If you have need for this feature, feel free to discuss on - [this GitHub issue](https://github.com/Automattic/mongoose/issues/3738). - There are several workarounds: - - ```javascript - doc.createdAt.setDate(2011, 5, 1); - doc.markModified('createdAt'); - doc.save(); // Works - - doc.createdAt = new Date(2011, 5, 1).setHours(4); - doc.save(); // Works - ``` - -
    - - **Q**. Why does calling `save()` multiple times on the same document in parallel only let - the first save call succeed and return ParallelSaveErrors for the rest? - - **A**. Due to the asynchronous nature of validation and middleware in general, calling - `save()` multiple times in parallel on the same doc could result in conflicts. For example, - validating, and then subsequently invalidating the same path. - -
    - - **Q**. Why is **any** 12 character string successfully cast to an ObjectId? - - **A**. Technically, any 12 character string is a valid [ObjectId](https://docs.mongodb.com/manual/reference/bson-types/#objectid). - Consider using a regex like `/^[a-f0-9]{24}$/` to test whether a string is exactly 24 hex characters. - - -
    - - **Q**. Why do keys in Mongoose Maps have to be strings? - - **A**. Because the Map eventually gets stored in MongoDB where the keys must be strings. - -
    - - **Q**. I am using `Model.find(...).populate(...)` with the `limit` option, but getting fewer results than the limit. What gives? - - **A**. In order to avoid executing a separate query for each document returned from the `find` query, Mongoose - instead queries using (numDocuments * limit) as the limit. If you need the correct limit, you should use the - [perDocumentLimit](/docs/populate.html#limit-vs-perDocumentLimit) option (new in Mongoose 5.9.0). Just keep in - mind that populate() will execute a separate query for each document. - -
    - - **Q**. My query/update seems to execute twice. Why is this happening? - - **A**. The most common cause of duplicate queries is **mixing callbacks and promises with queries**. - That's because passing a callback to a query function, like `find()` or `updateOne()`, - immediately executes the query, and calling [`then()`](https://masteringjs.io/tutorials/fundamentals/then) - executes the query again. - - Mixing promises and callbacks can lead to duplicate entries in arrays. - For example, the below code inserts 2 entries into the `tags` array, **not* just 1. - - ```javascript - const BlogPost = mongoose.model('BlogPost', new Schema({ - title: String, - tags: [String] - })); - - // Because there's both `await` **and** a callback, this `updateOne()` executes twice - // and thus pushes the same string into `tags` twice. - const update = { $push: { tags: ['javascript'] } }; - await BlogPost.updateOne({ title: 'Introduction to Promises' }, update, (err, res) => { - console.log(res); - }); - ``` - -
    - - **Something to add?** - - If you'd like to contribute to this page, please - [visit it](https://github.com/Automattic/mongoose/tree/master/docs/faq.pug) - on github and use the [Edit](https://github.com/blog/844-forking-with-the-edit-button) button to send a pull request. diff --git a/docs/further_reading.html b/docs/further_reading.html deleted file mode 100644 index c252233964b..00000000000 --- a/docs/further_reading.html +++ /dev/null @@ -1,248 +0,0 @@ -Mongoose v5.6.0: Further Reading

    Further Reading

    - - - - -

    There's a lot of great content out there to learn more about Mongoose. -This page has a list of video courses, books, and blog posts curated by -Mongoose maintainers. We've gone through every course, book, and article -on this page to make sure it is high quality.

    - -

    Video Courses

    - - - - - - - API Design in Node.js Featuring Express & Mongo - - -

    This course is a great introduction to building a RESTful API with Express -and Mongoose. The instructor is Scott Moss, -a serial entrepreneur and former intructor at Hack Reactor, San Francisco's number one coding bootcamp.

    -
    -

    - - - - - - - Building a Production Ready Node.js JSON API - - -



    -

    Thinkster has created a full stack web -development course for just about every backend and frontend framework -you can think of. Want to learn how to build a full stack app with Vue -and Django, or with Angular and Rails? Thinkster has a course for that. -The Node.js tutorial walks you through building a production-ready -RESTful API with Express and Mongoose from scratch.

    -
    -

    - - - - - - - - Introduction to Mongoose for Node.js and MongoDB - - -



    -

    Know someone who wants to get started building Node.js apps but doesn't have -a lot of software development experience? -This course is more beginner-friendly than API Design and is a great -resource for beginners looking to get started.

    -
    -

    - - - - - - - - RESTful Web Services with Node.js and Express - - -



    -

    Looking for a course to take you from zero to Express + Mongoose -REST API fast? This is the one. This course is halfway -between API Design and Introduction to Mongoose. It focuses more on -Express, but also doesn't go into as much detail as API Design.

    -
    -

    - - - - - - - - Building Business Applications with Vue.js and MongoDB - - -



    -

    This course walks you through building a full-stack web application using -the VENoM Stack. -Try this course if you're a Vue.js expert looking to -expand into backend engineering or an experienced MongoDB dev looking to -learn about frontend dev.

    -
    -

    - - - - - - - - Moving Forward with Mongoose.js - - -



    -

    Stuck maintaining a legacy codebase on Mongoose 3.x? This course will -give you a detailed overview of the breaking changes and new features in Mongoose 4.x, -so you can upgrade with confidence.

    -
    - -

    Books

    - - - - - - Mongoose for Application Development by Simon Holmes - - -

    This is the book on Mongoose. Even though it is from 2013, -Mongoose for Application Development has stood the test of time. This -book does an excellent job summarizing the core ideas that make Mongoose so -powerful. Just sub out callbacks for promises.

    -
    - - - - - - - The Little MongoDB Schema Design Book by Christian Kvalheim - - -

    Christian Kvalheim originally wrote the MongoDB Node.js driver in early 2010, -and maintained the driver almost singlehandedly until 2017. As maintainer -of the Node.js driver, Christian saw more Node.js+MongoDB code bases than -anyone, and his experience is unmatched. -This book distills 6 years of hard-learned lessons into concrete examples -of how to design MongoDB schemas for massive scale. If you want to learn -how to structure MongoDB schemas for an ecommerce store, a category tree, or a -blog that can support hundreds of millions of requests per day, this is -the book for you.

    -
    - -

    Blog Posts

    - - - - - - Introduction to Mongoose for MongoDB on freeCodeCamp - - -

    Coming from an SQL background and having trouble understanding Mongoose and MongoDB? -This article explains the core concepts of Mongoose with references to similar -concepts in SQL.

    -
    - - - - - - - Express Tutorial Part 3: Using a Database (with Mongoose) on Mozilla Developer Network - - -

    Virtually every JavaScript developer has read MDN docs. They're usually one -of the top 3 results when you Google "javascript startswith" or any other -core JavaScript function. This tutorial provides an overview of Mongoose -in MDN's standard style and tone.

    -
    \ No newline at end of file diff --git a/docs/further_reading.md b/docs/further_reading.md new file mode 100644 index 00000000000..fd8b4a13001 --- /dev/null +++ b/docs/further_reading.md @@ -0,0 +1,208 @@ +## Further Reading + + + +There's a lot of great content out there to learn more about Mongoose. +This page has a list of video courses, books, and blog posts curated by +Mongoose maintainers. We've gone through every course, book, and article +on this page to make sure it is high quality. + +* [Video Courses](#video-courses) +* [Books](#books) +* [Blog Posts](#blog-posts) + +## Video Courses + + + + + + + + API Design in Node.js Featuring Express & Mongo + + +This course is a great introduction to building a RESTful API with [Express](http://expressjs.com/) +and Mongoose. The instructor is [Scott Moss](https://frontendmasters.com/teachers/scott-moss/), +a serial entrepreneur and former intructor at [Hack Reactor](https://www.hackreactor.com/), San Francisco's number one coding bootcamp. + +
    +

    + + + + + + + Building a Production Ready Node.js JSON API + + +

    + +[Thinkster](https://thinkster.io/) has created a full stack web +development course for just about every backend and frontend framework +you can think of. Want to learn how to build a full stack app with Vue +and Django, or with Angular and Rails? Thinkster has a course for that. +The Node.js tutorial walks you through building a production-ready +RESTful API with Express and Mongoose from scratch. + +
    +

    + + + + + + + + Introduction to Mongoose for Node.js and MongoDB + + +

    + +Know someone who wants to get started building Node.js apps but doesn't have +a lot of software development experience? +This course is more beginner-friendly than _API Design_ and is a great +resource for beginners looking to get started. + +
    +

    + + + + + + + + RESTful Web Services with Node.js and Express + + +

    + +Looking for a course to take you from zero to Express + Mongoose +REST API fast? This is the one. This course is halfway +between _API Design_ and _Introduction to Mongoose_. It focuses more on +[Express](http://expressjs.com/), but also doesn't go into as much detail as _API Design_. + +
    +

    + + + + + + + + Building Business Applications with Vue.js and MongoDB + + +

    + +This course walks you through building a full-stack web application using +the [VENoM Stack](https://medium.com/@audretschjames/venom-stack-docker-setup-for-local-development-457093761ad1). +Try this course if you're a [Vue.js](https://vuejs.org/) expert looking to +expand into backend engineering or an experienced MongoDB dev looking to +learn about frontend dev. + +
    +

    + + + + + + + + Moving Forward with Mongoose.js + + +

    + +Stuck maintaining a legacy codebase on Mongoose 3.x? This course will +give you a detailed overview of the [breaking changes and new features in Mongoose 4.x](https://github.com/Automattic/mongoose/wiki/4.0-Release-Notes), +so you can upgrade with confidence. + +
    + +## Books + + + + + + + Mongoose for Application Development by Simon Holmes + + +This is the book on Mongoose. Even though it is from 2013, +_Mongoose for Application Development_ has stood the test of time. This +book does an excellent job summarizing the core ideas that make Mongoose so +powerful. Just sub out callbacks for promises. + +
    + + + + + + + The Little MongoDB Schema Design Book by Christian Kvalheim + + +Christian Kvalheim originally wrote the MongoDB Node.js driver in early 2010, +and maintained the driver almost singlehandedly until 2017. As maintainer +of the Node.js driver, Christian saw more Node.js+MongoDB code bases than +anyone, and his experience is unmatched. +This book distills 6 years of hard-learned lessons into concrete examples +of how to design MongoDB schemas for massive scale. If you want to learn +how to structure MongoDB schemas for an ecommerce store, a category tree, or a +blog that can support hundreds of millions of requests per day, this is +the book for you. + +
    + +## Blog Posts + + + + + + + Introduction to Mongoose for MongoDB on freeCodeCamp + + +Coming from an SQL background and having trouble understanding Mongoose and MongoDB? +This article explains the core concepts of Mongoose with references to similar +concepts in SQL. + +
    + + + + + + + Express Tutorial Part 3: Using a Database (with Mongoose) on Mozilla Developer Network + + +Virtually every JavaScript developer has read MDN docs. They're usually one +of the top 3 results when you Google "javascript startswith" or any other +core JavaScript function. This tutorial provides an overview of Mongoose +in MDN's standard style and tone. + +
    \ No newline at end of file diff --git a/docs/further_reading.pug b/docs/further_reading.pug deleted file mode 100644 index 17165a16761..00000000000 --- a/docs/further_reading.pug +++ /dev/null @@ -1,229 +0,0 @@ -extends layout - -append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - - style. - img { width: auto; } - - .pluralsight-link { - float: left; - margin-right: 0.5em; - } - - .pluralsight-title { - font-size: 1.5em; - } - - p { - line-height: 1.5em; - } - -block content - - - - - :markdown - ## Further Reading - - - - - - There's a lot of great content out there to learn more about Mongoose. - This page has a list of video courses, books, and blog posts curated by - Mongoose maintainers. We've gone through every course, book, and article - on this page to make sure it is high quality. - - * [Video Courses](#video-courses) - * [Books](#books) - * [Blog Posts](#blog-posts) - - ## Video Courses - - - - - - - - API Design in Node.js Featuring Express & Mongo - - - This course is a great introduction to building a RESTful API with [Express](http://expressjs.com/) - and Mongoose. The instructor is [Scott Moss](https://frontendmasters.com/teachers/scott-moss/), - a serial entrepreneur and former intructor at [Hack Reactor](https://www.hackreactor.com/), San Francisco's number one coding bootcamp. - -
    -

    - - - - - - - Building a Production Ready Node.js JSON API - - -

    - - [Thinkster](https://thinkster.io/) has created a full stack web - development course for just about every backend and frontend framework - you can think of. Want to learn how to build a full stack app with Vue - and Django, or with Angular and Rails? Thinkster has a course for that. - The Node.js tutorial walks you through building a production-ready - RESTful API with Express and Mongoose from scratch. - -
    -

    - - - - - - - - Introduction to Mongoose for Node.js and MongoDB - - -

    - - Know someone who wants to get started building Node.js apps but doesn't have - a lot of software development experience? - This course is more beginner-friendly than _API Design_ and is a great - resource for beginners looking to get started. - -
    -

    - - - - - - - - RESTful Web Services with Node.js and Express - - -

    - - Looking for a course to take you from zero to Express + Mongoose - REST API fast? This is the one. This course is halfway - between _API Design_ and _Introduction to Mongoose_. It focuses more on - [Express](http://expressjs.com/), but also doesn't go into as much detail as _API Design_. - -
    -

    - - - - - - - - Building Business Applications with Vue.js and MongoDB - - -

    - - This course walks you through building a full-stack web application using - the [VENoM Stack](https://medium.com/@audretschjames/venom-stack-docker-setup-for-local-development-457093761ad1). - Try this course if you're a [Vue.js](https://vuejs.org/) expert looking to - expand into backend engineering or an experienced MongoDB dev looking to - learn about frontend dev. - -
    -

    - - - - - - - - Moving Forward with Mongoose.js - - -

    - - Stuck maintaining a legacy codebase on Mongoose 3.x? This course will - give you a detailed overview of the [breaking changes and new features in Mongoose 4.x](https://github.com/Automattic/mongoose/wiki/4.0-Release-Notes), - so you can upgrade with confidence. - -
    - - ## Books - - - - - - - Mongoose for Application Development by Simon Holmes - - - This is the book on Mongoose. Even though it is from 2013, - _Mongoose for Application Development_ has stood the test of time. This - book does an excellent job summarizing the core ideas that make Mongoose so - powerful. Just sub out callbacks for promises. - -
    - - - - - - - The Little MongoDB Schema Design Book by Christian Kvalheim - - - Christian Kvalheim originally wrote the MongoDB Node.js driver in early 2010, - and maintained the driver almost singlehandedly until 2017. As maintainer - of the Node.js driver, Christian saw more Node.js+MongoDB code bases than - anyone, and his experience is unmatched. - This book distills 6 years of hard-learned lessons into concrete examples - of how to design MongoDB schemas for massive scale. If you want to learn - how to structure MongoDB schemas for an ecommerce store, a category tree, or a - blog that can support hundreds of millions of requests per day, this is - the book for you. - -
    - - ## Blog Posts - - - - - - - Introduction to Mongoose for MongoDB on freeCodeCamp - - - Coming from an SQL background and having trouble understanding Mongoose and MongoDB? - This article explains the core concepts of Mongoose with references to similar - concepts in SQL. - -
    - - - - - - - Express Tutorial Part 3: Using a Database (with Mongoose) on Mozilla Developer Network - - - Virtually every JavaScript developer has read MDN docs. They're usually one - of the top 3 results when you Google "javascript startswith" or any other - core JavaScript function. This tutorial provides an overview of Mongoose - in MDN's standard style and tone. - -
    \ No newline at end of file diff --git a/docs/geojson.html b/docs/geojson.html deleted file mode 100644 index 98835c183b2..00000000000 --- a/docs/geojson.html +++ /dev/null @@ -1,188 +0,0 @@ -Mongoose v5.6.0: Using GeoJSON

    Using GeoJSON

    - - - - -

    GeoJSON is a format for storing geographic points and -polygons. MongoDB has excellent support for geospatial queries -on GeoJSON objects. Let's take a look at how you can use Mongoose to store -and query GeoJSON objects.

    -

    Point Schema

    - -

    The most simple structure in GeoJSON is a point. Below is an example point -representing the approximate location of San Francisco. -Note that longitude comes first in a GeoJSON coordinate array, not latitude.

    -
    {
    -  "type" : "Point",
    -  "coordinates" : [
    -    -122.5,
    -    37.7
    -  ]
    -}

    Below is an example of a Mongoose schema where location is a point.

    -
    const citySchema = new mongoose.Schema({
    -  name: String,
    -  location: {
    -    type: {
    -      type: String, // Don't do `{ location: { type: String } }`
    -      enum: ['Point'], // 'location.type' must be 'Point'
    -      required: true
    -    },
    -    coordinates: {
    -      type: [Number],
    -      required: true
    -    }
    -  }
    -});
    -

    Using subdocuments, you can define a common pointSchema and reuse it everywhere you want to store a GeoJSON point.

    -
    const pointSchema = new mongoose.Schema({
    -  type: {
    -    type: String,
    -    enum: ['Point'],
    -    required: true
    -  },
    -  coordinates: {
    -    type: [Number],
    -    required: true
    -  }
    -});
    -
    -const citySchema = new mongoose.Schema({
    -  name: String,
    -  location: {
    -    type: pointSchema,
    -    required: true
    -  }
    -});
    -

    Polygon Schema

    - -

    GeoJSON polygons let you define an arbitrary shape on a map. For example, -the below polygon is a GeoJSON rectangle that approximates the border -of the state of Colorado.

    -
    {
    -  "type": "Polygon",
    -  "coordinates": [[
    -    [-109, 41],
    -    [-102, 41],
    -    [-102, 37],
    -    [-109, 37],
    -    [-109, 41]
    -  ]]
    -}

    Polygons are tricky because they use triple nested arrays. Below is -how you create a Mongoose schema where coordinates is a triple nested -array of numbers.

    -
    const polygonSchema = new mongoose.Schema({
    -  type: {
    -    type: String,
    -    enum: ['Polygon'],
    -    required: true
    -  },
    -  coordinates: {
    -    type: [[[Number]]], // Array of arrays of arrays of numbers
    -    required: true
    -  }
    -});
    -
    -const citySchema = new mongoose.Schema({
    -  name: String,
    -  location: polygonSchema
    -});
    -

    Geospatial Queries with Mongoose

    - -

    Mongoose queries support the same geospatial query operators -that the MongoDB driver does. For example, the below script saves a -city document those location property is a GeoJSON point representing -the city of Denver, Colorado. It then queries for all documents within -a polygon representing the state of Colorado using -the MongoDB $geoWithin operator.

    - - -
    const City = db.model('City', new Schema({
    -  name: String,
    -  location: pointSchema
    -}));
    -
    -const colorado = {
    -  type: 'Polygon',
    -  coordinates: [[
    -    [-109, 41],
    -    [-102, 41],
    -    [-102, 37],
    -    [-109, 37],
    -    [-109, 41]
    -  ]]
    -};
    -const denver = { type: 'Point', coordinates: [-104.9903, 39.7392] };
    -return City.create({ name: 'Denver', location: denver }).
    -  then(() => City.findOne({
    -    location: {
    -      $geoWithin: {
    -        $geometry: colorado
    -      }
    -    }
    -  })).
    -  then(doc => assert.equal(doc.name, 'Denver'));
    -

    Mongoose also has a within() helper -that's a shorthand for $geoWithin.

    -
    const denver = { type: 'Point', coordinates: [-104.9903, 39.7392] };
    -return City.create({ name: 'Denver', location: denver }).
    -  then(() => City.findOne().where('location').within(colorado)).
    -  then(doc => assert.equal(doc.name, 'Denver'));
    -
    \ No newline at end of file diff --git a/docs/geojson.md b/docs/geojson.md new file mode 100644 index 00000000000..ac2a726cb7f --- /dev/null +++ b/docs/geojson.md @@ -0,0 +1,160 @@ +# Using GeoJSON + + + + + +[GeoJSON](http://geojson.org/) is a format for storing geographic points and +polygons. [MongoDB has excellent support for geospatial queries](http://thecodebarbarian.com/80-20-guide-to-mongodb-geospatial-queries) +on GeoJSON objects. Let's take a look at how you can use Mongoose to store +and query GeoJSON objects. + +

    Point Schema

    + +The most simple structure in GeoJSON is a point. Below is an example point +representing the approximate location of [San Francisco](https://www.google.com/maps/@37.7,-122.5,9z). +Note that longitude comes first in a GeoJSON coordinate array, **not** latitude. + +``` +{ + "type" : "Point", + "coordinates" : [ + -122.5, + 37.7 + ] +} +``` + +Below is an example of a Mongoose schema where `location` is a point. + +```javascript +const citySchema = new mongoose.Schema({ + name: String, + location: { + type: { + type: String, // Don't do `{ location: { type: String } }` + enum: ['Point'], // 'location.type' must be 'Point' + required: true + }, + coordinates: { + type: [Number], + required: true + } + } +}); +``` + +Using [subdocuments](/docs/subdocs.html), you can define a common `pointSchema` and reuse it everywhere you want to store a GeoJSON point. + +```javascript +const pointSchema = new mongoose.Schema({ + type: { + type: String, + enum: ['Point'], + required: true + }, + coordinates: { + type: [Number], + required: true + } +}); + +const citySchema = new mongoose.Schema({ + name: String, + location: { + type: pointSchema, + required: true + } +}); +``` + +

    Polygon Schema

    + +GeoJSON polygons let you define an arbitrary shape on a map. For example, +the below polygon is a GeoJSON rectangle that approximates the border +of the state of Colorado. + +``` +{ + "type": "Polygon", + "coordinates": [[ + [-109, 41], + [-102, 41], + [-102, 37], + [-109, 37], + [-109, 41] + ]] +} +``` + +Polygons are tricky because they use triple nested arrays. Below is +how you create a Mongoose schema where `coordinates` is a triple nested +array of numbers. + +```javascript +const polygonSchema = new mongoose.Schema({ + type: { + type: String, + enum: ['Polygon'], + required: true + }, + coordinates: { + type: [[[Number]]], // Array of arrays of arrays of numbers + required: true + } +}); + +const citySchema = new mongoose.Schema({ + name: String, + location: polygonSchema +}); +``` + +

    Geospatial Queries with Mongoose

    + +Mongoose queries support the same [geospatial query operators](http://thecodebarbarian.com/80-20-guide-to-mongodb-geospatial-queries) +that the MongoDB driver does. For example, the below script saves a +`city` document those `location` property is a GeoJSON point representing +the city of Denver, Colorado. It then queries for all documents within +a polygon representing the state of Colorado using +[the MongoDB `$geoWithin` operator](https://docs.mongodb.com/manual/reference/operator/query/geoWithin/). + + + +```javascript +[require:geojson.*driver query] +``` + +Mongoose also has a [`within()` helper](/docs/api.html#query_Query-within) +that's a shorthand for `$geoWithin`. + +```javascript +[require:geojson.*within helper] +``` + +

    Geospatial Indexes

    + +MongoDB supports [2dsphere indexes](https://docs.mongodb.com/manual/core/2dsphere/) +for speeding up geospatial queries. Here's how you can define +a 2dsphere index on a GeoJSON point: + +```javascript +[require:geojson.*index$] +``` + +You can also define a geospatial index using the [`Schema#index()` function](/docs/api/schema.html#schema_Schema-index) +as shown below. + +```javascript +citySchema.index({ location: '2dsphere' }); +``` + +MongoDB's [`$near` query operator](https://docs.mongodb.com/v4.0/reference/operator/query/near/#op._S_near) +and [`$geoNear` aggregation stage](https://docs.mongodb.com/manual/reference/operator/aggregation/geoNear/#pipe._S_geoNear) +_require_ a 2dsphere index. \ No newline at end of file diff --git a/docs/geojson.pug b/docs/geojson.pug deleted file mode 100644 index 0873c3f41e0..00000000000 --- a/docs/geojson.pug +++ /dev/null @@ -1,172 +0,0 @@ -extends layout - -block append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown - # Using GeoJSON - - - - - - [GeoJSON](http://geojson.org/) is a format for storing geographic points and - polygons. [MongoDB has excellent support for geospatial queries](http://thecodebarbarian.com/80-20-guide-to-mongodb-geospatial-queries) - on GeoJSON objects. Let's take a look at how you can use Mongoose to store - and query GeoJSON objects. - -

    Point Schema

    - - The most simple structure in GeoJSON is a point. Below is an example point - representing the approximate location of [San Francisco](https://www.google.com/maps/@37.7,-122.5,9z). - Note that longitude comes first in a GeoJSON coordinate array, **not** latitude. - - ``` - { - "type" : "Point", - "coordinates" : [ - -122.5, - 37.7 - ] - } - ``` - - Below is an example of a Mongoose schema where `location` is a point. - - ```javascript - const citySchema = new mongoose.Schema({ - name: String, - location: { - type: { - type: String, // Don't do `{ location: { type: String } }` - enum: ['Point'], // 'location.type' must be 'Point' - required: true - }, - coordinates: { - type: [Number], - required: true - } - } - }); - ``` - - Using [subdocuments](/docs/subdocs.html), you can define a common `pointSchema` and reuse it everywhere you want to store a GeoJSON point. - - ```javascript - const pointSchema = new mongoose.Schema({ - type: { - type: String, - enum: ['Point'], - required: true - }, - coordinates: { - type: [Number], - required: true - } - }); - - const citySchema = new mongoose.Schema({ - name: String, - location: { - type: pointSchema, - required: true - } - }); - ``` - -

    Polygon Schema

    - - GeoJSON polygons let you define an arbitrary shape on a map. For example, - the below polygon is a GeoJSON rectangle that approximates the border - of the state of Colorado. - - ``` - { - "type": "Polygon", - "coordinates": [[ - [-109, 41], - [-102, 41], - [-102, 37], - [-109, 37], - [-109, 41] - ]] - } - ``` - - Polygons are tricky because they use triple nested arrays. Below is - how you create a Mongoose schema where `coordinates` is a triple nested - array of numbers. - - ```javascript - const polygonSchema = new mongoose.Schema({ - type: { - type: String, - enum: ['Polygon'], - required: true - }, - coordinates: { - type: [[[Number]]], // Array of arrays of arrays of numbers - required: true - } - }); - - const citySchema = new mongoose.Schema({ - name: String, - location: polygonSchema - }); - ``` - -

    Geospatial Queries with Mongoose

    - - Mongoose queries support the same [geospatial query operators](http://thecodebarbarian.com/80-20-guide-to-mongodb-geospatial-queries) - that the MongoDB driver does. For example, the below script saves a - `city` document those `location` property is a GeoJSON point representing - the city of Denver, Colorado. It then queries for all documents within - a polygon representing the state of Colorado using - [the MongoDB `$geoWithin` operator](https://docs.mongodb.com/manual/reference/operator/query/geoWithin/). - - - - ```javascript - [require:geojson.*driver query] - ``` - - Mongoose also has a [`within()` helper](/docs/api.html#query_Query-within) - that's a shorthand for `$geoWithin`. - - ```javascript - [require:geojson.*within helper] - ``` - -

    Geospatial Indexes

    - - MongoDB supports [2dsphere indexes](https://docs.mongodb.com/manual/core/2dsphere/) - for speeding up geospatial queries. Here's how you can define - a 2dsphere index on a GeoJSON point: - - ```javascript - [require:geojson.*index$] - ``` - - You can also define a geospatial index using the [`Schema#index()` function](/docs/api/schema.html#schema_Schema-index) - as shown below. - - ```javascript - citySchema.index({ location: '2dsphere' }); - ``` - - MongoDB's [`$near` query operator](https://docs.mongodb.com/v4.0/reference/operator/query/near/#op._S_near) - and [`$geoNear` aggregation stage](https://docs.mongodb.com/manual/reference/operator/aggregation/geoNear/#pipe._S_geoNear) - _require_ a 2dsphere index. diff --git a/docs/guide.html b/docs/guide.html deleted file mode 100644 index b5f153ffb27..00000000000 --- a/docs/guide.html +++ /dev/null @@ -1,900 +0,0 @@ -Mongoose v5.6.0: Schemas

    Schemas

    - - - -

    If you haven't yet done so, please take a minute to read the quickstart to get an idea of how Mongoose works. -If you are migrating from 4.x to 5.x please take a moment to read the migration guide.

    -
    - -

    Defining your schema

    - -

    Everything in Mongoose starts with a Schema. Each schema maps to a MongoDB -collection and defines the shape of the documents within that collection.

    -
      var mongoose = require('mongoose');
    -  var Schema = mongoose.Schema;
    -
    -  var blogSchema = new Schema({
    -    title:  String,
    -    author: String,
    -    body:   String,
    -    comments: [{ body: String, date: Date }],
    -    date: { type: Date, default: Date.now },
    -    hidden: Boolean,
    -    meta: {
    -      votes: Number,
    -      favs:  Number
    -    }
    -  });
    -

    If you want to add additional keys later, use the -Schema#add method.

    -

    Each key in our code blogSchema defines a property in our documents which -will be cast to its associated SchemaType. -For example, we've defined a property title which will be cast to the -String SchemaType and property date -which will be cast to a Date SchemaType. Keys may also be assigned -nested objects containing further key/type definitions like -the meta property above.

    -

    The permitted SchemaTypes are:

    - -

    Read more about SchemaTypes here.

    -

    Schemas not only define the structure of your document and casting of -properties, they also define document instance methods, -static Model methods, compound indexes, -and document lifecycle hooks called middleware.

    -

    Creating a model

    - -

    To use our schema definition, we need to convert our blogSchema into a -Model we can work with. -To do so, we pass it into mongoose.model(modelName, schema):

    -
      var Blog = mongoose.model('Blog', blogSchema);
    -  // ready to go!
    -

    Instance methods

    - -

    Instances of Models are documents. Documents have -many of their own built-in instance methods. -We may also define our own custom document instance methods too.

    -
      // define a schema
    -  var animalSchema = new Schema({ name: String, type: String });
    -
    -  // assign a function to the "methods" object of our animalSchema
    -  animalSchema.methods.findSimilarTypes = function(cb) {
    -    return this.model('Animal').find({ type: this.type }, cb);
    -  };
    -

    Now all of our animal instances have a findSimilarTypes method available -to them.

    -
      var Animal = mongoose.model('Animal', animalSchema);
    -  var dog = new Animal({ type: 'dog' });
    -
    -  dog.findSimilarTypes(function(err, dogs) {
    -    console.log(dogs); // woof
    -  });
    -
      -
    • Overwriting a default mongoose document method may lead to unpredictable results. See this for more details.
    • -
    • The example above uses the Schema.methods object directly to save an instance method. You can also use the Schema.method() helper as described here.
    • -
    • Do not declare methods using ES6 arrow functions (=>). Arrow functions explicitly prevent binding this, so your method will not have access to the document and the above examples will not work.
    • -
    -

    Statics

    - -

    You can also add static functions to your model. There are 2 equivalent -ways to add a static:

    - -
      // Assign a function to the "statics" object of our animalSchema
    -  animalSchema.statics.findByName = function(name) {
    -    return this.find({ name: new RegExp(name, 'i') });
    -  };
    -  // Or, equivalently, you can call `animalSchema.static()`.
    -  animalSchema.static('findByBreed', function(breed) {
    -    return this.find({ breed });
    -  });
    -
    -  const Animal = mongoose.model('Animal', animalSchema);
    -  let animals = await Animal.findByName('fido');
    -  animls = animals.concat(await Animal.findByBreed('Poodle'));
    -

    Do not declare statics using ES6 arrow functions (=>). Arrow functions explicitly prevent binding this, so the above examples will not work because of the value of this.

    -

    Query Helpers

    - -

    You can also add query helper functions, which are like instance methods -but for mongoose queries. Query helper methods let you extend mongoose's -chainable query builder API.

    -
      animalSchema.query.byName = function(name) {
    -    return this.where({ name: new RegExp(name, 'i') });
    -  };
    -
    -  var Animal = mongoose.model('Animal', animalSchema);
    -
    -  Animal.find().byName('fido').exec(function(err, animals) {
    -    console.log(animals);
    -  });
    -
    -  Animal.findOne().byName('fido').exec(function(err, animal) {
    -    console.log(animal);
    -  });
    -

    Indexes

    - -

    MongoDB supports secondary indexes. -With mongoose, we define these indexes within our Schema at the path level or the schema level. -Defining indexes at the schema level is necessary when creating -compound indexes.

    -
      var animalSchema = new Schema({
    -    name: String,
    -    type: String,
    -    tags: { type: [String], index: true } // field level
    -  });
    -
    -  animalSchema.index({ name: 1, type: -1 }); // schema level
    -

    When your application starts up, Mongoose automatically calls createIndex for each defined index in your schema. -Mongoose will call createIndex for each index sequentially, and emit an 'index' event on the model when all the createIndex calls succeeded or when there was an error. -While nice for development, it is recommended this behavior be disabled in production since index creation can cause a significant performance impact. Disable the behavior by setting the autoIndex option of your schema to false, or globally on the connection by setting the option autoIndex to false.

    -
      mongoose.connect('mongodb://user:pass@localhost:port/database', { autoIndex: false });
    -  // or
    -  mongoose.createConnection('mongodb://user:pass@localhost:port/database', { autoIndex: false });
    -  // or
    -  animalSchema.set('autoIndex', false);
    -  // or
    -  new Schema({..}, { autoIndex: false });
    -

    Mongoose will emit an index event on the model when indexes are done -building or an error occurred.

    -
      // Will cause an error because mongodb has an _id index by default that
    -  // is not sparse
    -  animalSchema.index({ _id: 1 }, { sparse: true });
    -  var Animal = mongoose.model('Animal', animalSchema);
    -
    -  Animal.on('index', function(error) {
    -    // "_id index cannot be sparse"
    -    console.log(error.message);
    -  });
    -

    See also the Model#ensureIndexes method.

    -

    Virtuals

    - -

    Virtuals are document properties that -you can get and set but that do not get persisted to MongoDB. The getters -are useful for formatting or combining fields, while setters are useful for -de-composing a single value into multiple values for storage.

    -
      // define a schema
    -  var personSchema = new Schema({
    -    name: {
    -      first: String,
    -      last: String
    -    }
    -  });
    -
    -  // compile our model
    -  var Person = mongoose.model('Person', personSchema);
    -
    -  // create a document
    -  var axl = new Person({
    -    name: { first: 'Axl', last: 'Rose' }
    -  });
    -

    Suppose you want to print out the person's full name. You could do it yourself:

    -
    console.log(axl.name.first + ' ' + axl.name.last); // Axl Rose
    -

    But concatenating the first and last name every time can get cumbersome. -And what if you want to do some extra processing on the name, like -removing diacritics? A -virtual property getter lets you -define a fullName property that won't get persisted to MongoDB.

    -
    personSchema.virtual('fullName').get(function () {
    -  return this.name.first + ' ' + this.name.last;
    -});
    -

    Now, mongoose will call your getter function every time you access the -fullName property:

    -
    console.log(axl.fullName); // Axl Rose
    -

    If you use toJSON() or toObject() mongoose will not include virtuals -by default. This includes the output of calling JSON.stringify() -on a Mongoose document, because JSON.stringify() calls toJSON(). -Pass { virtuals: true } to either -toObject() or toJSON().

    -

    You can also add a custom setter to your virtual that will let you set both -first name and last name via the fullName virtual.

    -
    personSchema.virtual('fullName').
    -  get(function() { return this.name.first + ' ' + this.name.last; }).
    -  set(function(v) {
    -    this.name.first = v.substr(0, v.indexOf(' '));
    -    this.name.last = v.substr(v.indexOf(' ') + 1);
    -  });
    -
    -axl.fullName = 'William Rose'; // Now `axl.name.first` is "William"
    -

    Virtual property setters are applied before other validation. So the example -above would still work even if the first and last name fields were -required.

    -

    Only non-virtual properties work as part of queries and for field selection. -Since virtuals are not stored in MongoDB, you can't query with them.

    -
    Aliases
    - -

    Aliases are a particular type of virtual where the getter and setter -seamlessly get and set another property. This is handy for saving network -bandwidth, so you can convert a short property name stored in the database -into a longer name for code readability.

    -
    var personSchema = new Schema({
    -  n: {
    -    type: String,
    -    // Now accessing `name` will get you the value of `n`, and setting `n` will set the value of `name`
    -    alias: 'name'
    -  }
    -});
    -
    -// Setting `name` will propagate to `n`
    -var person = new Person({ name: 'Val' });
    -console.log(person); // { n: 'Val' }
    -console.log(person.toObject({ virtuals: true })); // { n: 'Val', name: 'Val' }
    -console.log(person.name); // "Val"
    -
    -person.name = 'Not Val';
    -console.log(person); // { n: 'Not Val' }
    -

    You can also declare aliases on nested paths. It is easier to use nested -schemas and subdocuments, but you can also declare -nested path aliases inline as long as you use the full nested path -nested.myProp as the alias.

    -
    const childSchema = new Schema({
    -  n: {
    -    type: String,
    -    alias: 'name'
    -  }
    -}, { _id: false });
    -
    -const parentSchema = new Schema({
    -  // If in a child schema, alias doesn't need to include the full nested path
    -  c: childSchema,
    -  name: {
    -    f: {
    -      type: String,
    -      // Alias needs to include the full nested path if declared inline
    -      alias: 'name.first'
    -    }
    -  }
    -});
    -

    Options

    - -

    Schemas have a few configurable options which can be passed to the -constructor or set directly:

    -
    new Schema({..}, options);
    -
    -// or
    -
    -var schema = new Schema({..});
    -schema.set(option, value);
    -

    Valid options:

    - -

    option: autoIndex

    - -

    By default, Mongoose's init() function -creates all the indexes defined in your model's schema by calling -Model.createIndexes() -after you successfully connect to MongoDB. Creating indexes automatically is -great for development and test environments. But index builds can also create -significant load on your production database. If you want to manage indexes -carefully in production, you can set autoIndex to false.

    -
    const schema = new Schema({..}, { autoIndex: false });
    -const Clock = mongoose.model('Clock', schema);
    -Clock.ensureIndexes(callback);
    -

    The autoIndex option is set to true by default. You can change this -default by setting mongoose.use('autoIndex', false);

    -

    option: autoCreate

    - -

    Before Mongoose builds indexes, it calls Model.createCollection() -to create the underlying collection in MongoDB if autoCreate is set to true. -Calling createCollection() -sets the collection's default collation -based on the collation option and establishes the collection as -a capped collection if you set the capped schema option. Like -autoIndex, setting autoCreate to true is helpful for development and -test environments.

    -

    Unfortunately, createCollection() cannot change an existing collection. -For example, if you add capped: 1024 to your schema and the existing -collection is not capped, createCollection() will throw an error. -Generally, autoCreate should be false for production environments.

    -
    const schema = new Schema({..}, { autoCreate: true, capped: 1024 });
    -const Clock = mongoose.model('Clock', schema);
    -// Mongoose will create the capped collection for you.
    -

    Unlike autoIndex, autoCreate is false by default. You can change this -default by setting mongoose.use('autoCreate', true);

    -

    option: bufferCommands

    - -

    By default, mongoose buffers commands when the connection goes down until -the driver manages to reconnect. To disable buffering, set bufferCommands -to false.

    -
    var schema = new Schema({..}, { bufferCommands: false });
    -

    The schema bufferCommands option overrides the global bufferCommands option.

    -
    mongoose.set('bufferCommands', true);
    -// Schema option below overrides the above, if the schema option is set.
    -var schema = new Schema({..}, { bufferCommands: false });
    -

    option: capped

    - -

    Mongoose supports MongoDBs capped -collections. To specify the underlying MongoDB collection be capped, set -the capped option to the maximum size of the collection in -bytes.

    -
    new Schema({..}, { capped: 1024 });
    -

    The capped option may also be set to an object if you want to pass -additional options like max -or autoIndexId. -In this case you must explicitly pass the size option, which is required.

    -
    new Schema({..}, { capped: { size: 1024, max: 1000, autoIndexId: true } });
    -

    option: collection

    - -

    Mongoose by default produces a collection name by passing the model name to -the utils.toCollectionName method. -This method pluralizes the name. Set this option if you need a different name -for your collection.

    -
    var dataSchema = new Schema({..}, { collection: 'data' });
    -

    option: id

    - -

    Mongoose assigns each of your schemas an id virtual getter by default -which returns the documents _id field cast to a string, or in the case of -ObjectIds, its hexString. If you don't want an id getter added to your -schema, you may disable it passing this option at schema construction time.

    -
    // default behavior
    -var schema = new Schema({ name: String });
    -var Page = mongoose.model('Page', schema);
    -var p = new Page({ name: 'mongodb.org' });
    -console.log(p.id); // '50341373e894ad16347efe01'
    -
    -// disabled id
    -var schema = new Schema({ name: String }, { id: false });
    -var Page = mongoose.model('Page', schema);
    -var p = new Page({ name: 'mongodb.org' });
    -console.log(p.id); // undefined
    -

    option: _id

    - -

    Mongoose assigns each of your schemas an _id field by default if one -is not passed into the Schema constructor. -The type assigned is an ObjectId -to coincide with MongoDB's default behavior. If you don't want an _id -added to your schema at all, you may disable it using this option.

    -

    You can only use this option on subdocuments. Mongoose can't -save a document without knowing its id, so you will get an error if -you try to save a document without an _id.

    -
    // default behavior
    -var schema = new Schema({ name: String });
    -var Page = mongoose.model('Page', schema);
    -var p = new Page({ name: 'mongodb.org' });
    -console.log(p); // { _id: '50341373e894ad16347efe01', name: 'mongodb.org' }
    -
    -// disabled _id
    -var childSchema = new Schema({ name: String }, { _id: false });
    -var parentSchema = new Schema({ children: [childSchema] });
    -
    -var Model = mongoose.model('Model', parentSchema);
    -
    -Model.create({ children: [{ name: 'Luke' }] }, function(error, doc) {
    -  // doc.children[0]._id will be undefined
    -});
    -

    option: minimize

    - -

    Mongoose will, by default, "minimize" schemas by removing empty objects.

    -
    const schema = new Schema({ name: String, inventory: {} });
    -const Character = mongoose.model('Character', schema);
    -
    -// will store `inventory` field if it is not empty
    -const frodo = new Character({ name: 'Frodo', inventory: { ringOfPower: 1 }});
    -await frodo.save();
    -let doc = await Character.findOne({ name: 'Frodo' }).lean();
    -doc.inventory; // { ringOfPower: 1 }
    -
    -// will not store `inventory` field if it is empty
    -const sam = new Character({ name: 'Sam', inventory: {}});
    -await sam.save();
    -doc = await Character.findOne({ name: 'Sam' }).lean();
    -doc.inventory; // undefined
    -

    This behavior can be overridden by setting minimize option to false. It -will then store empty objects.

    -
    const schema = new Schema({ name: String, inventory: {} }, { minimize: false });
    -const Character = mongoose.model('Character', schema);
    -
    -// will store `inventory` if empty
    -const sam = new Character({ name: 'Sam', inventory: {} });
    -await sam.save();
    -doc = await Character.findOne({ name: 'Sam' }).lean();
    -doc.inventory; // {}
    -

    To check whether an object is empty, you can use the $isEmpty() helper:

    -
    const sam = new Character({ name: 'Sam', inventory: {} });
    -sam.$isEmpty('inventory'); // true
    -
    -sam.inventory.barrowBlade = 1;
    -sam.$isEmpty('inventory'); // false
    -

    option: read

    - -

    Allows setting query#read options at the -schema level, providing us a way to apply default -ReadPreferences -to all queries derived from a model.

    -
    var schema = new Schema({..}, { read: 'primary' });            // also aliased as 'p'
    -var schema = new Schema({..}, { read: 'primaryPreferred' });   // aliased as 'pp'
    -var schema = new Schema({..}, { read: 'secondary' });          // aliased as 's'
    -var schema = new Schema({..}, { read: 'secondaryPreferred' }); // aliased as 'sp'
    -var schema = new Schema({..}, { read: 'nearest' });            // aliased as 'n'
    -

    The alias of each pref is also permitted so instead of having to type out -'secondaryPreferred' and getting the spelling wrong, we can simply pass 'sp'.

    -

    The read option also allows us to specify tag sets. These tell the -driver from which members -of the replica-set it should attempt to read. Read more about tag sets -here and -here.

    -

    NOTE: you may also specify the driver read pref strategy -option when connecting:

    -
    // pings the replset members periodically to track network latency
    -var options = { replset: { strategy: 'ping' }};
    -mongoose.connect(uri, options);
    -
    -var schema = new Schema({..}, { read: ['nearest', { disk: 'ssd' }] });
    -mongoose.model('JellyBean', schema);
    -

    option: writeConcern

    - -

    Allows setting write concern -at the schema level.

    -
    const schema = new Schema({ name: String }, {
    -  writeConcern: {
    -    w: 'majority',
    -    j: true,
    -    wtimeout: 1000
    -  }
    -});
    -

    option: shardKey

    - -

    The shardKey option is used when we have a sharded MongoDB architecture. -Each sharded collection is given a shard key which must be present in all -insert/update operations. We just need to set this schema option to the same -shard key and we’ll be all set.

    -
    new Schema({ .. }, { shardKey: { tag: 1, name: 1 }})
    -

    Note that Mongoose does not send the shardcollection command for you. You -must configure your shards yourself.

    -

    option: strict

    - -

    The strict option, (enabled by default), ensures that values passed to our -model constructor that were not specified in our schema do not get saved to -the db.

    -
    var thingSchema = new Schema({..})
    -var Thing = mongoose.model('Thing', thingSchema);
    -var thing = new Thing({ iAmNotInTheSchema: true });
    -thing.save(); // iAmNotInTheSchema is not saved to the db
    -
    -// set to false..
    -var thingSchema = new Schema({..}, { strict: false });
    -var thing = new Thing({ iAmNotInTheSchema: true });
    -thing.save(); // iAmNotInTheSchema is now saved to the db!!
    -

    This also affects the use of doc.set() to set a property value.

    -
    var thingSchema = new Schema({..})
    -var Thing = mongoose.model('Thing', thingSchema);
    -var thing = new Thing;
    -thing.set('iAmNotInTheSchema', true);
    -thing.save(); // iAmNotInTheSchema is not saved to the db
    -

    This value can be overridden at the model instance level by passing a second -boolean argument:

    -
    var Thing = mongoose.model('Thing');
    -var thing = new Thing(doc, true);  // enables strict mode
    -var thing = new Thing(doc, false); // disables strict mode
    -

    The strict option may also be set to "throw" which will cause errors -to be produced instead of dropping the bad data.

    -

    NOTE: Any key/val set on the instance that does not exist in your schema is always ignored, regardless of schema option.

    -
    var thingSchema = new Schema({..})
    -var Thing = mongoose.model('Thing', thingSchema);
    -var thing = new Thing;
    -thing.iAmNotInTheSchema = true;
    -thing.save(); // iAmNotInTheSchema is never saved to the db
    -

    option: strictQuery

    - -

    For backwards compatibility, the strict option does not apply to -the filter parameter for queries.

    -
    const mySchema = new Schema({ field: Number }, { strict: true });
    -const MyModel = mongoose.model('Test', mySchema);
    -
    -// Mongoose will **not** filter out `notInSchema: 1`, despite `strict: true`
    -MyModel.find({ notInSchema: 1 });
    -

    The strict option does apply to updates.

    -
    // Mongoose will strip out `notInSchema` from the update if `strict` is
    -// not `false`
    -MyModel.updateMany({}, { $set: { notInSchema: 1 } });
    -

    Mongoose has a separate strictQuery option to toggle strict mode for -the filter parameter to queries.

    -
    const mySchema = new Schema({ field: Number }, {
    -  strict: true,
    -  strictQuery: true // Turn on strict mode for query filters
    -});
    -const MyModel = mongoose.model('Test', mySchema);
    -
    -// Mongoose will strip out `notInSchema: 1` because `strictQuery` is `true`
    -MyModel.find({ notInSchema: 1 });
    -

    option: toJSON

    - -

    Exactly the same as the toObject option but only applies when -the documents toJSON method is called.

    -
    var schema = new Schema({ name: String });
    -schema.path('name').get(function (v) {
    -  return v + ' is my name';
    -});
    -schema.set('toJSON', { getters: true, virtuals: false });
    -var M = mongoose.model('Person', schema);
    -var m = new M({ name: 'Max Headroom' });
    -console.log(m.toObject()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom' }
    -console.log(m.toJSON()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' }
    -// since we know toJSON is called whenever a js object is stringified:
    -console.log(JSON.stringify(m)); // { "_id": "504e0cd7dd992d9be2f20b6f", "name": "Max Headroom is my name" }
    -

    To see all available toJSON/toObject options, read this.

    -

    option: toObject

    - -

    Documents have a toObject method -which converts the mongoose document into a plain javascript object. This -method accepts a few options. Instead of applying these options on a -per-document basis we may declare the options here and have it applied to -all of this schemas documents by default.

    -

    To have all virtuals show up in your console.log output, set the -toObject option to { getters: true }:

    -
    var schema = new Schema({ name: String });
    -schema.path('name').get(function (v) {
    -  return v + ' is my name';
    -});
    -schema.set('toObject', { getters: true });
    -var M = mongoose.model('Person', schema);
    -var m = new M({ name: 'Max Headroom' });
    -console.log(m); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' }
    -

    To see all available toObject options, read this.

    -

    option: typeKey

    - -

    By default, if you have an object with key 'type' in your schema, mongoose -will interpret it as a type declaration.

    -
    // Mongoose interprets this as 'loc is a String'
    -var schema = new Schema({ loc: { type: String, coordinates: [Number] } });
    -

    However, for applications like geoJSON, -the 'type' property is important. If you want to control which key mongoose -uses to find type declarations, set the 'typeKey' schema option.

    -
    var schema = new Schema({
    -  // Mongoose interpets this as 'loc is an object with 2 keys, type and coordinates'
    -  loc: { type: String, coordinates: [Number] },
    -  // Mongoose interprets this as 'name is a String'
    -  name: { $type: String }
    -}, { typeKey: '$type' }); // A '$type' key means this object is a type declaration
    -

    option: validateBeforeSave

    - -

    By default, documents are automatically validated before they are saved to -the database. This is to prevent saving an invalid document. If you want to -handle validation manually, and be able to save objects which don't pass -validation, you can set validateBeforeSave to false.

    -
    var schema = new Schema({ name: String });
    -schema.set('validateBeforeSave', false);
    -schema.path('name').validate(function (value) {
    -    return v != null;
    -});
    -var M = mongoose.model('Person', schema);
    -var m = new M({ name: null });
    -m.validate(function(err) {
    -    console.log(err); // Will tell you that null is not allowed.
    -});
    -m.save(); // Succeeds despite being invalid
    -

    option: versionKey

    - -

    The versionKey is a property set on each document when first created by -Mongoose. This keys value contains the internal -revision -of the document. The versionKey option is a string that represents the -path to use for versioning. The default is __v. If this conflicts with -your application you can configure as such:

    -
    const schema = new Schema({ name: 'string' });
    -const Thing = mongoose.model('Thing', schema);
    -const thing = new Thing({ name: 'mongoose v3' });
    -await thing.save(); // { __v: 0, name: 'mongoose v3' }
    -
    -// customized versionKey
    -new Schema({..}, { versionKey: '_somethingElse' })
    -const Thing = mongoose.model('Thing', schema);
    -const thing = new Thing({ name: 'mongoose v3' });
    -thing.save(); // { _somethingElse: 0, name: 'mongoose v3' }
    -

    Note that Mongoose versioning is not a full optimistic concurrency -solution. Use mongoose-update-if-current -for OCC support. Mongoose versioning only operates on arrays:

    -
    // 2 copies of the same document
    -const doc1 = await Model.findOne({ _id });
    -const doc2 = await Model.findOne({ _id });
    -
    -// Delete first 3 comments from `doc1`
    -doc1.comments.splice(0, 3);
    -await doc1.save();
    -
    -// The below `save()` will throw a VersionError, because you're trying to
    -// modify the comment at index 1, and the above `splice()` removed that
    -// comment.
    -doc2.set('comments.1.body', 'new comment');
    -await doc2.save();
    -

    Document versioning can also be disabled by setting the versionKey to -false. -DO NOT disable versioning unless you know what you are doing.

    -
    new Schema({..}, { versionKey: false });
    -const Thing = mongoose.model('Thing', schema);
    -const thing = new Thing({ name: 'no versioning please' });
    -thing.save(); // { name: 'no versioning please' }
    -

    Mongoose only updates the version key when you use save(). -If you use update(), findOneAndUpdate(), etc. Mongoose will not -update the version key. As a workaround, you can use the below middleware.

    -
    schema.pre('findOneAndUpdate', function() {
    -  const update = this.getUpdate();
    -  if (update.__v != null) {
    -    delete update.__v;
    -  }
    -  const keys = ['$set', '$setOnInsert'];
    -  for (const key of keys) {
    -    if (update[key] != null && update[key].__v != null) {
    -      delete update[key].__v;
    -      if (Object.keys(update[key]).length === 0) {
    -        delete update[key];
    -      }
    -    }
    -  }
    -  update.$inc = update.$inc || {};
    -  update.$inc.__v = 1;
    -});
    -

    option: collation

    - -

    Sets a default collation -for every query and aggregation. Here's a beginner-friendly overview of collations.

    -
    var schema = new Schema({
    -  name: String
    -}, { collation: { locale: 'en_US', strength: 1 } });
    -
    -var MyModel = db.model('MyModel', schema);
    -
    -MyModel.create([{ name: 'val' }, { name: 'Val' }]).
    -  then(function() {
    -    return MyModel.find({ name: 'val' });
    -  }).
    -  then(function(docs) {
    -    // `docs` will contain both docs, because `strength: 1` means
    -    // MongoDB will ignore case when matching.
    -  });
    -

    option: skipVersioning

    - -

    skipVersioning allows excluding paths from versioning (i.e., the internal -revision will not be incremented even if these paths are updated). DO NOT -do this unless you know what you're doing. For subdocuments, include this -on the parent document using the fully qualified path.

    -
    new Schema({..}, { skipVersioning: { dontVersionMe: true } });
    -thing.dontVersionMe.push('hey');
    -thing.save(); // version is not incremented
    -

    option: timestamps

    - -

    If set timestamps, mongoose assigns createdAt and updatedAt fields to -your schema, the type assigned is Date.

    -

    By default, the name of two fields are createdAt and updatedAt, customize -the field name by setting timestamps.createdAt and timestamps.updatedAt.

    -
    const thingSchema = new Schema({..}, { timestamps: { createdAt: 'created_at' } });
    -const Thing = mongoose.model('Thing', thingSchema);
    -const thing = new Thing();
    -await thing.save(); // `created_at` & `updatedAt` will be included
    -
    -// With updates, Mongoose will add `updatedAt` to `$set`
    -await Thing.updateOne({}, { $set: { name: 'Test' } });
    -
    -// If you set upsert: true, Mongoose will add `created_at` to `$setOnInsert` as well
    -await Thing.findOneAndUpdate({}, { $set: { name: 'Test2' } });
    -
    -// Mongoose also adds timestamps to bulkWrite() operations
    -// See https://mongoosejs.com/docs/api.html#model_Model.bulkWrite
    -await Thing.bulkWrite([
    -  insertOne: {
    -    document: {
    -      name: 'Jean-Luc Picard',
    -      ship: 'USS Stargazer'
    -      // Mongoose will add `created_at` and `updatedAt`
    -    }
    -  },
    -  updateOne: {
    -    filter: { name: 'Jean-Luc Picard' },
    -    update: {
    -      $set: {
    -        ship: 'USS Enterprise'
    -        // Mongoose will add `updatedAt`
    -      }
    -    }
    -  }
    -]);
    -

    option: useNestedStrict

    - -

    Write operations like update(), updateOne(), updateMany(), -and findOneAndUpdate() only check the top-level -schema's strict mode setting.

    -
    var childSchema = new Schema({}, { strict: false });
    -var parentSchema = new Schema({ child: childSchema }, { strict: 'throw' });
    -var Parent = mongoose.model('Parent', parentSchema);
    -Parent.update({}, { 'child.name': 'Luke Skywalker' }, function(error) {
    -  // Error because parentSchema has `strict: throw`, even though
    -  // `childSchema` has `strict: false`
    -});
    -
    -var update = { 'child.name': 'Luke Skywalker' };
    -var opts = { strict: false };
    -Parent.update({}, update, opts, function(error) {
    -  // This works because passing `strict: false` to `update()` overwrites
    -  // the parent schema.
    -});
    -

    If you set useNestedStrict to true, mongoose will use the child schema's -strict option for casting updates.

    -
    var childSchema = new Schema({}, { strict: false });
    -var parentSchema = new Schema({ child: childSchema },
    -  { strict: 'throw', useNestedStrict: true });
    -var Parent = mongoose.model('Parent', parentSchema);
    -Parent.update({}, { 'child.name': 'Luke Skywalker' }, function(error) {
    -  // Works!
    -});
    -

    - - option: selectPopulatedPaths - -

    - -

    By default, Mongoose will automatically select() any populated paths for -you, unless you explicitly exclude them.

    -
    const bookSchema = new Schema({
    -  title: 'String',
    -  author: { type: 'ObjectId', ref: 'Person' }
    -});
    -const Book = mongoose.model('Book', bookSchema);
    -
    -// By default, Mongoose will add `author` to the below `select()`.
    -await Book.find().select('title').populate('author');
    -
    -// In other words, the below query is equivalent to the above
    -await Book.find().select('title author').populate('author');
    -

    To opt out of selecting populated fields by default, set selectPopulatedPaths -to false in your schema.

    -
    const bookSchema = new Schema({
    -  title: 'String',
    -  author: { type: 'ObjectId', ref: 'Person' }
    -}, { selectPopulatedPaths: false });
    -const Book = mongoose.model('Book', bookSchema);
    -
    -// Because `selectPopulatedPaths` is false, the below doc will **not**
    -// contain an `author` property.
    -const doc = await Book.findOne().select('title').populate('author');
    -

    - - option: storeSubdocValidationError - -

    - -

    For legacy reasons, when there is a validation error in subpath of a -single nested schema, Mongoose will record that there was a validation error -in the single nested schema path as well. For example:

    -
    const childSchema = new Schema({ name: { type: String, required: true } });
    -const parentSchema = new Schema({ child: childSchema });
    -
    -const Parent = mongoose.model('Parent', parentSchema);
    -
    -// Will contain an error for both 'child.name' _and_ 'child'
    -new Parent({ child: {} }).validateSync().errors;
    -

    Set the storeSubdocValidationError to false on the child schema to make -Mongoose only report the parent error.

    -
    const childSchema = new Schema({
    -  name: { type: String, required: true }
    -}, { storeSubdocValidationError: false }); // <-- set on the child schema
    -const parentSchema = new Schema({ child: childSchema });
    -
    -const Parent = mongoose.model('Parent', parentSchema);
    -
    -// Will only contain an error for 'child.name'
    -new Parent({ child: {} }).validateSync().errors;
    -

    Pluggable

    - -

    Schemas are also pluggable which allows us to package up reusable features into -plugins that can be shared with the community or just between your projects.

    -

    Further Reading

    - -

    To get the most out of MongoDB, you need to learn the basics of MongoDB schema design. -SQL schema design (third normal form) was designed to minimize storage costs, -whereas MongoDB schema design is about making common queries as fast as possible. -The 6 Rules of Thumb for MongoDB Schema Design blog series -is an excellent resource for learning the basic rules for making your queries -fast.

    -

    Users looking to master MongoDB schema design in Node.js should look into -The Little MongoDB Schema Design Book -by Christian Kvalheim, the original author of the MongoDB Node.js driver. -This book shows you how to implement performant schemas for a laundry list -of use cases, including ecommerce, wikis, and appointment bookings.

    -

    Next Up

    - -

    Now that we've covered Schemas, let's take a look at SchemaTypes.

    -
    \ No newline at end of file diff --git a/docs/guide.md b/docs/guide.md new file mode 100644 index 00000000000..acf75a90487 --- /dev/null +++ b/docs/guide.md @@ -0,0 +1,1280 @@ +## Schemas + +If you haven't yet done so, please take a minute to read the [quickstart](./index.html) to get an idea of how Mongoose works. +If you are migrating from 4.x to 5.x please take a moment to read the [migration guide](/docs/migrating_to_5.html). + + + +

    Defining your schema

    + +Everything in Mongoose starts with a Schema. Each schema maps to a MongoDB +collection and defines the shape of the documents within that collection. + +```javascript + import mongoose from 'mongoose'; + const { Schema } = mongoose; + + const blogSchema = new Schema({ + title: String, // String is shorthand for {type: String} + author: String, + body: String, + comments: [{ body: String, date: Date }], + date: { type: Date, default: Date.now }, + hidden: Boolean, + meta: { + votes: Number, + favs: Number + } + }); +``` + +If you want to add additional keys later, use the +[Schema#add](./api.html#schema_Schema-add) method. + +Each key in our code `blogSchema` defines a property in our documents which +will be cast to its associated [SchemaType](./api.html#schematype_SchemaType). +For example, we've defined a property `title` which will be cast to the +[String](./api.html#schema-string-js) SchemaType and property `date` +which will be cast to a `Date` SchemaType. + +Notice above that if a property only requires a type, it can be specified using +a shorthand notation (contrast the `title` property above with the `date` +property). + +Keys may also be assigned nested objects containing further key/type definitions +like the `meta` property above. This will happen whenever a key's value is a POJO +that doesn't have a `type` property. + +In these cases, Mongoose only creates actual schema paths for leaves +in the tree. (like `meta.votes` and `meta.favs` above), +and the branches do not have actual paths. A side-effect of this is that `meta` +above cannot have its own validation. If validation is needed up the tree, a path +needs to be created up the tree - see the [Subdocuments](./subdocs.html) section +for more information on how to do this. Also read the [Mixed](./schematypes.html) +subsection of the SchemaTypes guide for some gotchas. + +The permitted SchemaTypes are: + +* [String](./schematypes.html#strings) +* [Number](./schematypes.html#numbers) +* [Date](./schematypes.html#dates) +* [Buffer](./schematypes.html#buffers) +* [Boolean](./schematypes.html#booleans) +* [Mixed](./schematypes.html#mixed) +* [ObjectId](./schematypes.html#objectids) +* [Array](./schematypes.html#arrays) +* [Decimal128](./api.html#mongoose_Mongoose-Decimal128) +* [Map](./schematypes.html#maps) + +Read more about [SchemaTypes here](./schematypes.html). + +Schemas not only define the structure of your document and casting of +properties, they also define document [instance methods](#methods), +[static Model methods](#statics), [compound indexes](#indexes), +and document lifecycle hooks called [middleware](./middleware.html). + +

    Creating a model

    + +To use our schema definition, we need to convert our `blogSchema` into a +[Model](./models.html) we can work with. +To do so, we pass it into `mongoose.model(modelName, schema)`: + +```javascript + const Blog = mongoose.model('Blog', blogSchema); + // ready to go! +``` + +

    Ids

    + +By default, Mongoose adds an `_id` property to your schemas. + +```javascript +const schema = new Schema(); + +schema.path('_id'); // ObjectId { ... } +``` + +When you create a new document with the automatically added +`_id` property, Mongoose creates a new [`_id` of type ObjectId](https://masteringjs.io/tutorials/mongoose/objectid) +to your document. + +```javascript +const Model = mongoose.model('Test', schema); + +const doc = new Model(); +doc._id instanceof mongoose.Types.ObjectId; // true +``` + +You can also overwrite Mongoose's default `_id` with your +own `_id`. Just be careful: Mongoose will refuse to save a +document that doesn't have an `_id`, so you're responsible +for setting `_id` if you define your own `_id` path. + +```javascript +const schema = new Schema({ _id: Number }); +const Model = mongoose.model('Test', schema); + +const doc = new Model(); +await doc.save(); // Throws "document must have an _id before saving" + +doc._id = 1; +await doc.save(); // works +``` + +

    Instance methods

    + +Instances of `Models` are [documents](./documents.html). Documents have +many of their own [built-in instance methods](./api/document.html). +We may also define our own custom document instance methods. + +```javascript + // define a schema + const animalSchema = new Schema({ name: String, type: String }); + + // assign a function to the "methods" object of our animalSchema + animalSchema.methods.findSimilarTypes = function(cb) { + return mongoose.model('Animal').find({ type: this.type }, cb); + }; +``` + +Now all of our `animal` instances have a `findSimilarTypes` method available +to them. + +```javascript + const Animal = mongoose.model('Animal', animalSchema); + const dog = new Animal({ type: 'dog' }); + + dog.findSimilarTypes((err, dogs) => { + console.log(dogs); // woof + }); +``` + +* Overwriting a default mongoose document method may lead to unpredictable results. See [this](./api.html#schema_Schema.reserved) for more details. +* The example above uses the `Schema.methods` object directly to save an instance method. You can also use the `Schema.method()` helper as described [here](./api.html#schema_Schema-method). +* Do **not** declare methods using ES6 arrow functions (`=>`). Arrow functions [explicitly prevent binding `this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions#No_binding_of_this), so your method will **not** have access to the document and the above examples will not work. + +

    Statics

    + +You can also add static functions to your model. There are two equivalent +ways to add a static: + +- Add a function property to `schema.statics` +- Call the [`Schema#static()` function](/docs/api.html#schema_Schema-static) + +```javascript + // Assign a function to the "statics" object of our animalSchema + animalSchema.statics.findByName = function(name) { + return this.find({ name: new RegExp(name, 'i') }); + }; + // Or, equivalently, you can call `animalSchema.static()`. + animalSchema.static('findByBreed', function(breed) { return this.find({ breed }); }); + + const Animal = mongoose.model('Animal', animalSchema); + let animals = await Animal.findByName('fido'); + animals = animals.concat(await Animal.findByBreed('Poodle')); +``` + +Do **not** declare statics using ES6 arrow functions (`=>`). Arrow functions [explicitly prevent binding `this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions#No_binding_of_this), so the above examples will not work because of the value of `this`. + +

    Query Helpers

    + +You can also add query helper functions, which are like instance methods +but for mongoose queries. Query helper methods let you extend mongoose's +[chainable query builder API](./queries.html). + +```javascript + animalSchema.query.byName = function(name) { + return this.where({ name: new RegExp(name, 'i') }) + }; + + const Animal = mongoose.model('Animal', animalSchema); + + Animal.find().byName('fido').exec((err, animals) => { + console.log(animals); + }); + + Animal.findOne().byName('fido').exec((err, animal) => { + console.log(animal); + }); +``` + +

    Indexes

    + +MongoDB supports [secondary indexes](http://docs.mongodb.org/manual/indexes/). +With mongoose, we define these indexes within our `Schema` [at](./api.html#schematype_SchemaType-index) [the](./api.html#schematype_SchemaType-unique) [path](./api.html#schematype_SchemaType-sparse) [level](./api.html#schema_date_SchemaDate-expires) or the `schema` level. +Defining indexes at the schema level is necessary when creating +[compound indexes](https://docs.mongodb.com/manual/core/index-compound/). + +```javascript + const animalSchema = new Schema({ + name: String, + type: String, + tags: { type: [String], index: true } // field level + }); + + animalSchema.index({ name: 1, type: -1 }); // schema level +``` + +When your application starts up, Mongoose automatically calls [`createIndex`](https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/#db.collection.createIndex) for each defined index in your schema. +Mongoose will call `createIndex` for each index sequentially, and emit an 'index' event on the model when all the `createIndex` calls succeeded or when there was an error. +While nice for development, it is recommended this behavior be disabled in production since index creation can cause a [significant performance impact](https://docs.mongodb.com/manual/core/index-creation/#index-build-impact-on-database-performance). +Disable the behavior by setting the `autoIndex` option of your schema to `false`, or globally on the connection by setting the option `autoIndex` to `false`. + +```javascript + mongoose.connect('mongodb://user:pass@localhost:port/database', { autoIndex: false }); + // or + mongoose.createConnection('mongodb://user:pass@localhost:port/database', { autoIndex: false }); + // or + animalSchema.set('autoIndex', false); + // or + new Schema({..}, { autoIndex: false }); +``` + +Mongoose will emit an `index` event on the model when indexes are done +building or an error occurred. + +```javascript + // Will cause an error because mongodb has an _id index by default that + // is not sparse + animalSchema.index({ _id: 1 }, { sparse: true }); + const Animal = mongoose.model('Animal', animalSchema); + + Animal.on('index', error => { + // "_id index cannot be sparse" + console.log(error.message); + }); +``` + +See also the [Model#ensureIndexes](./api.html#model_Model.ensureIndexes) method. + +

    Virtuals

    + +[Virtuals](./api.html#schema_Schema-virtual) are document properties that +you can get and set but that do not get persisted to MongoDB. The getters +are useful for formatting or combining fields, while setters are useful for +de-composing a single value into multiple values for storage. + +```javascript + // define a schema + const personSchema = new Schema({ + name: { + first: String, + last: String + } + }); + + // compile our model + const Person = mongoose.model('Person', personSchema); + + // create a document + const axl = new Person({ + name: { first: 'Axl', last: 'Rose' } + }); +``` + +Suppose you want to print out the person's full name. You could do it yourself: + +```javascript +console.log(axl.name.first + ' ' + axl.name.last); // Axl Rose +``` + +But [concatenating](https://masteringjs.io/tutorials/fundamentals/string-concat) the first and +last name every time can get cumbersome. +And what if you want to do some extra processing on the name, like +[removing diacritics](https://www.npmjs.com/package/diacritics)? A +[virtual property getter](./api.html#virtualtype_VirtualType-get) lets you +define a `fullName` property that won't get persisted to MongoDB. + +```javascript +personSchema.virtual('fullName').get(function() { + return this.name.first + ' ' + this.name.last; +}); +``` + +Now, mongoose will call your getter function every time you access the +`fullName` property: + +```javascript +console.log(axl.fullName); // Axl Rose +``` + +If you use `toJSON()` or `toObject()` mongoose will *not* include virtuals +by default. This includes the output of calling [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) +on a Mongoose document, because [`JSON.stringify()` calls `toJSON()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Description). +Pass `{ virtuals: true }` to either +[`toObject()`](./api.html#document_Document-toObject) or [`toJSON()`](./api.html#document_Document-toJSON). + +You can also add a custom setter to your virtual that will let you set both +first name and last name via the `fullName` virtual. + +```javascript +personSchema.virtual('fullName'). + get(function() { + return this.name.first + ' ' + this.name.last; + }). + set(function(v) { + this.name.first = v.substr(0, v.indexOf(' ')); + this.name.last = v.substr(v.indexOf(' ') + 1); + }); + +axl.fullName = 'William Rose'; // Now `axl.name.first` is "William" +``` + +Virtual property setters are applied before other validation. So the example +above would still work even if the `first` and `last` name fields were +required. + +Only non-virtual properties work as part of queries and for field selection. +Since virtuals are not stored in MongoDB, you can't query with them. + +You can [learn more about virtuals here](https://masteringjs.io/tutorials/mongoose/virtuals). + +

    Aliases

    + +Aliases are a particular type of virtual where the getter and setter +seamlessly get and set another property. This is handy for saving network +bandwidth, so you can convert a short property name stored in the database +into a longer name for code readability. + +```javascript +const personSchema = new Schema({ + n: { + type: String, + // Now accessing `name` will get you the value of `n`, and setting `name` will set the value of `n` + alias: 'name' + } +}); + +// Setting `name` will propagate to `n` +const person = new Person({ name: 'Val' }); +console.log(person); // { n: 'Val' } +console.log(person.toObject({ virtuals: true })); // { n: 'Val', name: 'Val' } +console.log(person.name); // "Val" + +person.name = 'Not Val'; +console.log(person); // { n: 'Not Val' } +``` + +You can also declare aliases on nested paths. It is easier to use nested +schemas and [subdocuments](/docs/subdocs.html), but you can also declare +nested path aliases inline as long as you use the full nested path +`nested.myProp` as the alias. + +```javascript +[require:gh-6671] +``` + +

    Options

    + +Schemas have a few configurable options which can be passed to the +constructor or to the `set` method: + +```javascript +new Schema({..}, options); + +// or + +const schema = new Schema({..}); +schema.set(option, value); +``` + +Valid options: + +- [autoIndex](#autoIndex) +- [autoCreate](#autoCreate) +- [bufferCommands](#bufferCommands) +- [bufferTimeoutMS](#bufferTimeoutMS) +- [capped](#capped) +- [collection](#collection) +- [discriminatorKey](#discriminatorKey) +- [id](#id) +- [_id](#_id) +- [minimize](#minimize) +- [read](#read) +- [writeConcern](#writeConcern) +- [shardKey](#shardKey) +- [strict](#strict) +- [strictQuery](#strictQuery) +- [toJSON](#toJSON) +- [toObject](#toObject) +- [typeKey](#typeKey) +- [typePojoToMixed](/docs/schematypes.html#mixed) +- [useNestedStrict](#useNestedStrict) +- [validateBeforeSave](#validateBeforeSave) +- [versionKey](#versionKey) +- [optimisticConcurrency](#optimisticConcurrency) +- [collation](#collation) +- [selectPopulatedPaths](#selectPopulatedPaths) +- [skipVersioning](#skipVersioning) +- [timestamps](#timestamps) +- [storeSubdocValidationError](#storeSubdocValidationError) + +

    option: autoIndex

    + +By default, Mongoose's [`init()` function](/docs/api.html#model_Model.init) +creates all the indexes defined in your model's schema by calling +[`Model.createIndexes()`](/docs/api.html#model_Model.createIndexes) +after you successfully connect to MongoDB. Creating indexes automatically is +great for development and test environments. But index builds can also create +significant load on your production database. If you want to manage indexes +carefully in production, you can set `autoIndex` to false. + +```javascript +const schema = new Schema({..}, { autoIndex: false }); +const Clock = mongoose.model('Clock', schema); +Clock.ensureIndexes(callback); +``` + +The `autoIndex` option is set to `true` by default. You can change this +default by setting [`mongoose.set('autoIndex', false);`](/docs/api/mongoose.html#mongoose_Mongoose-set) + +

    option: autoCreate

    + +Before Mongoose builds indexes, it calls `Model.createCollection()` +to create the underlying collection in MongoDB if `autoCreate` is set to true. +Calling `createCollection()` +sets the [collection's default collation](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations) +based on the [collation option](#collation) and establishes the collection as +a capped collection if you set the [`capped` schema option](#capped). Like +`autoIndex`, setting `autoCreate` to true is helpful for development and +test environments. + +Unfortunately, `createCollection()` cannot change an existing collection. +For example, if you add `capped: 1024` to your schema and the existing +collection is not capped, `createCollection()` will throw an error. +Generally, `autoCreate` should be `false` for production environments. + +```javascript +const schema = new Schema({..}, { autoCreate: true, capped: 1024 }); +const Clock = mongoose.model('Clock', schema); +// Mongoose will create the capped collection for you. +``` + +Unlike `autoIndex`, `autoCreate` is `false` by default. You can change this +default by setting [`mongoose.set('autoCreate', true);`](/docs/api/mongoose.html#mongoose_Mongoose-set) + +

    option: bufferCommands

    + +By default, mongoose buffers commands when the connection goes down until +the driver manages to reconnect. To disable buffering, set `bufferCommands` +to false. + +```javascript +const schema = new Schema({..}, { bufferCommands: false }); +``` + +The schema `bufferCommands` option overrides the global `bufferCommands` option. + +```javascript +mongoose.set('bufferCommands', true); +// Schema option below overrides the above, if the schema option is set. +const schema = new Schema({..}, { bufferCommands: false }); +``` + +

    option: 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). + +```javascript +// If an operation is buffered for more than 1 second, throw an error. +const schema = new Schema({..}, { bufferTimeoutMS: 1000 }); +``` + +

    option: capped

    + +Mongoose supports MongoDBs [capped](http://www.mongodb.org/display/DOCS/Capped+Collections) +collections. To specify the underlying MongoDB collection be `capped`, set +the `capped` option to the maximum size of the collection in +[bytes](http://www.mongodb.org/display/DOCS/Capped+Collections#CappedCollections-size.). + +```javascript +new Schema({..}, { capped: 1024 }); +``` + +The `capped` option may also be set to an object if you want to pass +additional options like [max](http://www.mongodb.org/display/DOCS/Capped+Collections#CappedCollections-max) +or [autoIndexId](http://www.mongodb.org/display/DOCS/Capped+Collections#CappedCollections-autoIndexId). +In this case you must explicitly pass the `size` option, which is required. + +```javascript +new Schema({..}, { capped: { size: 1024, max: 1000, autoIndexId: true } }); +``` + +

    option: collection

    + +Mongoose by default produces a collection name by passing the model name to +the [utils.toCollectionName](./api.html#utils_exports.toCollectionName) method. +This method pluralizes the name. Set this option if you need a different name +for your collection. + +```javascript +const dataSchema = new Schema({..}, { collection: 'data' }); +``` + +

    option: discriminatorKey

    + +When you define a [discriminator](/docs/discriminators.html), Mongoose adds a path to your +schema that stores which discriminator a document is an instance of. By default, Mongoose +adds an `__t` path, but you can set `discriminatorKey` to overwrite this default. + +```javascript +const baseSchema = new Schema({}, { discriminatorKey: 'type' }); +const BaseModel = mongoose.model('Test', baseSchema); + +const personSchema = new Schema({ name: String }); +const PersonModel = BaseModel.discriminator('Person', personSchema); + +const doc = new PersonModel({ name: 'James T. Kirk' }); +// Without `discriminatorKey`, Mongoose would store the discriminator +// key in `__t` instead of `type` +doc.type; // 'Person' +``` + +

    option: id

    + +Mongoose assigns each of your schemas an `id` virtual getter by default +which returns the document's `_id` field cast to a string, or in the case of +ObjectIds, its hexString. If you don't want an `id` getter added to your +schema, you may disable it by passing this option at schema construction time. + +```javascript +// default behavior +const schema = new Schema({ name: String }); +const Page = mongoose.model('Page', schema); +const p = new Page({ name: 'mongodb.org' }); +console.log(p.id); // '50341373e894ad16347efe01' + +// disabled id +const schema = new Schema({ name: String }, { id: false }); +const Page = mongoose.model('Page', schema); +const p = new Page({ name: 'mongodb.org' }); +console.log(p.id); // undefined +``` + +

    option: _id

    + +Mongoose assigns each of your schemas an `_id` field by default if one +is not passed into the [Schema](/docs/api.html#schema-js) constructor. +The type assigned is an [ObjectId](/docs/api.html#schema_Schema.Types) +to coincide with MongoDB's default behavior. If you don't want an `_id` +added to your schema at all, you may disable it using this option. + +You can **only** use this option on subdocuments. Mongoose can't +save a document without knowing its id, so you will get an error if +you try to save a document without an `_id`. + +```javascript +// default behavior +const schema = new Schema({ name: String }); +const Page = mongoose.model('Page', schema); +const p = new Page({ name: 'mongodb.org' }); +console.log(p); // { _id: '50341373e894ad16347efe01', name: 'mongodb.org' } + +// disabled _id +const childSchema = new Schema({ name: String }, { _id: false }); +const parentSchema = new Schema({ children: [childSchema] }); + +const Model = mongoose.model('Model', parentSchema); + +Model.create({ children: [{ name: 'Luke' }] }, (error, doc) => { + // doc.children[0]._id will be undefined +}); +``` + +

    option: minimize

    + +Mongoose will, by default, "minimize" schemas by removing empty objects. + +```javascript +const schema = new Schema({ name: String, inventory: {} }); +const Character = mongoose.model('Character', schema); + +// will store `inventory` field if it is not empty +const frodo = new Character({ name: 'Frodo', inventory: { ringOfPower: 1 }}); +await frodo.save(); +let doc = await Character.findOne({ name: 'Frodo' }).lean(); +doc.inventory; // { ringOfPower: 1 } + +// will not store `inventory` field if it is empty +const sam = new Character({ name: 'Sam', inventory: {}}); +await sam.save(); +doc = await Character.findOne({ name: 'Sam' }).lean(); +doc.inventory; // undefined +``` + +This behavior can be overridden by setting `minimize` option to `false`. It +will then store empty objects. + +```javascript +const schema = new Schema({ name: String, inventory: {} }, { minimize: false }); +const Character = mongoose.model('Character', schema); + +// will store `inventory` if empty +const sam = new Character({ name: 'Sam', inventory: {} }); +await sam.save(); +doc = await Character.findOne({ name: 'Sam' }).lean(); +doc.inventory; // {} +``` + +To check whether an object is empty, you can use the `$isEmpty()` helper: + +```javascript +const sam = new Character({ name: 'Sam', inventory: {} }); +sam.$isEmpty('inventory'); // true + +sam.inventory.barrowBlade = 1; +sam.$isEmpty('inventory'); // false +``` + +

    option: read

    + +Allows setting [query#read](/docs/api.html#query_Query-read) options at the +schema level, providing us a way to apply default +[ReadPreferences](http://docs.mongodb.org/manual/applications/replication/#replica-set-read-preference) +to all queries derived from a model. + +```javascript +const schema = new Schema({..}, { read: 'primary' }); // also aliased as 'p' +const schema = new Schema({..}, { read: 'primaryPreferred' }); // aliased as 'pp' +const schema = new Schema({..}, { read: 'secondary' }); // aliased as 's' +const schema = new Schema({..}, { read: 'secondaryPreferred' }); // aliased as 'sp' +const schema = new Schema({..}, { read: 'nearest' }); // aliased as 'n' +``` + +The alias of each pref is also permitted so instead of having to type out +'secondaryPreferred' and getting the spelling wrong, we can simply pass 'sp'. + +The read option also allows us to specify _tag sets_. These tell the +[driver](https://github.com/mongodb/node-mongodb-native/) from which members +of the replica-set it should attempt to read. Read more about tag sets +[here](http://docs.mongodb.org/manual/applications/replication/#tag-sets) and +[here](http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences). + +_NOTE: you may also specify the driver read pref [strategy](http://mongodb.github.com/node-mongodb-native/api-generated/replset.html?highlight=strategy) +option when connecting:_ + +```javascript +// pings the replset members periodically to track network latency +const options = { replset: { strategy: 'ping' }}; +mongoose.connect(uri, options); + +const schema = new Schema({..}, { read: ['nearest', { disk: 'ssd' }] }); +mongoose.model('JellyBean', schema); +``` + +

    option: writeConcern

    + +Allows setting [write concern](https://docs.mongodb.com/manual/reference/write-concern/) +at the schema level. + +```javascript +const schema = new Schema({ name: String }, { + writeConcern: { + w: 'majority', + j: true, + wtimeout: 1000 + } +}); +``` + +

    option: shardKey

    + +The `shardKey` option is used when we have a [sharded MongoDB architecture](http://www.mongodb.org/display/DOCS/Sharding+Introduction). +Each sharded collection is given a shard key which must be present in all +insert/update operations. We just need to set this schema option to the same +shard key and we’ll be all set. + +```javascript +new Schema({ .. }, { shardKey: { tag: 1, name: 1 }}) +``` + +_Note that Mongoose does not send the `shardcollection` command for you. You +must configure your shards yourself._ + +

    option: strict

    + +The strict option, (enabled by default), ensures that values passed to our +model constructor that were not specified in our schema do not get saved to +the db. + +```javascript +const thingSchema = new Schema({..}) +const Thing = mongoose.model('Thing', thingSchema); +const thing = new Thing({ iAmNotInTheSchema: true }); +thing.save(); // iAmNotInTheSchema is not saved to the db + +// set to false.. +const thingSchema = new Schema({..}, { strict: false }); +const thing = new Thing({ iAmNotInTheSchema: true }); +thing.save(); // iAmNotInTheSchema is now saved to the db!! +``` + +This also affects the use of `doc.set()` to set a property value. + +```javascript +const thingSchema = new Schema({..}) +const Thing = mongoose.model('Thing', thingSchema); +const thing = new Thing; +thing.set('iAmNotInTheSchema', true); +thing.save(); // iAmNotInTheSchema is not saved to the db +``` + +This value can be overridden at the model instance level by passing a second +boolean argument: + +```javascript +const Thing = mongoose.model('Thing'); +const thing = new Thing(doc, true); // enables strict mode +const thing = new Thing(doc, false); // disables strict mode +``` + +The `strict` option may also be set to `"throw"` which will cause errors +to be produced instead of dropping the bad data. + +_NOTE: Any key/val set on the instance that does not exist in your schema +is always ignored, regardless of schema option._ + +```javascript +const thingSchema = new Schema({..}) +const Thing = mongoose.model('Thing', thingSchema); +const thing = new Thing; +thing.iAmNotInTheSchema = true; +thing.save(); // iAmNotInTheSchema is never saved to the db +``` + +

    option: strictQuery

    + +For backwards compatibility, the `strict` option does **not** apply to +the `filter` parameter for queries. + +```javascript +const mySchema = new Schema({ field: Number }, { strict: true }); +const MyModel = mongoose.model('Test', mySchema); + +// Mongoose will **not** filter out `notInSchema: 1`, despite `strict: true` +MyModel.find({ notInSchema: 1 }); +``` + +The `strict` option does apply to updates. + +```javascript +// Mongoose will strip out `notInSchema` from the update if `strict` is +// not `false` +MyModel.updateMany({}, { $set: { notInSchema: 1 } }); +``` + +Mongoose has a separate `strictQuery` option to toggle strict mode for +the `filter` parameter to queries. + +```javascript +const mySchema = new Schema({ field: Number }, { + strict: true, + strictQuery: true // Turn on strict mode for query filters +}); +const MyModel = mongoose.model('Test', mySchema); + +// Mongoose will strip out `notInSchema: 1` because `strictQuery` is `true` +MyModel.find({ notInSchema: 1 }); +``` + +

    option: toJSON

    + +Exactly the same as the [toObject](#toObject) option but only applies when +the document's [`toJSON` method](https://thecodebarbarian.com/what-is-the-tojson-function-in-javascript.html) is called. + +```javascript +const schema = new Schema({ name: String }); +schema.path('name').get(function (v) { + return v + ' is my name'; +}); +schema.set('toJSON', { getters: true, virtuals: false }); +const M = mongoose.model('Person', schema); +const m = new M({ name: 'Max Headroom' }); +console.log(m.toObject()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom' } +console.log(m.toJSON()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' } +// since we know toJSON is called whenever a js object is stringified: +console.log(JSON.stringify(m)); // { "_id": "504e0cd7dd992d9be2f20b6f", "name": "Max Headroom is my name" } +``` + +To see all available `toJSON/toObject` options, read [this](/docs/api.html#document_Document-toObject). + +

    option: toObject

    + +Documents have a [toObject](/docs/api.html#document_Document-toObject) method +which converts the mongoose document into a plain JavaScript object. This +method accepts a few options. Instead of applying these options on a +per-document basis, we may declare the options at the schema level and have +them applied to all of the schema's documents by default. + +To have all virtuals show up in your `console.log` output, set the +`toObject` option to `{ getters: true }`: + +```javascript +const schema = new Schema({ name: String }); +schema.path('name').get(function(v) { + return v + ' is my name'; +}); +schema.set('toObject', { getters: true }); +const M = mongoose.model('Person', schema); +const m = new M({ name: 'Max Headroom' }); +console.log(m); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' } +``` + +To see all available `toObject` options, read [this](/docs/api.html#document_Document-toObject). + +

    option: typeKey

    + +By default, if you have an object with key 'type' in your schema, mongoose +will interpret it as a type declaration. + +```javascript +// Mongoose interprets this as 'loc is a String' +const schema = new Schema({ loc: { type: String, coordinates: [Number] } }); +``` + +However, for applications like [geoJSON](http://docs.mongodb.org/manual/reference/geojson/), +the 'type' property is important. If you want to control which key mongoose +uses to find type declarations, set the 'typeKey' schema option. + +```javascript +const schema = new Schema({ + // Mongoose interpets this as 'loc is an object with 2 keys, type and coordinates' + loc: { type: String, coordinates: [Number] }, + // Mongoose interprets this as 'name is a String' + name: { $type: String } +}, { typeKey: '$type' }); // A '$type' key means this object is a type declaration +``` + +

    option: validateBeforeSave

    + +By default, documents are automatically validated before they are saved to +the database. This is to prevent saving an invalid document. If you want to +handle validation manually, and be able to save objects which don't pass +validation, you can set `validateBeforeSave` to false. + +```javascript +const schema = new Schema({ name: String }); +schema.set('validateBeforeSave', false); +schema.path('name').validate(function (value) { + return value != null; +}); +const M = mongoose.model('Person', schema); +const m = new M({ name: null }); +m.validate(function(err) { + console.log(err); // Will tell you that null is not allowed. +}); +m.save(); // Succeeds despite being invalid +``` + +

    option: versionKey

    + +The `versionKey` is a property set on each document when first created by +Mongoose. This keys value contains the internal +[revision](http://aaronheckmann.blogspot.com/2012/06/mongoose-v3-part-1-versioning.html) +of the document. The `versionKey` option is a string that represents the +path to use for versioning. The default is `__v`. If this conflicts with +your application you can configure as such: + +```javascript +const schema = new Schema({ name: 'string' }); +const Thing = mongoose.model('Thing', schema); +const thing = new Thing({ name: 'mongoose v3' }); +await thing.save(); // { __v: 0, name: 'mongoose v3' } + +// customized versionKey +new Schema({..}, { versionKey: '_somethingElse' }) +const Thing = mongoose.model('Thing', schema); +const thing = new Thing({ name: 'mongoose v3' }); +thing.save(); // { _somethingElse: 0, name: 'mongoose v3' } +``` + +Note that Mongoose's default versioning is **not** a full [optimistic concurrency](https://en.wikipedia.org/wiki/Optimistic_concurrency_control) +solution. Mongoose's default versioning only operates on arrays as shown below. + +```javascript +// 2 copies of the same document +const doc1 = await Model.findOne({ _id }); +const doc2 = await Model.findOne({ _id }); + +// Delete first 3 comments from `doc1` +doc1.comments.splice(0, 3); +await doc1.save(); + +// The below `save()` will throw a VersionError, because you're trying to +// modify the comment at index 1, and the above `splice()` removed that +// comment. +doc2.set('comments.1.body', 'new comment'); +await doc2.save(); +``` + +If you need optimistic concurrency support for `save()`, you can set the [`optimisticConcurrency` option](#optimisticConcurrency) + +Document versioning can also be disabled by setting the `versionKey` to +`false`. +_DO NOT disable versioning unless you [know what you are doing](http://aaronheckmann.blogspot.com/2012/06/mongoose-v3-part-1-versioning.html)._ + +```javascript +new Schema({..}, { versionKey: false }); +const Thing = mongoose.model('Thing', schema); +const thing = new Thing({ name: 'no versioning please' }); +thing.save(); // { name: 'no versioning please' } +``` + +Mongoose _only_ updates the version key when you use [`save()`](/docs/api.html#document_Document-save). +If you use `update()`, `findOneAndUpdate()`, etc. Mongoose will **not** +update the version key. As a workaround, you can use the below middleware. + +```javascript +schema.pre('findOneAndUpdate', function() { + const update = this.getUpdate(); + if (update.__v != null) { + delete update.__v; + } + const keys = ['$set', '$setOnInsert']; + for (const key of keys) { + if (update[key] != null && update[key].__v != null) { + delete update[key].__v; + if (Object.keys(update[key]).length === 0) { + delete update[key]; + } + } + } + update.$inc = update.$inc || {}; + update.$inc.__v = 1; +}); +``` + +

    option: optimisticConcurrency

    + +[Optimistic concurrency](https://en.wikipedia.org/wiki/Optimistic_concurrency_control) is a strategy to ensure +the document you're updating didn't change between when you loaded it using `find()` or `findOne()`, and when +you update it using `save()`. + +For example, suppose you have a `House` model that contains a list of `photos`, and a `status` that represents +whether this house shows up in searches. Suppose that a house that has status `'APPROVED'` must have at least +two `photos`. You might implement the logic of approving a house document as shown below: + +```javascript +async function markApproved(id) { + const house = await House.findOne({ _id }); + if (house.photos.length < 2) { + throw new Error('House must have at least two photos!'); + } + + house.status = 'APPROVED'; + await house.save(); +} +``` + +The `markApproved()` function looks right in isolation, but there might be a potential issue: what if another +function removes the house's photos between the `findOne()` call and the `save()` call? For example, the below +code will succeed: + +```javascript +const house = await House.findOne({ _id }); +if (house.photos.length < 2) { + throw new Error('House must have at least two photos!'); +} + +const house2 = await House.findOne({ _id }); +house2.photos = []; +await house2.save(); + +// Marks the house as 'APPROVED' even though it has 0 photos! +house.status = 'APPROVED'; +await house.save(); +``` + +If you set the `optimisticConcurrency` option on the `House` model's schema, the above script will throw an +error. + +```javascript +const House = mongoose.model('House', Schema({ + status: String, + photos: [String] +}, { optimisticConcurrency: true })); + +const house = await House.findOne({ _id }); +if (house.photos.length < 2) { + throw new Error('House must have at least two photos!'); +} + +const house2 = await House.findOne({ _id }); +house2.photos = []; +await house2.save(); + +// Throws 'VersionError: No matching document found for id "..." version 0' +house.status = 'APPROVED'; +await house.save(); +``` + +

    option: collation

    + +Sets a default [collation](https://docs.mongodb.com/manual/reference/collation/) +for every query and aggregation. [Here's a beginner-friendly overview of collations](http://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations). + +```javascript +const schema = new Schema({ + name: String +}, { collation: { locale: 'en_US', strength: 1 } }); + +const MyModel = db.model('MyModel', schema); + +MyModel.create([{ name: 'val' }, { name: 'Val' }]). + then(() => { + return MyModel.find({ name: 'val' }); + }). + then((docs) => { + // `docs` will contain both docs, because `strength: 1` means + // MongoDB will ignore case when matching. + }); +``` + +

    option: skipVersioning

    + +`skipVersioning` allows excluding paths from versioning (i.e., the internal +revision will not be incremented even if these paths are updated). DO NOT +do this unless you know what you're doing. For subdocuments, include this +on the parent document using the fully qualified path. + +```javascript +new Schema({..}, { skipVersioning: { dontVersionMe: true } }); +thing.dontVersionMe.push('hey'); +thing.save(); // version is not incremented +``` + +

    option: timestamps

    + +The `timestamps` option tells mongoose to assign `createdAt` and `updatedAt` fields +to your schema. The type assigned is [Date](./api.html#schema-date-js). + +By default, the names of the fields are `createdAt` and `updatedAt`. Customize +the field names by setting `timestamps.createdAt` and `timestamps.updatedAt`. + +```javascript +const thingSchema = new Schema({..}, { timestamps: { createdAt: 'created_at' } }); +const Thing = mongoose.model('Thing', thingSchema); +const thing = new Thing(); +await thing.save(); // `created_at` & `updatedAt` will be included + +// With updates, Mongoose will add `updatedAt` to `$set` +await Thing.updateOne({}, { $set: { name: 'Test' } }); + +// If you set upsert: true, Mongoose will add `created_at` to `$setOnInsert` as well +await Thing.findOneAndUpdate({}, { $set: { name: 'Test2' } }); + +// Mongoose also adds timestamps to bulkWrite() operations +// See https://mongoosejs.com/docs/api.html#model_Model.bulkWrite +await Thing.bulkWrite([ + insertOne: { + document: { + name: 'Jean-Luc Picard', + ship: 'USS Stargazer' + // Mongoose will add `created_at` and `updatedAt` + } + }, + updateOne: { + filter: { name: 'Jean-Luc Picard' }, + update: { + $set: { + ship: 'USS Enterprise' + // Mongoose will add `updatedAt` + } + } + } +]); +``` + +By default, Mongoose uses `new Date()` to get the current time. +If you want to overwrite the function +Mongoose uses to get the current time, you can set the +`timestamps.currentTime` option. Mongoose will call the +`timestamps.currentTime` function whenever it needs to get +the current time. + +```javascript +const schema = Schema({ + createdAt: Number, + updatedAt: Number, + name: String +}, { + // Make Mongoose use Unix time (seconds since Jan 1, 1970) + timestamps: { currentTime: () => Math.floor(Date.now() / 1000) } +}); +``` + +

    option: useNestedStrict

    + +Write operations like `update()`, `updateOne()`, `updateMany()`, +and `findOneAndUpdate()` only check the top-level +schema's strict mode setting. + +```javascript +const childSchema = new Schema({}, { strict: false }); +const parentSchema = new Schema({ child: childSchema }, { strict: 'throw' }); +const Parent = mongoose.model('Parent', parentSchema); +Parent.update({}, { 'child.name': 'Luke Skywalker' }, (error) => { + // Error because parentSchema has `strict: throw`, even though + // `childSchema` has `strict: false` +}); + +const update = { 'child.name': 'Luke Skywalker' }; +const opts = { strict: false }; +Parent.update({}, update, opts, function(error) { + // This works because passing `strict: false` to `update()` overwrites + // the parent schema. +}); +``` + +If you set `useNestedStrict` to true, mongoose will use the child schema's +`strict` option for casting updates. + +```javascript +const childSchema = new Schema({}, { strict: false }); +const parentSchema = new Schema({ child: childSchema }, + { strict: 'throw', useNestedStrict: true }); +const Parent = mongoose.model('Parent', parentSchema); +Parent.update({}, { 'child.name': 'Luke Skywalker' }, error => { + // Works! +}); +``` + +

    + + option: selectPopulatedPaths + +

    + +By default, Mongoose will automatically `select()` any populated paths for +you, unless you explicitly exclude them. + +```javascript +const bookSchema = new Schema({ + title: 'String', + author: { type: 'ObjectId', ref: 'Person' } +}); +const Book = mongoose.model('Book', bookSchema); + +// By default, Mongoose will add `author` to the below `select()`. +await Book.find().select('title').populate('author'); + +// In other words, the below query is equivalent to the above +await Book.find().select('title author').populate('author'); +``` + +To opt out of selecting populated fields by default, set `selectPopulatedPaths` +to `false` in your schema. + +```javascript +const bookSchema = new Schema({ + title: 'String', + author: { type: 'ObjectId', ref: 'Person' } +}, { selectPopulatedPaths: false }); +const Book = mongoose.model('Book', bookSchema); + +// Because `selectPopulatedPaths` is false, the below doc will **not** +// contain an `author` property. +const doc = await Book.findOne().select('title').populate('author'); +``` + +

    + + option: storeSubdocValidationError + +

    + +For legacy reasons, when there is a validation error in subpath of a +single nested schema, Mongoose will record that there was a validation error +in the single nested schema path as well. For example: + +```javascript +const childSchema = new Schema({ name: { type: String, required: true } }); +const parentSchema = new Schema({ child: childSchema }); + +const Parent = mongoose.model('Parent', parentSchema); + +// Will contain an error for both 'child.name' _and_ 'child' +new Parent({ child: {} }).validateSync().errors; +``` + +Set the `storeSubdocValidationError` to `false` on the child schema to make +Mongoose only reports the parent error. + +```javascript +const childSchema = new Schema({ + name: { type: String, required: true } +}, { storeSubdocValidationError: false }); // <-- set on the child schema +const parentSchema = new Schema({ child: childSchema }); + +const Parent = mongoose.model('Parent', parentSchema); + +// Will only contain an error for 'child.name' +new Parent({ child: {} }).validateSync().errors; +``` + +

    With ES6 Classes

    + +Schemas have a [`loadClass()` method](/docs/api/schema.html#schema_Schema-loadClass) +that you can use to create a Mongoose schema from an [ES6 class](https://thecodebarbarian.com/an-overview-of-es6-classes): + +* [ES6 class methods](https://masteringjs.io/tutorials/fundamentals/class#methods) become [Mongoose methods](/docs/guide.html#methods) +* [ES6 class statics](https://masteringjs.io/tutorials/fundamentals/class#statics) become [Mongoose statics](/docs/guide.html#statics) +* [ES6 getters and setters](https://masteringjs.io/tutorials/fundamentals/class#getterssetters) become [Mongoose virtuals](/docs/tutorials/virtuals.html) + +Here's an example of using `loadClass()` to create a schema from an ES6 class: + +```javascript +class MyClass { + myMethod() { return 42; } + static myStatic() { return 42; } + get myVirtual() { return 42; } +} + +const schema = new mongoose.Schema(); +schema.loadClass(MyClass); + +console.log(schema.methods); // { myMethod: [Function: myMethod] } +console.log(schema.statics); // { myStatic: [Function: myStatic] } +console.log(schema.virtuals); // { myVirtual: VirtualType { ... } } +``` + +

    Pluggable

    + +Schemas are also [pluggable](./plugins.html) which allows us to package up reusable features into +plugins that can be shared with the community or just between your projects. + +

    Further Reading

    + +Here's an [alternative introduction to Mongoose schemas](https://masteringjs.io/tutorials/mongoose/schema). + +To get the most out of MongoDB, you need to learn the basics of MongoDB schema design. +SQL schema design (third normal form) was designed to [minimize storage costs](https://en.wikipedia.org/wiki/Third_normal_form), +whereas MongoDB schema design is about making common queries as fast as possible. +The [_6 Rules of Thumb for MongoDB Schema Design_ blog series](https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design-part-1) +is an excellent resource for learning the basic rules for making your queries +fast. + +Users looking to master MongoDB schema design in Node.js should look into +[_The Little MongoDB Schema Design Book_](http://bit.ly/mongodb-schema-design) +by Christian Kvalheim, the original author of the [MongoDB Node.js driver](http://npmjs.com/package/mongodb). +This book shows you how to implement performant schemas for a laundry list +of use cases, including e-commerce, wikis, and appointment bookings. + +

    Next Up

    + +Now that we've covered `Schemas`, let's take a look at [SchemaTypes](/docs/schematypes.html). diff --git a/docs/guide.pug b/docs/guide.pug deleted file mode 100644 index 1695fb55869..00000000000 --- a/docs/guide.pug +++ /dev/null @@ -1,1304 +0,0 @@ -extends layout - -append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown - ## Schemas - - - - - - .important - :markdown - If you haven't yet done so, please take a minute to read the [quickstart](./index.html) to get an idea of how Mongoose works. - If you are migrating from 4.x to 5.x please take a moment to read the [migration guide](/docs/migrating_to_5.html). - :markdown - - -

    Defining your schema

    - - Everything in Mongoose starts with a Schema. Each schema maps to a MongoDB - collection and defines the shape of the documents within that collection. - - ```javascript - import mongoose from 'mongoose'; - const { Schema } = mongoose; - - const blogSchema = new Schema({ - title: String, // String is shorthand for {type: String} - author: String, - body: String, - comments: [{ body: String, date: Date }], - date: { type: Date, default: Date.now }, - hidden: Boolean, - meta: { - votes: Number, - favs: Number - } - }); - ``` - - If you want to add additional keys later, use the - [Schema#add](./api.html#schema_Schema-add) method. - - Each key in our code `blogSchema` defines a property in our documents which - will be cast to its associated [SchemaType](./api.html#schematype_SchemaType). - For example, we've defined a property `title` which will be cast to the - [String](./api.html#schema-string-js) SchemaType and property `date` - which will be cast to a `Date` SchemaType. - - Notice above that if a property only requires a type, it can be specified using - a shorthand notation (contrast the `title` property above with the `date` - property). - - Keys may also be assigned nested objects containing further key/type definitions - like the `meta` property above. This will happen whenever a key's value is a POJO - that doesn't have a `type` property. - - In these cases, Mongoose only creates actual schema paths for leaves - in the tree. (like `meta.votes` and `meta.favs` above), - and the branches do not have actual paths. A side-effect of this is that `meta` - above cannot have its own validation. If validation is needed up the tree, a path - needs to be created up the tree - see the [Subdocuments](./subdocs.html) section - for more information on how to do this. Also read the [Mixed](./schematypes.html) - subsection of the SchemaTypes guide for some gotchas. - - The permitted SchemaTypes are: - - * [String](./schematypes.html#strings) - * [Number](./schematypes.html#numbers) - * [Date](./schematypes.html#dates) - * [Buffer](./schematypes.html#buffers) - * [Boolean](./schematypes.html#booleans) - * [Mixed](./schematypes.html#mixed) - * [ObjectId](./schematypes.html#objectids) - * [Array](./schematypes.html#arrays) - * [Decimal128](./api.html#mongoose_Mongoose-Decimal128) - * [Map](./schematypes.html#maps) - - Read more about [SchemaTypes here](./schematypes.html). - - Schemas not only define the structure of your document and casting of - properties, they also define document [instance methods](#methods), - [static Model methods](#statics), [compound indexes](#indexes), - and document lifecycle hooks called [middleware](./middleware.html). - -

    Creating a model

    - - To use our schema definition, we need to convert our `blogSchema` into a - [Model](./models.html) we can work with. - To do so, we pass it into `mongoose.model(modelName, schema)`: - - ```javascript - const Blog = mongoose.model('Blog', blogSchema); - // ready to go! - ``` - -

    Ids

    - - By default, Mongoose adds an `_id` property to your schemas. - - ```javascript - const schema = new Schema(); - - schema.path('_id'); // ObjectId { ... } - ``` - - When you create a new document with the automatically added - `_id` property, Mongoose creates a new [`_id` of type ObjectId](https://masteringjs.io/tutorials/mongoose/objectid) - to your document. - - ```javascript - const Model = mongoose.model('Test', schema); - - const doc = new Model(); - doc._id instanceof mongoose.Types.ObjectId; // true - ``` - - You can also overwrite Mongoose's default `_id` with your - own `_id`. Just be careful: Mongoose will refuse to save a - document that doesn't have an `_id`, so you're responsible - for setting `_id` if you define your own `_id` path. - - ```javascript - const schema = new Schema({ _id: Number }); - const Model = mongoose.model('Test', schema); - - const doc = new Model(); - await doc.save(); // Throws "document must have an _id before saving" - - doc._id = 1; - await doc.save(); // works - ``` - -

    Instance methods

    - - Instances of `Models` are [documents](./documents.html). Documents have - many of their own [built-in instance methods](./api/document.html). - We may also define our own custom document instance methods. - - ```javascript - // define a schema - const animalSchema = new Schema({ name: String, type: String }); - - // assign a function to the "methods" object of our animalSchema - animalSchema.methods.findSimilarTypes = function(cb) { - return mongoose.model('Animal').find({ type: this.type }, cb); - }; - ``` - - Now all of our `animal` instances have a `findSimilarTypes` method available - to them. - - ```javascript - const Animal = mongoose.model('Animal', animalSchema); - const dog = new Animal({ type: 'dog' }); - - dog.findSimilarTypes((err, dogs) => { - console.log(dogs); // woof - }); - ``` - - * Overwriting a default mongoose document method may lead to unpredictable results. See [this](./api.html#schema_Schema.reserved) for more details. - * The example above uses the `Schema.methods` object directly to save an instance method. You can also use the `Schema.method()` helper as described [here](./api.html#schema_Schema-method). - * Do **not** declare methods using ES6 arrow functions (`=>`). Arrow functions [explicitly prevent binding `this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions#No_binding_of_this), so your method will **not** have access to the document and the above examples will not work. - -

    Statics

    - - You can also add static functions to your model. There are two equivalent - ways to add a static: - - - Add a function property to `schema.statics` - - Call the [`Schema#static()` function](/docs/api.html#schema_Schema-static) - - ```javascript - // Assign a function to the "statics" object of our animalSchema - animalSchema.statics.findByName = function(name) { - return this.find({ name: new RegExp(name, 'i') }); - }; - // Or, equivalently, you can call `animalSchema.static()`. - animalSchema.static('findByBreed', function(breed) { return this.find({ breed }); }); - - const Animal = mongoose.model('Animal', animalSchema); - let animals = await Animal.findByName('fido'); - animals = animals.concat(await Animal.findByBreed('Poodle')); - ``` - - Do **not** declare statics using ES6 arrow functions (`=>`). Arrow functions [explicitly prevent binding `this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions#No_binding_of_this), so the above examples will not work because of the value of `this`. - -

    Query Helpers

    - - You can also add query helper functions, which are like instance methods - but for mongoose queries. Query helper methods let you extend mongoose's - [chainable query builder API](./queries.html). - - ```javascript - animalSchema.query.byName = function(name) { - return this.where({ name: new RegExp(name, 'i') }) - }; - - const Animal = mongoose.model('Animal', animalSchema); - - Animal.find().byName('fido').exec((err, animals) => { - console.log(animals); - }); - - Animal.findOne().byName('fido').exec((err, animal) => { - console.log(animal); - }); - ``` - -

    Indexes

    - - MongoDB supports [secondary indexes](http://docs.mongodb.org/manual/indexes/). - With mongoose, we define these indexes within our `Schema` [at](./api.html#schematype_SchemaType-index) [the](./api.html#schematype_SchemaType-unique) [path](./api.html#schematype_SchemaType-sparse) [level](./api.html#schema_date_SchemaDate-expires) or the `schema` level. - Defining indexes at the schema level is necessary when creating - [compound indexes](https://docs.mongodb.com/manual/core/index-compound/). - - ```javascript - const animalSchema = new Schema({ - name: String, - type: String, - tags: { type: [String], index: true } // field level - }); - - animalSchema.index({ name: 1, type: -1 }); // schema level - ``` - - When your application starts up, Mongoose automatically calls [`createIndex`](https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/#db.collection.createIndex) for each defined index in your schema. - Mongoose will call `createIndex` for each index sequentially, and emit an 'index' event on the model when all the `createIndex` calls succeeded or when there was an error. - While nice for development, it is recommended this behavior be disabled in production since index creation can cause a [significant performance impact](https://docs.mongodb.com/manual/core/index-creation/#index-build-impact-on-database-performance). - Disable the behavior by setting the `autoIndex` option of your schema to `false`, or globally on the connection by setting the option `autoIndex` to `false`. - - ```javascript - mongoose.connect('mongodb://user:pass@localhost:port/database', { autoIndex: false }); - // or - mongoose.createConnection('mongodb://user:pass@localhost:port/database', { autoIndex: false }); - // or - animalSchema.set('autoIndex', false); - // or - new Schema({..}, { autoIndex: false }); - ``` - - Mongoose will emit an `index` event on the model when indexes are done - building or an error occurred. - - ```javascript - // Will cause an error because mongodb has an _id index by default that - // is not sparse - animalSchema.index({ _id: 1 }, { sparse: true }); - const Animal = mongoose.model('Animal', animalSchema); - - Animal.on('index', error => { - // "_id index cannot be sparse" - console.log(error.message); - }); - ``` - - See also the [Model#ensureIndexes](./api.html#model_Model.ensureIndexes) method. - -

    Virtuals

    - - [Virtuals](./api.html#schema_Schema-virtual) are document properties that - you can get and set but that do not get persisted to MongoDB. The getters - are useful for formatting or combining fields, while setters are useful for - de-composing a single value into multiple values for storage. - - ```javascript - // define a schema - const personSchema = new Schema({ - name: { - first: String, - last: String - } - }); - - // compile our model - const Person = mongoose.model('Person', personSchema); - - // create a document - const axl = new Person({ - name: { first: 'Axl', last: 'Rose' } - }); - ``` - - Suppose you want to print out the person's full name. You could do it yourself: - - ```javascript - console.log(axl.name.first + ' ' + axl.name.last); // Axl Rose - ``` - - But [concatenating](https://masteringjs.io/tutorials/fundamentals/string-concat) the first and - last name every time can get cumbersome. - And what if you want to do some extra processing on the name, like - [removing diacritics](https://www.npmjs.com/package/diacritics)? A - [virtual property getter](./api.html#virtualtype_VirtualType-get) lets you - define a `fullName` property that won't get persisted to MongoDB. - - ```javascript - personSchema.virtual('fullName').get(function() { - return this.name.first + ' ' + this.name.last; - }); - ``` - - Now, mongoose will call your getter function every time you access the - `fullName` property: - - ```javascript - console.log(axl.fullName); // Axl Rose - ``` - - If you use `toJSON()` or `toObject()` mongoose will *not* include virtuals - by default. This includes the output of calling [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) - on a Mongoose document, because [`JSON.stringify()` calls `toJSON()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Description). - Pass `{ virtuals: true }` to either - [`toObject()`](./api.html#document_Document-toObject) or [`toJSON()`](./api.html#document_Document-toJSON). - - You can also add a custom setter to your virtual that will let you set both - first name and last name via the `fullName` virtual. - - ```javascript - personSchema.virtual('fullName'). - get(function() { - return this.name.first + ' ' + this.name.last; - }). - set(function(v) { - this.name.first = v.substr(0, v.indexOf(' ')); - this.name.last = v.substr(v.indexOf(' ') + 1); - }); - - axl.fullName = 'William Rose'; // Now `axl.name.first` is "William" - ``` - - Virtual property setters are applied before other validation. So the example - above would still work even if the `first` and `last` name fields were - required. - - Only non-virtual properties work as part of queries and for field selection. - Since virtuals are not stored in MongoDB, you can't query with them. - - You can [learn more about virtuals here](https://masteringjs.io/tutorials/mongoose/virtuals). - -

    Aliases

    - - Aliases are a particular type of virtual where the getter and setter - seamlessly get and set another property. This is handy for saving network - bandwidth, so you can convert a short property name stored in the database - into a longer name for code readability. - - ```javascript - const personSchema = new Schema({ - n: { - type: String, - // Now accessing `name` will get you the value of `n`, and setting `n` will set the value of `name` - alias: 'name' - } - }); - - // Setting `name` will propagate to `n` - const person = new Person({ name: 'Val' }); - console.log(person); // { n: 'Val' } - console.log(person.toObject({ virtuals: true })); // { n: 'Val', name: 'Val' } - console.log(person.name); // "Val" - - person.name = 'Not Val'; - console.log(person); // { n: 'Not Val' } - ``` - - You can also declare aliases on nested paths. It is easier to use nested - schemas and [subdocuments](/docs/subdocs.html), but you can also declare - nested path aliases inline as long as you use the full nested path - `nested.myProp` as the alias. - - ```javascript - [require:gh-6671] - ``` - -

    Options

    - - Schemas have a few configurable options which can be passed to the - constructor or to the `set` method: - - ```javascript - new Schema({..}, options); - - // or - - const schema = new Schema({..}); - schema.set(option, value); - ``` - - Valid options: - - - [autoIndex](#autoIndex) - - [autoCreate](#autoCreate) - - [bufferCommands](#bufferCommands) - - [bufferTimeoutMS](#bufferTimeoutMS) - - [capped](#capped) - - [collection](#collection) - - [discriminatorKey](#discriminatorKey) - - [id](#id) - - [_id](#_id) - - [minimize](#minimize) - - [read](#read) - - [writeConcern](#writeConcern) - - [shardKey](#shardKey) - - [strict](#strict) - - [strictQuery](#strictQuery) - - [toJSON](#toJSON) - - [toObject](#toObject) - - [typeKey](#typeKey) - - [typePojoToMixed](/docs/schematypes.html#mixed) - - [useNestedStrict](#useNestedStrict) - - [validateBeforeSave](#validateBeforeSave) - - [versionKey](#versionKey) - - [optimisticConcurrency](#optimisticConcurrency) - - [collation](#collation) - - [selectPopulatedPaths](#selectPopulatedPaths) - - [skipVersioning](#skipVersioning) - - [timestamps](#timestamps) - - [storeSubdocValidationError](#storeSubdocValidationError) - -

    option: autoIndex

    - - By default, Mongoose's [`init()` function](/docs/api.html#model_Model.init) - creates all the indexes defined in your model's schema by calling - [`Model.createIndexes()`](/docs/api.html#model_Model.createIndexes) - after you successfully connect to MongoDB. Creating indexes automatically is - great for development and test environments. But index builds can also create - significant load on your production database. If you want to manage indexes - carefully in production, you can set `autoIndex` to false. - - ```javascript - const schema = new Schema({..}, { autoIndex: false }); - const Clock = mongoose.model('Clock', schema); - Clock.ensureIndexes(callback); - ``` - - The `autoIndex` option is set to `true` by default. You can change this - default by setting [`mongoose.set('autoIndex', false);`](/docs/api/mongoose.html#mongoose_Mongoose-set) - -

    option: autoCreate

    - - Before Mongoose builds indexes, it calls `Model.createCollection()` - to create the underlying collection in MongoDB if `autoCreate` is set to true. - Calling `createCollection()` - sets the [collection's default collation](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations) - based on the [collation option](#collation) and establishes the collection as - a capped collection if you set the [`capped` schema option](#capped). Like - `autoIndex`, setting `autoCreate` to true is helpful for development and - test environments. - - Unfortunately, `createCollection()` cannot change an existing collection. - For example, if you add `capped: 1024` to your schema and the existing - collection is not capped, `createCollection()` will throw an error. - Generally, `autoCreate` should be `false` for production environments. - - ```javascript - const schema = new Schema({..}, { autoCreate: true, capped: 1024 }); - const Clock = mongoose.model('Clock', schema); - // Mongoose will create the capped collection for you. - ``` - - Unlike `autoIndex`, `autoCreate` is `false` by default. You can change this - default by setting [`mongoose.set('autoCreate', true);`](/docs/api/mongoose.html#mongoose_Mongoose-set) - -

    option: bufferCommands

    - - By default, mongoose buffers commands when the connection goes down until - the driver manages to reconnect. To disable buffering, set `bufferCommands` - to false. - - ```javascript - const schema = new Schema({..}, { bufferCommands: false }); - ``` - - The schema `bufferCommands` option overrides the global `bufferCommands` option. - - ```javascript - mongoose.set('bufferCommands', true); - // Schema option below overrides the above, if the schema option is set. - const schema = new Schema({..}, { bufferCommands: false }); - ``` - -

    option: 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). - - ```javascript - // If an operation is buffered for more than 1 second, throw an error. - const schema = new Schema({..}, { bufferTimeoutMS: 1000 }); - ``` - -

    option: capped

    - - Mongoose supports MongoDBs [capped](http://www.mongodb.org/display/DOCS/Capped+Collections) - collections. To specify the underlying MongoDB collection be `capped`, set - the `capped` option to the maximum size of the collection in - [bytes](http://www.mongodb.org/display/DOCS/Capped+Collections#CappedCollections-size.). - - ```javascript - new Schema({..}, { capped: 1024 }); - ``` - - The `capped` option may also be set to an object if you want to pass - additional options like [max](http://www.mongodb.org/display/DOCS/Capped+Collections#CappedCollections-max) - or [autoIndexId](http://www.mongodb.org/display/DOCS/Capped+Collections#CappedCollections-autoIndexId). - In this case you must explicitly pass the `size` option, which is required. - - ```javascript - new Schema({..}, { capped: { size: 1024, max: 1000, autoIndexId: true } }); - ``` - -

    option: collection

    - - Mongoose by default produces a collection name by passing the model name to - the [utils.toCollectionName](./api.html#utils_exports.toCollectionName) method. - This method pluralizes the name. Set this option if you need a different name - for your collection. - - ```javascript - const dataSchema = new Schema({..}, { collection: 'data' }); - ``` - -

    option: discriminatorKey

    - - When you define a [discriminator](/docs/discriminators.html), Mongoose adds a path to your - schema that stores which discriminator a document is an instance of. By default, Mongoose - adds an `__t` path, but you can set `discriminatorKey` to overwrite this default. - - ```javascript - const baseSchema = new Schema({}, { discriminatorKey: 'type' }); - const BaseModel = mongoose.model('Test', baseSchema); - - const personSchema = new Schema({ name: String }); - const PersonModel = BaseModel.discriminator('Person', personSchema); - - const doc = new PersonModel({ name: 'James T. Kirk' }); - // Without `discriminatorKey`, Mongoose would store the discriminator - // key in `__t` instead of `type` - doc.type; // 'Person' - ``` - -

    option: id

    - - Mongoose assigns each of your schemas an `id` virtual getter by default - which returns the document's `_id` field cast to a string, or in the case of - ObjectIds, its hexString. If you don't want an `id` getter added to your - schema, you may disable it by passing this option at schema construction time. - - ```javascript - // default behavior - const schema = new Schema({ name: String }); - const Page = mongoose.model('Page', schema); - const p = new Page({ name: 'mongodb.org' }); - console.log(p.id); // '50341373e894ad16347efe01' - - // disabled id - const schema = new Schema({ name: String }, { id: false }); - const Page = mongoose.model('Page', schema); - const p = new Page({ name: 'mongodb.org' }); - console.log(p.id); // undefined - ``` - -

    option: _id

    - - Mongoose assigns each of your schemas an `_id` field by default if one - is not passed into the [Schema](/docs/api.html#schema-js) constructor. - The type assigned is an [ObjectId](/docs/api.html#schema_Schema.Types) - to coincide with MongoDB's default behavior. If you don't want an `_id` - added to your schema at all, you may disable it using this option. - - You can **only** use this option on subdocuments. Mongoose can't - save a document without knowing its id, so you will get an error if - you try to save a document without an `_id`. - - ```javascript - // default behavior - const schema = new Schema({ name: String }); - const Page = mongoose.model('Page', schema); - const p = new Page({ name: 'mongodb.org' }); - console.log(p); // { _id: '50341373e894ad16347efe01', name: 'mongodb.org' } - - // disabled _id - const childSchema = new Schema({ name: String }, { _id: false }); - const parentSchema = new Schema({ children: [childSchema] }); - - const Model = mongoose.model('Model', parentSchema); - - Model.create({ children: [{ name: 'Luke' }] }, (error, doc) => { - // doc.children[0]._id will be undefined - }); - ``` - -

    option: minimize

    - - Mongoose will, by default, "minimize" schemas by removing empty objects. - - ```javascript - const schema = new Schema({ name: String, inventory: {} }); - const Character = mongoose.model('Character', schema); - - // will store `inventory` field if it is not empty - const frodo = new Character({ name: 'Frodo', inventory: { ringOfPower: 1 }}); - await frodo.save(); - let doc = await Character.findOne({ name: 'Frodo' }).lean(); - doc.inventory; // { ringOfPower: 1 } - - // will not store `inventory` field if it is empty - const sam = new Character({ name: 'Sam', inventory: {}}); - await sam.save(); - doc = await Character.findOne({ name: 'Sam' }).lean(); - doc.inventory; // undefined - ``` - - This behavior can be overridden by setting `minimize` option to `false`. It - will then store empty objects. - - ```javascript - const schema = new Schema({ name: String, inventory: {} }, { minimize: false }); - const Character = mongoose.model('Character', schema); - - // will store `inventory` if empty - const sam = new Character({ name: 'Sam', inventory: {} }); - await sam.save(); - doc = await Character.findOne({ name: 'Sam' }).lean(); - doc.inventory; // {} - ``` - - To check whether an object is empty, you can use the `$isEmpty()` helper: - - ```javascript - const sam = new Character({ name: 'Sam', inventory: {} }); - sam.$isEmpty('inventory'); // true - - sam.inventory.barrowBlade = 1; - sam.$isEmpty('inventory'); // false - ``` - -

    option: read

    - - Allows setting [query#read](/docs/api.html#query_Query-read) options at the - schema level, providing us a way to apply default - [ReadPreferences](http://docs.mongodb.org/manual/applications/replication/#replica-set-read-preference) - to all queries derived from a model. - - ```javascript - const schema = new Schema({..}, { read: 'primary' }); // also aliased as 'p' - const schema = new Schema({..}, { read: 'primaryPreferred' }); // aliased as 'pp' - const schema = new Schema({..}, { read: 'secondary' }); // aliased as 's' - const schema = new Schema({..}, { read: 'secondaryPreferred' }); // aliased as 'sp' - const schema = new Schema({..}, { read: 'nearest' }); // aliased as 'n' - ``` - - The alias of each pref is also permitted so instead of having to type out - 'secondaryPreferred' and getting the spelling wrong, we can simply pass 'sp'. - - The read option also allows us to specify _tag sets_. These tell the - [driver](https://github.com/mongodb/node-mongodb-native/) from which members - of the replica-set it should attempt to read. Read more about tag sets - [here](http://docs.mongodb.org/manual/applications/replication/#tag-sets) and - [here](http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences). - - _NOTE: you may also specify the driver read pref [strategy](http://mongodb.github.com/node-mongodb-native/api-generated/replset.html?highlight=strategy) - option when connecting:_ - - ```javascript - // pings the replset members periodically to track network latency - const options = { replset: { strategy: 'ping' }}; - mongoose.connect(uri, options); - - const schema = new Schema({..}, { read: ['nearest', { disk: 'ssd' }] }); - mongoose.model('JellyBean', schema); - ``` - -

    option: writeConcern

    - - Allows setting [write concern](https://docs.mongodb.com/manual/reference/write-concern/) - at the schema level. - - ```javascript - const schema = new Schema({ name: String }, { - writeConcern: { - w: 'majority', - j: true, - wtimeout: 1000 - } - }); - ``` - -

    option: shardKey

    - - The `shardKey` option is used when we have a [sharded MongoDB architecture](http://www.mongodb.org/display/DOCS/Sharding+Introduction). - Each sharded collection is given a shard key which must be present in all - insert/update operations. We just need to set this schema option to the same - shard key and we’ll be all set. - - ```javascript - new Schema({ .. }, { shardKey: { tag: 1, name: 1 }}) - ``` - - _Note that Mongoose does not send the `shardcollection` command for you. You - must configure your shards yourself._ - -

    option: strict

    - - The strict option, (enabled by default), ensures that values passed to our - model constructor that were not specified in our schema do not get saved to - the db. - - ```javascript - const thingSchema = new Schema({..}) - const Thing = mongoose.model('Thing', thingSchema); - const thing = new Thing({ iAmNotInTheSchema: true }); - thing.save(); // iAmNotInTheSchema is not saved to the db - - // set to false.. - const thingSchema = new Schema({..}, { strict: false }); - const thing = new Thing({ iAmNotInTheSchema: true }); - thing.save(); // iAmNotInTheSchema is now saved to the db!! - ``` - - This also affects the use of `doc.set()` to set a property value. - - ```javascript - const thingSchema = new Schema({..}) - const Thing = mongoose.model('Thing', thingSchema); - const thing = new Thing; - thing.set('iAmNotInTheSchema', true); - thing.save(); // iAmNotInTheSchema is not saved to the db - ``` - - This value can be overridden at the model instance level by passing a second - boolean argument: - - ```javascript - const Thing = mongoose.model('Thing'); - const thing = new Thing(doc, true); // enables strict mode - const thing = new Thing(doc, false); // disables strict mode - ``` - - The `strict` option may also be set to `"throw"` which will cause errors - to be produced instead of dropping the bad data. - - _NOTE: Any key/val set on the instance that does not exist in your schema - is always ignored, regardless of schema option._ - - ```javascript - const thingSchema = new Schema({..}) - const Thing = mongoose.model('Thing', thingSchema); - const thing = new Thing; - thing.iAmNotInTheSchema = true; - thing.save(); // iAmNotInTheSchema is never saved to the db - ``` - -

    option: strictQuery

    - - For backwards compatibility, the `strict` option does **not** apply to - the `filter` parameter for queries. - - ```javascript - const mySchema = new Schema({ field: Number }, { strict: true }); - const MyModel = mongoose.model('Test', mySchema); - - // Mongoose will **not** filter out `notInSchema: 1`, despite `strict: true` - MyModel.find({ notInSchema: 1 }); - ``` - - The `strict` option does apply to updates. - - ```javascript - // Mongoose will strip out `notInSchema` from the update if `strict` is - // not `false` - MyModel.updateMany({}, { $set: { notInSchema: 1 } }); - ``` - - Mongoose has a separate `strictQuery` option to toggle strict mode for - the `filter` parameter to queries. - - ```javascript - const mySchema = new Schema({ field: Number }, { - strict: true, - strictQuery: true // Turn on strict mode for query filters - }); - const MyModel = mongoose.model('Test', mySchema); - - // Mongoose will strip out `notInSchema: 1` because `strictQuery` is `true` - MyModel.find({ notInSchema: 1 }); - ``` - -

    option: toJSON

    - - Exactly the same as the [toObject](#toObject) option but only applies when - the document's [`toJSON` method](https://thecodebarbarian.com/what-is-the-tojson-function-in-javascript.html) is called. - - ```javascript - const schema = new Schema({ name: String }); - schema.path('name').get(function (v) { - return v + ' is my name'; - }); - schema.set('toJSON', { getters: true, virtuals: false }); - const M = mongoose.model('Person', schema); - const m = new M({ name: 'Max Headroom' }); - console.log(m.toObject()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom' } - console.log(m.toJSON()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' } - // since we know toJSON is called whenever a js object is stringified: - console.log(JSON.stringify(m)); // { "_id": "504e0cd7dd992d9be2f20b6f", "name": "Max Headroom is my name" } - ``` - - To see all available `toJSON/toObject` options, read [this](/docs/api.html#document_Document-toObject). - -

    option: toObject

    - - Documents have a [toObject](/docs/api.html#document_Document-toObject) method - which converts the mongoose document into a plain JavaScript object. This - method accepts a few options. Instead of applying these options on a - per-document basis, we may declare the options at the schema level and have - them applied to all of the schema's documents by default. - - To have all virtuals show up in your `console.log` output, set the - `toObject` option to `{ getters: true }`: - - ```javascript - const schema = new Schema({ name: String }); - schema.path('name').get(function(v) { - return v + ' is my name'; - }); - schema.set('toObject', { getters: true }); - const M = mongoose.model('Person', schema); - const m = new M({ name: 'Max Headroom' }); - console.log(m); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' } - ``` - - To see all available `toObject` options, read [this](/docs/api.html#document_Document-toObject). - -

    option: typeKey

    - - By default, if you have an object with key 'type' in your schema, mongoose - will interpret it as a type declaration. - - ```javascript - // Mongoose interprets this as 'loc is a String' - const schema = new Schema({ loc: { type: String, coordinates: [Number] } }); - ``` - - However, for applications like [geoJSON](http://docs.mongodb.org/manual/reference/geojson/), - the 'type' property is important. If you want to control which key mongoose - uses to find type declarations, set the 'typeKey' schema option. - - ```javascript - const schema = new Schema({ - // Mongoose interpets this as 'loc is an object with 2 keys, type and coordinates' - loc: { type: String, coordinates: [Number] }, - // Mongoose interprets this as 'name is a String' - name: { $type: String } - }, { typeKey: '$type' }); // A '$type' key means this object is a type declaration - ``` - -

    option: validateBeforeSave

    - - By default, documents are automatically validated before they are saved to - the database. This is to prevent saving an invalid document. If you want to - handle validation manually, and be able to save objects which don't pass - validation, you can set `validateBeforeSave` to false. - - ```javascript - const schema = new Schema({ name: String }); - schema.set('validateBeforeSave', false); - schema.path('name').validate(function (value) { - return value != null; - }); - const M = mongoose.model('Person', schema); - const m = new M({ name: null }); - m.validate(function(err) { - console.log(err); // Will tell you that null is not allowed. - }); - m.save(); // Succeeds despite being invalid - ``` - -

    option: versionKey

    - - The `versionKey` is a property set on each document when first created by - Mongoose. This keys value contains the internal - [revision](http://aaronheckmann.blogspot.com/2012/06/mongoose-v3-part-1-versioning.html) - of the document. The `versionKey` option is a string that represents the - path to use for versioning. The default is `__v`. If this conflicts with - your application you can configure as such: - - ```javascript - const schema = new Schema({ name: 'string' }); - const Thing = mongoose.model('Thing', schema); - const thing = new Thing({ name: 'mongoose v3' }); - await thing.save(); // { __v: 0, name: 'mongoose v3' } - - // customized versionKey - new Schema({..}, { versionKey: '_somethingElse' }) - const Thing = mongoose.model('Thing', schema); - const thing = new Thing({ name: 'mongoose v3' }); - thing.save(); // { _somethingElse: 0, name: 'mongoose v3' } - ``` - - Note that Mongoose's default versioning is **not** a full [optimistic concurrency](https://en.wikipedia.org/wiki/Optimistic_concurrency_control) - solution. Mongoose's default versioning only operates on arrays as shown below. - - ```javascript - // 2 copies of the same document - const doc1 = await Model.findOne({ _id }); - const doc2 = await Model.findOne({ _id }); - - // Delete first 3 comments from `doc1` - doc1.comments.splice(0, 3); - await doc1.save(); - - // The below `save()` will throw a VersionError, because you're trying to - // modify the comment at index 1, and the above `splice()` removed that - // comment. - doc2.set('comments.1.body', 'new comment'); - await doc2.save(); - ``` - - If you need optimistic concurrency support for `save()`, you can set the [`optimisticConcurrency` option](#optimisticConcurrency) - - Document versioning can also be disabled by setting the `versionKey` to - `false`. - _DO NOT disable versioning unless you [know what you are doing](http://aaronheckmann.blogspot.com/2012/06/mongoose-v3-part-1-versioning.html)._ - - ```javascript - new Schema({..}, { versionKey: false }); - const Thing = mongoose.model('Thing', schema); - const thing = new Thing({ name: 'no versioning please' }); - thing.save(); // { name: 'no versioning please' } - ``` - - Mongoose _only_ updates the version key when you use [`save()`](/docs/api.html#document_Document-save). - If you use `update()`, `findOneAndUpdate()`, etc. Mongoose will **not** - update the version key. As a workaround, you can use the below middleware. - - ```javascript - schema.pre('findOneAndUpdate', function() { - const update = this.getUpdate(); - if (update.__v != null) { - delete update.__v; - } - const keys = ['$set', '$setOnInsert']; - for (const key of keys) { - if (update[key] != null && update[key].__v != null) { - delete update[key].__v; - if (Object.keys(update[key]).length === 0) { - delete update[key]; - } - } - } - update.$inc = update.$inc || {}; - update.$inc.__v = 1; - }); - ``` - -

    option: optimisticConcurrency

    - - [Optimistic concurrency](https://en.wikipedia.org/wiki/Optimistic_concurrency_control) is a strategy to ensure - the document you're updating didn't change between when you loaded it using `find()` or `findOne()`, and when - you update it using `save()`. - - For example, suppose you have a `House` model that contains a list of `photos`, and a `status` that represents - whether this house shows up in searches. Suppose that a house that has status `'APPROVED'` must have at least - two `photos`. You might implement the logic of approving a house document as shown below: - - ```javascript - async function markApproved(id) { - const house = await House.findOne({ _id }); - if (house.photos.length < 2) { - throw new Error('House must have at least two photos!'); - } - - house.status = 'APPROVED'; - await house.save(); - } - ``` - - The `markApproved()` function looks right in isolation, but there might be a potential issue: what if another - function removes the house's photos between the `findOne()` call and the `save()` call? For example, the below - code will succeed: - - ```javascript - const house = await House.findOne({ _id }); - if (house.photos.length < 2) { - throw new Error('House must have at least two photos!'); - } - - const house2 = await House.findOne({ _id }); - house2.photos = []; - await house2.save(); - - // Marks the house as 'APPROVED' even though it has 0 photos! - house.status = 'APPROVED'; - await house.save(); - ``` - - If you set the `optimisticConcurrency` option on the `House` model's schema, the above script will throw an - error. - - ```javascript - const House = mongoose.model('House', Schema({ - status: String, - photos: [String] - }, { optimisticConcurrency: true })); - - const house = await House.findOne({ _id }); - if (house.photos.length < 2) { - throw new Error('House must have at least two photos!'); - } - - const house2 = await House.findOne({ _id }); - house2.photos = []; - await house2.save(); - - // Throws 'VersionError: No matching document found for id "..." version 0' - house.status = 'APPROVED'; - await house.save(); - ``` - -

    option: collation

    - - Sets a default [collation](https://docs.mongodb.com/manual/reference/collation/) - for every query and aggregation. [Here's a beginner-friendly overview of collations](http://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations). - - ```javascript - const schema = new Schema({ - name: String - }, { collation: { locale: 'en_US', strength: 1 } }); - - const MyModel = db.model('MyModel', schema); - - MyModel.create([{ name: 'val' }, { name: 'Val' }]). - then(() => { - return MyModel.find({ name: 'val' }); - }). - then((docs) => { - // `docs` will contain both docs, because `strength: 1` means - // MongoDB will ignore case when matching. - }); - ``` - -

    option: skipVersioning

    - - `skipVersioning` allows excluding paths from versioning (i.e., the internal - revision will not be incremented even if these paths are updated). DO NOT - do this unless you know what you're doing. For subdocuments, include this - on the parent document using the fully qualified path. - - ```javascript - new Schema({..}, { skipVersioning: { dontVersionMe: true } }); - thing.dontVersionMe.push('hey'); - thing.save(); // version is not incremented - ``` - -

    option: timestamps

    - - The `timestamps` option tells mongoose to assign `createdAt` and `updatedAt` fields - to your schema. The type assigned is [Date](./api.html#schema-date-js). - - By default, the names of the fields are `createdAt` and `updatedAt`. Customize - the field names by setting `timestamps.createdAt` and `timestamps.updatedAt`. - - ```javascript - const thingSchema = new Schema({..}, { timestamps: { createdAt: 'created_at' } }); - const Thing = mongoose.model('Thing', thingSchema); - const thing = new Thing(); - await thing.save(); // `created_at` & `updatedAt` will be included - - // With updates, Mongoose will add `updatedAt` to `$set` - await Thing.updateOne({}, { $set: { name: 'Test' } }); - - // If you set upsert: true, Mongoose will add `created_at` to `$setOnInsert` as well - await Thing.findOneAndUpdate({}, { $set: { name: 'Test2' } }); - - // Mongoose also adds timestamps to bulkWrite() operations - // See https://mongoosejs.com/docs/api.html#model_Model.bulkWrite - await Thing.bulkWrite([ - insertOne: { - document: { - name: 'Jean-Luc Picard', - ship: 'USS Stargazer' - // Mongoose will add `created_at` and `updatedAt` - } - }, - updateOne: { - filter: { name: 'Jean-Luc Picard' }, - update: { - $set: { - ship: 'USS Enterprise' - // Mongoose will add `updatedAt` - } - } - } - ]); - ``` - - By default, Mongoose uses `new Date()` to get the current time. - If you want to overwrite the function - Mongoose uses to get the current time, you can set the - `timestamps.currentTime` option. Mongoose will call the - `timestamps.currentTime` function whenever it needs to get - the current time. - - ```javascript - const schema = Schema({ - createdAt: Number, - updatedAt: Number, - name: String - }, { - // Make Mongoose use Unix time (seconds since Jan 1, 1970) - timestamps: { currentTime: () => Math.floor(Date.now() / 1000) } - }); - ``` - -

    option: useNestedStrict

    - - Write operations like `update()`, `updateOne()`, `updateMany()`, - and `findOneAndUpdate()` only check the top-level - schema's strict mode setting. - - ```javascript - const childSchema = new Schema({}, { strict: false }); - const parentSchema = new Schema({ child: childSchema }, { strict: 'throw' }); - const Parent = mongoose.model('Parent', parentSchema); - Parent.update({}, { 'child.name': 'Luke Skywalker' }, (error) => { - // Error because parentSchema has `strict: throw`, even though - // `childSchema` has `strict: false` - }); - - const update = { 'child.name': 'Luke Skywalker' }; - const opts = { strict: false }; - Parent.update({}, update, opts, function(error) { - // This works because passing `strict: false` to `update()` overwrites - // the parent schema. - }); - ``` - - If you set `useNestedStrict` to true, mongoose will use the child schema's - `strict` option for casting updates. - - ```javascript - const childSchema = new Schema({}, { strict: false }); - const parentSchema = new Schema({ child: childSchema }, - { strict: 'throw', useNestedStrict: true }); - const Parent = mongoose.model('Parent', parentSchema); - Parent.update({}, { 'child.name': 'Luke Skywalker' }, error => { - // Works! - }); - ``` - -

    - - option: selectPopulatedPaths - -

    - - By default, Mongoose will automatically `select()` any populated paths for - you, unless you explicitly exclude them. - - ```javascript - const bookSchema = new Schema({ - title: 'String', - author: { type: 'ObjectId', ref: 'Person' } - }); - const Book = mongoose.model('Book', bookSchema); - - // By default, Mongoose will add `author` to the below `select()`. - await Book.find().select('title').populate('author'); - - // In other words, the below query is equivalent to the above - await Book.find().select('title author').populate('author'); - ``` - - To opt out of selecting populated fields by default, set `selectPopulatedPaths` - to `false` in your schema. - - ```javascript - const bookSchema = new Schema({ - title: 'String', - author: { type: 'ObjectId', ref: 'Person' } - }, { selectPopulatedPaths: false }); - const Book = mongoose.model('Book', bookSchema); - - // Because `selectPopulatedPaths` is false, the below doc will **not** - // contain an `author` property. - const doc = await Book.findOne().select('title').populate('author'); - ``` - -

    - - option: storeSubdocValidationError - -

    - - For legacy reasons, when there is a validation error in subpath of a - single nested schema, Mongoose will record that there was a validation error - in the single nested schema path as well. For example: - - ```javascript - const childSchema = new Schema({ name: { type: String, required: true } }); - const parentSchema = new Schema({ child: childSchema }); - - const Parent = mongoose.model('Parent', parentSchema); - - // Will contain an error for both 'child.name' _and_ 'child' - new Parent({ child: {} }).validateSync().errors; - ``` - - Set the `storeSubdocValidationError` to `false` on the child schema to make - Mongoose only reports the parent error. - - ```javascript - const childSchema = new Schema({ - name: { type: String, required: true } - }, { storeSubdocValidationError: false }); // <-- set on the child schema - const parentSchema = new Schema({ child: childSchema }); - - const Parent = mongoose.model('Parent', parentSchema); - - // Will only contain an error for 'child.name' - new Parent({ child: {} }).validateSync().errors; - ``` - -

    With ES6 Classes

    - - Schemas have a [`loadClass()` method](/docs/api/schema.html#schema_Schema-loadClass) - that you can use to create a Mongoose schema from an [ES6 class](https://thecodebarbarian.com/an-overview-of-es6-classes): - - * [ES6 class methods](https://masteringjs.io/tutorials/fundamentals/class#methods) become [Mongoose methods](/docs/guide.html#methods) - * [ES6 class statics](https://masteringjs.io/tutorials/fundamentals/class#statics) become [Mongoose statics](/docs/guide.html#statics) - * [ES6 getters and setters](https://masteringjs.io/tutorials/fundamentals/class#getterssetters) become [Mongoose virtuals](/docs/tutorials/virtuals.html) - - Here's an example of using `loadClass()` to create a schema from an ES6 class: - - ```javascript - class MyClass { - myMethod() { return 42; } - static myStatic() { return 42; } - get myVirtual() { return 42; } - } - - const schema = new mongoose.Schema(); - schema.loadClass(MyClass); - - console.log(schema.methods); // { myMethod: [Function: myMethod] } - console.log(schema.statics); // { myStatic: [Function: myStatic] } - console.log(schema.virtuals); // { myVirtual: VirtualType { ... } } - ``` - -

    Pluggable

    - - Schemas are also [pluggable](./plugins.html) which allows us to package up reusable features into - plugins that can be shared with the community or just between your projects. - -

    Further Reading

    - - Here's an [alternative introduction to Mongoose schemas](https://masteringjs.io/tutorials/mongoose/schema). - - To get the most out of MongoDB, you need to learn the basics of MongoDB schema design. - SQL schema design (third normal form) was designed to [minimize storage costs](https://en.wikipedia.org/wiki/Third_normal_form), - whereas MongoDB schema design is about making common queries as fast as possible. - The [_6 Rules of Thumb for MongoDB Schema Design_ blog series](https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design-part-1) - is an excellent resource for learning the basic rules for making your queries - fast. - - Users looking to master MongoDB schema design in Node.js should look into - [_The Little MongoDB Schema Design Book_](http://bit.ly/mongodb-schema-design) - by Christian Kvalheim, the original author of the [MongoDB Node.js driver](http://npmjs.com/package/mongodb). - This book shows you how to implement performant schemas for a laundry list - of use cases, including e-commerce, wikis, and appointment bookings. - -

    Next Up

    - - Now that we've covered `Schemas`, let's take a look at [SchemaTypes](/docs/schematypes.html). diff --git a/docs/guides.html b/docs/guides.html deleted file mode 100644 index 396952ffdc2..00000000000 --- a/docs/guides.html +++ /dev/null @@ -1,101 +0,0 @@ -Mongoose v5.6.0: Schemas

    Guides

    - - - - -

    Mongoose guides provide detailed tutorials on Mongoose's core concepts and -integrating Mongoose with external tools and frameworks.

    -

    Mongoose Core Concepts

    - -

    Integrations

    - -

    Migration Guides

    - -
    \ No newline at end of file diff --git a/docs/guides.md b/docs/guides.md new file mode 100644 index 00000000000..c8196bfa341 --- /dev/null +++ b/docs/guides.md @@ -0,0 +1,47 @@ +## Guides + +Mongoose guides provide detailed tutorials on Mongoose's core concepts and +integrating Mongoose with external tools and frameworks. + +### Mongoose Core Concepts + +* [Schemas](/docs/guide.html) +* [SchemaTypes](/docs/schematypes.html) +* [Connections](/docs/connections.html) +* [Models](/docs/models.html) +* [Documents](/docs/documents.html) +* [Subdocuments](/docs/subdocs.html) +* [Queries](/docs/queries.html) +* [Validation](/docs/validation.html) +* [Middleware](/docs/middleware.html) +* [Populate](/docs/populate.html) +* [Discriminators](/docs/discriminators.html) +* [Plugins](/docs/plugins.html) +* [Faster Mongoose Queries With Lean](/docs/tutorials/lean.html) +* [Query Casting](/docs/tutorials/query_casting.html) +* [findOneAndUpdate](/docs/tutorials/findoneandupdate.html) +* [Getters and Setters](/docs/tutorials/getters-setters.html) +* [Virtuals](/docs/tutorials/virtuals.html) + +### Advanced Topics + +* [Working with Dates](/docs/tutorials/dates.html) +* [Custom Casting For Built-in Types](/docs/tutorials/custom-casting.html) +* [Custom SchemaTypes](/docs/customschematypes.html) + +### Integrations + +* [Promises](/docs/promises.html) +* [AWS Lambda](/docs/lambda.html) +* [Browser Library](/docs/browser.html) +* [GeoJSON](/docs/geojson.html) +* [Transactions](/docs/transactions.html) +* [MongoDB Driver Deprecation Warnings](/docs/deprecations.html) +* [Testing with Jest](/docs/jest.html) +* [SSL Connections](/docs/tutorials/ssl.html) + +### Migration Guides + +* [Mongoose 4.x to 5.x](/docs/migrating_to_5.html) +* [Mongoose 3.x to 4.x](/docs/migration.html) + diff --git a/docs/guides.pug b/docs/guides.pug deleted file mode 100644 index 04f6312edb3..00000000000 --- a/docs/guides.pug +++ /dev/null @@ -1,69 +0,0 @@ -extends layout - -block append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown - ## Guides - - - - - - Mongoose guides provide detailed tutorials on Mongoose's core concepts and - integrating Mongoose with external tools and frameworks. - - ### Mongoose Core Concepts - - * [Schemas](/docs/guide.html) - * [SchemaTypes](/docs/schematypes.html) - * [Connections](/docs/connections.html) - * [Models](/docs/models.html) - * [Documents](/docs/documents.html) - * [Subdocuments](/docs/subdocs.html) - * [Queries](/docs/queries.html) - * [Validation](/docs/validation.html) - * [Middleware](/docs/middleware.html) - * [Populate](/docs/populate.html) - * [Discriminators](/docs/discriminators.html) - * [Plugins](/docs/plugins.html) - * [Faster Mongoose Queries With Lean](/docs/tutorials/lean.html) - * [Query Casting](/docs/tutorials/query_casting.html) - * [findOneAndUpdate](/docs/tutorials/findoneandupdate.html) - * [Getters and Setters](/docs/tutorials/getters-setters.html) - * [Virtuals](/docs/tutorials/virtuals.html) - - ### Advanced Topics - - * [Working with Dates](/docs/tutorials/dates.html) - * [Custom Casting For Built-in Types](/docs/tutorials/custom-casting.html) - * [Custom SchemaTypes](/docs/customschematypes.html) - - ### Integrations - - * [Promises](/docs/promises.html) - * [AWS Lambda](/docs/lambda.html) - * [Browser Library](/docs/browser.html) - * [GeoJSON](/docs/geojson.html) - * [Transactions](/docs/transactions.html) - * [MongoDB Driver Deprecation Warnings](/docs/deprecations.html) - * [Testing with Jest](/docs/jest.html) - * [SSL Connections](/docs/tutorials/ssl.html) - - ### Migration Guides - - * [Mongoose 4.x to 5.x](/docs/migrating_to_5.html) - * [Mongoose 3.x to 4.x](/docs/migration.html) - diff --git a/docs/images/fortunegames.jpg b/docs/images/fortunegames.jpg new file mode 100644 index 00000000000..795eca76cf2 Binary files /dev/null and b/docs/images/fortunegames.jpg differ diff --git a/docs/images/logo-poprey-com-white-100.png b/docs/images/logo-poprey-com-white-100.png new file mode 100644 index 00000000000..c4ab421aca1 Binary files /dev/null and b/docs/images/logo-poprey-com-white-100.png differ diff --git a/docs/images/mongoose.svg b/docs/images/mongoose.svg new file mode 100644 index 00000000000..a230aefd1f9 --- /dev/null +++ b/docs/images/mongoose.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 688c6d666a5..00000000000 --- a/docs/index.html +++ /dev/null @@ -1,128 +0,0 @@ -Mongoose v5.6.0: Getting Started

    Getting Started

    -

    First be sure you have MongoDB and Node.js installed.

    -

    Next install Mongoose from the command line using npm:

    -
    $ npm install mongoose

    Now say we like fuzzy kittens and want to record every kitten we ever meet -in MongoDB. -The first thing we need to do is include mongoose in our project and open a -connection to the test database on our locally running instance of MongoDB.

    -
    // getting-started.js
    -var mongoose = require('mongoose');
    -mongoose.connect('mongodb://localhost/test', {useNewUrlParser: true});
    -

    We have a pending connection to the test database running on localhost. -We now need to get notified if we connect successfully or if a connection -error occurs:

    -
    var db = mongoose.connection;
    -db.on('error', console.error.bind(console, 'connection error:'));
    -db.once('open', function() {
    -  // we're connected!
    -});
    -

    Once our connection opens, our callback will be called. For brevity, -let's assume that all following code is within this callback.

    -

    With Mongoose, everything is derived from a Schema. -Let's get a reference to it and define our kittens.

    -
    var kittySchema = new mongoose.Schema({
    -  name: String
    -});
    -

    So far so good. We've got a schema with one property, name, which will be a String. The next step is compiling our schema into a Model.

    -
    var Kitten = mongoose.model('Kitten', kittySchema);
    -

    A model is a class with which we construct documents. -In this case, each document will be a kitten with properties and behaviors as declared in our schema. -Let's create a kitten document representing the little guy we just met on the sidewalk outside:

    -
    var silence = new Kitten({ name: 'Silence' });
    -console.log(silence.name); // 'Silence'
    -

    Kittens can meow, so let's take a look at how to add "speak" functionality -to our documents:

    -
    // NOTE: methods must be added to the schema before compiling it with mongoose.model()
    -kittySchema.methods.speak = function () {
    -  var greeting = this.name
    -    ? "Meow name is " + this.name
    -    : "I don't have a name";
    -  console.log(greeting);
    -}
    -
    -var Kitten = mongoose.model('Kitten', kittySchema);
    -

    Functions added to the methods property of a schema get compiled into -the Model prototype and exposed on each document instance:

    -
    var fluffy = new Kitten({ name: 'fluffy' });
    -fluffy.speak(); // "Meow name is fluffy"
    -

    We have talking kittens! But we still haven't saved anything to MongoDB. -Each document can be saved to the database by calling its save method. The first argument to the callback will be an error if any occurred.

    -
      fluffy.save(function (err, fluffy) {
    -    if (err) return console.error(err);
    -    fluffy.speak();
    -  });
    -

    Say time goes by and we want to display all the kittens we've seen. -We can access all of the kitten documents through our Kitten model.

    -
    Kitten.find(function (err, kittens) {
    -  if (err) return console.error(err);
    -  console.log(kittens);
    -})
    -

    We just logged all of the kittens in our db to the console. -If we want to filter our kittens by name, Mongoose supports MongoDBs rich querying syntax.

    -
    Kitten.find({ name: /^fluff/ }, callback);
    -

    This performs a search for all documents with a name property that begins -with "Fluff" and returns the result as an array of kittens to the callback.

    -

    Congratulations

    -

    That's the end of our quick start. We created a schema, added a custom document method, saved and queried kittens in MongoDB using Mongoose. Head over to the guide, or API docs for more.

    -
    \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000000..69fdf994253 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,128 @@ +## Getting Started + + + +_First be sure you have [MongoDB](http://www.mongodb.org/downloads) and [Node.js](http://nodejs.org/) installed._ + +Next install Mongoose from the command line using `npm`: + +``` +$ npm install mongoose --save +``` + +Now say we like fuzzy kittens and want to record every kitten we ever meet +in MongoDB. +The first thing we need to do is include mongoose in our project and open a +connection to the `test` database on our locally running instance of MongoDB. + +```javascript +// getting-started.js +const mongoose = require('mongoose'); +mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true, useUnifiedTopology: true}); +``` + +We have a pending connection to the test database running on localhost. +We now need to get notified if we connect successfully or if a connection +error occurs: + +```javascript +const db = mongoose.connection; +db.on('error', console.error.bind(console, 'connection error:')); +db.once('open', function() { + // we're connected! +}); +``` + +Once our connection opens, our callback will be called. For brevity, +let's assume that all following code is within this callback. + +With Mongoose, everything is derived from a [Schema](/docs/guide.html). +Let's get a reference to it and define our kittens. + +```javascript +const kittySchema = new mongoose.Schema({ + name: String +}); +``` + +So far so good. We've got a schema with one property, `name`, which will be a `String`. The next step is compiling our schema into a [Model](/docs/models.html). + +```javascript +const Kitten = mongoose.model('Kitten', kittySchema); +``` + +A model is a class with which we construct documents. +In this case, each document will be a kitten with properties and behaviors as declared in our schema. +Let's create a kitten document representing the little guy we just met on the sidewalk outside: + +```javascript +const silence = new Kitten({ name: 'Silence' }); +console.log(silence.name); // 'Silence' +``` + +Kittens can meow, so let's take a look at how to add "speak" functionality +to our documents: + +```javascript +// NOTE: methods must be added to the schema before compiling it with mongoose.model() +kittySchema.methods.speak = function () { + const greeting = this.name + ? "Meow name is " + this.name + : "I don't have a name"; + console.log(greeting); +} + +const Kitten = mongoose.model('Kitten', kittySchema); +``` + +Functions added to the `methods` property of a schema get compiled into +the `Model` prototype and exposed on each document instance: + +```javascript +const fluffy = new Kitten({ name: 'fluffy' }); +fluffy.speak(); // "Meow name is fluffy" +``` + +We have talking kittens! But we still haven't saved anything to MongoDB. +Each document can be saved to the database by calling its [save](/docs/api.html#model_Model-save) method. The first argument to the callback will be an error if any occurred. + +```javascript + fluffy.save(function (err, fluffy) { + if (err) return console.error(err); + fluffy.speak(); + }); +``` + +Say time goes by and we want to display all the kittens we've seen. +We can access all of the kitten documents through our Kitten [model](/docs/models.html). + +```javascript +Kitten.find(function (err, kittens) { + if (err) return console.error(err); + console.log(kittens); +}) +``` + +We just logged all of the kittens in our db to the console. +If we want to filter our kittens by name, Mongoose supports MongoDBs rich [querying](/docs/queries.html) syntax. + +```javascript +Kitten.find({ name: /^fluff/ }, callback); +``` + +This performs a search for all documents with a name property that begins +with "fluff" and returns the result as an array of kittens to the callback. + +### Congratulations + +That's the end of our quick start. We created a schema, added a custom document method, saved and queried kittens in MongoDB using Mongoose. Head over to the [guide](guide.html), or [API docs](api.html) for more. diff --git a/docs/index.pug b/docs/index.pug deleted file mode 100644 index a1c0ecb06f2..00000000000 --- a/docs/index.pug +++ /dev/null @@ -1,132 +0,0 @@ -extends layout - -block append style - style. - hr { - display: block; - margin-top: 40px; - margin-bottom: 40px; - border: 0px; - height: 1px; - background-color: #232323; - width: 100%; - } - -block content - :markdown - ## Getting Started - - _First be sure you have [MongoDB](http://www.mongodb.org/downloads) and [Node.js](http://nodejs.org/) installed._ - - Next install Mongoose from the command line using `npm`: - - ``` - $ npm install mongoose --save - ``` - - Now say we like fuzzy kittens and want to record every kitten we ever meet - in MongoDB. - The first thing we need to do is include mongoose in our project and open a - connection to the `test` database on our locally running instance of MongoDB. - - ```javascript - // getting-started.js - const mongoose = require('mongoose'); - mongoose.connect('mongodb://localhost/test', {useNewUrlParser: true, useUnifiedTopology: true}); - ``` - - We have a pending connection to the test database running on localhost. - We now need to get notified if we connect successfully or if a connection - error occurs: - - ```javascript - const db = mongoose.connection; - db.on('error', console.error.bind(console, 'connection error:')); - db.once('open', function() { - // we're connected! - }); - ``` - - Once our connection opens, our callback will be called. For brevity, - let's assume that all following code is within this callback. - - With Mongoose, everything is derived from a [Schema](/docs/guide.html). - Let's get a reference to it and define our kittens. - - ```javascript - const kittySchema = new mongoose.Schema({ - name: String - }); - ``` - - So far so good. We've got a schema with one property, `name`, which will be a `String`. The next step is compiling our schema into a [Model](/docs/models.html). - - ```javascript - const Kitten = mongoose.model('Kitten', kittySchema); - ``` - - A model is a class with which we construct documents. - In this case, each document will be a kitten with properties and behaviors as declared in our schema. - Let's create a kitten document representing the little guy we just met on the sidewalk outside: - - ```javascript - const silence = new Kitten({ name: 'Silence' }); - console.log(silence.name); // 'Silence' - ``` - - Kittens can meow, so let's take a look at how to add "speak" functionality - to our documents: - - ```javascript - // NOTE: methods must be added to the schema before compiling it with mongoose.model() - kittySchema.methods.speak = function () { - const greeting = this.name - ? "Meow name is " + this.name - : "I don't have a name"; - console.log(greeting); - } - - const Kitten = mongoose.model('Kitten', kittySchema); - ``` - - Functions added to the `methods` property of a schema get compiled into - the `Model` prototype and exposed on each document instance: - - ```javascript - const fluffy = new Kitten({ name: 'fluffy' }); - fluffy.speak(); // "Meow name is fluffy" - ``` - - We have talking kittens! But we still haven't saved anything to MongoDB. - Each document can be saved to the database by calling its [save](/docs/api.html#model_Model-save) method. The first argument to the callback will be an error if any occurred. - - ```javascript - fluffy.save(function (err, fluffy) { - if (err) return console.error(err); - fluffy.speak(); - }); - ``` - - Say time goes by and we want to display all the kittens we've seen. - We can access all of the kitten documents through our Kitten [model](/docs/models.html). - - ```javascript - Kitten.find(function (err, kittens) { - if (err) return console.error(err); - console.log(kittens); - }) - ``` - - We just logged all of the kittens in our db to the console. - If we want to filter our kittens by name, Mongoose supports MongoDBs rich [querying](/docs/queries.html) syntax. - - ```javascript - Kitten.find({ name: /^fluff/ }, callback); - ``` - - This performs a search for all documents with a name property that begins - with "fluff" and returns the result as an array of kittens to the callback. - - ### Congratulations - - That's the end of our quick start. We created a schema, added a custom document method, saved and queried kittens in MongoDB using Mongoose. Head over to the [guide](guide.html), or [API docs](api.html) for more. diff --git a/docs/jest.html b/docs/jest.html deleted file mode 100644 index 3b25879653d..00000000000 --- a/docs/jest.html +++ /dev/null @@ -1,119 +0,0 @@ -Mongoose v5.6.0: Testing Mongoose with Jest

    Testing Mongoose with Jest

    - - - - -

    Jest is a client-side JavaScript testing library developed by Facebook. -It was one of the libraries affected by Facebook's licensing scandal in 2017. -Because Jest is designed primarily for testing React applications, using -it to test Node.js server-side applications comes with a lot of caveats. -We strongly recommend using Mocha instead. -If you choose to delve into dangerous waters and test Mongoose apps with -Jest, here's what you need to know:

    - -

    Do not use Jest's default jsdom test environment -when testing Mongoose apps, unless you are explicitly testing an -application that only uses -Mongoose's browser library.

    -

    The jsdom test environment attempts to create a browser-like test -environment in Node.js, and it comes with numerous nasty surprises like a -stubbed setTimeout() function -that silently fails after tests are finished. Mongoose does not support jsdom -in general and is not expected to function correctly in the jsdom test -environment.

    -

    To change your testEnvironment to Node.js, add testEnvironment to your -jest.config.js file:

    -
    module.exports = {
    -  testEnvironment: 'node'
    -};
    -

    Timer Mocks

    -

    Absolutely do not use timer mocks -when testing Mongoose apps. Fake timers stub out global functions like -setTimeout() and setInterval(), which causes problems when an underlying -library uses these functions. The MongoDB Node.js driver uses these functions -for deferring work until the next tick of the event loop and for monitoring -connections to the MongoDB server.

    -

    Mongoose devs have already refactored out code -to avoid using setImmediate() -to defer work to the next tick of the event loop, but we can't reasonably -ensure that every library Mongoose depends on doesn't use setImmediate().

    -

    Mongoose uses nextTick(), which Jest's underlying dependency explicitly doesn't stub by default. -But, process.nextTick() isn't the same as setImmediate() because of -microtasks vs macrotasks, -so underlying libraries like the MongoDB driver may use setImmediate().

    -

    To work around this, create your own wrapper around setTimeout() and -stub that instead using sinon.

    -
    // time.js
    -exports.setTimeout = function() {
    -  return global.setTimeout.apply(global, arguments);
    -};
    -
    -// Tests
    -const time = require('../util/time');
    -const sinon = require('sinon');
    -sinon.stub(time, 'setTimeout');
    -

    Further Reading

    -

    Want to learn more about testing Mongoose apps? The -RESTful Web Services with Node.js and Express -course on Pluralsight has a great section on testing Mongoose apps with Mocha.

    - - -
    \ No newline at end of file diff --git a/docs/jest.md b/docs/jest.md new file mode 100644 index 00000000000..36e07845d45 --- /dev/null +++ b/docs/jest.md @@ -0,0 +1,84 @@ +# Testing Mongoose with [Jest](https://www.npmjs.com/package/jest) + +Jest is a JavaScript runtime developed by Facebook that is usually used for testing. +Because Jest is designed primarily for testing React applications, using it to test Node.js server-side applications comes with a lot of caveats. +We strongly recommend using a different testing framework, like [Mocha](https://mochajs.org/). + +If you choose to delve into dangerous waters and test Mongoose apps with Jest, here's what you need to know: + + + +If you are using Jest `<=26`, do **not** use Jest's default [`jsdom` test environment](https://jestjs.io/docs/en/configuration.html#testenvironment-string) when testing Mongoose apps, _unless_ you are explicitly testing an application that only uses [Mongoose's browser library](https://mongoosejs.com/docs/browser.html). In Jest `>=27`, ["node" is Jest's default `testEnvironment`](https://jestjs.io/ro/blog/2021/05/25/jest-27#flipping-defaults), so this is no longer an issue. + +The `jsdom` test environment attempts to create a browser-like test +environment in Node.js, and it comes with numerous nasty surprises like a +[stubbed `setTimeout()` function](https://github.com/jsdom/jsdom/commit/3f306bea5362aceb2a219a2e98ff96a7464d2f19#commitcomment-31316213) +that silently fails after tests are finished. Mongoose does not support jsdom +in general and is not expected to function correctly in the `jsdom` test +environment. + +To change your `testEnvironment` to Node.js, add `testEnvironment` to your +`jest.config.js` file: + +```javascript +module.exports = { + testEnvironment: 'node' +}; +``` + +

    Timer Mocks

    + +Absolutely do **not** use [timer mocks](https://jestjs.io/docs/en/timer-mocks.html) when testing Mongoose apps. +This is especially important if you're using Jest `>=25`, which stubs out `process.nextTick()`. + +Fake timers stub out global functions like `setTimeout()` and `setInterval()`, which causes problems when an underlying library uses these functions. +Mongoose and the MongoDB Node.js driver uses these functions for deferring work until the next tick of the event loop and for monitoring connections to the MongoDB server. + +If you absolutely must use timer mocks, make sure you import Mongoose **before** calling `useFakeTimers()`: + +```javascript +// Fine for basic cases, but may still cause issues: +const mongoose = require('mongoose'); + +jest.useFakeTimers(); + +// Bad: +jest.useFakeTimers(); + +const mongoose = require('mongoose'); +``` + +Mongoose devs have already refactored out code to [avoid using `setImmediate()`](https://github.com/Automattic/mongoose/issues/6074) to defer work to the next tick of the event loop, but we can't reasonably ensure that every library Mongoose depends on doesn't use `setImmediate()`. + +A better alternative is to create your own wrapper around `setTimeout()` and +stub that instead using [sinon](http://npmjs.com/package/sinon). + +```javascript +// time.js +exports.setTimeout = function() { + return global.setTimeout.apply(global, arguments); +}; + +// Tests +const time = require('../util/time'); +const sinon = require('sinon'); +sinon.stub(time, 'setTimeout'); +``` + +

    globalSetup and globalTeardown

    + +Do **not** use `globalSetup` to call `mongoose.connect()` or +`mongoose.createConnection()`. Jest runs `globalSetup` in +a [separate environment](https://github.com/facebook/jest/issues/7184), +so you cannot use any connections you create in `globalSetup` +in your tests. + +## Further Reading + +Want to learn how to test Mongoose apps correctly? The +[RESTful Web Services with Node.js and Express](https://pluralsight.pxf.io/c/1321469/424552/7490?u=https%3A%2F%2Fapp.pluralsight.com%2Flibrary%2Fcourses%2Fnode-js-express-rest-web-services%2Ftable-of-contents) +course on Pluralsight has a great section on testing Mongoose apps with [Mocha](http://npmjs.com/package/mocha). + + + + \ No newline at end of file diff --git a/docs/jest.pug b/docs/jest.pug deleted file mode 100644 index ca70187c2ce..00000000000 --- a/docs/jest.pug +++ /dev/null @@ -1,107 +0,0 @@ -extends layout - -block append style - link(rel="stylesheet" href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown - # Testing Mongoose with [Jest](https://www.npmjs.com/package/jest) - - - - - - Jest is a client-side JavaScript testing library developed by Facebook. - Because Jest is designed primarily for testing React applications, using - it to test Node.js server-side applications comes with a lot of caveats. - We strongly advise against using Jest for testing any Node.js apps unless - you are an expert developer with an intimate knowledge of Jest. - - If you choose to delve into dangerous waters and test Mongoose apps with - Jest, here's what you need to know: - - ## Recommended `testEnvironment` - - Do **not** use Jest's default [`jsdom` test environment](https://jestjs.io/docs/en/configuration.html#testenvironment-string) - when testing Mongoose apps, _unless_ you are explicitly testing an - application that only uses - [Mongoose's browser library](https://mongoosejs.com/docs/browser.html). - - The `jsdom` test environment attempts to create a browser-like test - environment in Node.js, and it comes with numerous nasty surprises like a - [stubbed `setTimeout()` function](https://github.com/jsdom/jsdom/commit/3f306bea5362aceb2a219a2e98ff96a7464d2f19#commitcomment-31316213) - that silently fails after tests are finished. Mongoose does not support jsdom - in general and is not expected to function correctly in the `jsdom` test - environment. - - To change your `testEnvironment` to Node.js, add `testEnvironment` to your - `jest.config.js` file: - - ```javascript - module.exports = { - testEnvironment: 'node' - }; - ``` - - ## Timer Mocks - - Absolutely do **not** use [timer mocks](https://jestjs.io/docs/en/timer-mocks.html) - when testing Mongoose apps. Fake timers stub out global functions like - `setTimeout()` and `setInterval()`, which causes problems when an underlying - library uses these functions. The MongoDB Node.js driver uses these functions - for deferring work until the next tick of the event loop and for monitoring - connections to the MongoDB server. - - Mongoose devs have already refactored out code - to [avoid using `setImmediate()`](https://github.com/Automattic/mongoose/issues/6074) - to defer work to the next tick of the event loop, but we can't reasonably - ensure that every library Mongoose depends on doesn't use `setImmediate()`. - - Mongoose uses `nextTick()`, which [Jest's underlying dependency explicitly doesn't stub by default](https://sinonjs.org/releases/latest/fake-timers/). - But, `process.nextTick()` isn't the same as `setImmediate()` because of - [microtasks vs macrotasks](https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f#2261), - so underlying libraries like the [MongoDB driver](http://npmjs.com/package/mongodb) may use `setImmediate()`. - - To work around this, create your own wrapper around `setTimeout()` and - stub that instead using [sinon](http://npmjs.com/package/sinon). - - ```javascript - // time.js - exports.setTimeout = function() { - return global.setTimeout.apply(global, arguments); - }; - - // Tests - const time = require('../util/time'); - const sinon = require('sinon'); - sinon.stub(time, 'setTimeout'); - ``` - - ## `globalSetup` and `globalTeardown` - - Do **not** use `globalSetup` to call `mongoose.connect()` or - `mongoose.createConnection()`. Jest runs `globalSetup` in - a [separate environment](https://github.com/facebook/jest/issues/7184), - so you cannot use any connections you create in `globalSetup` - in your tests. - - ## Further Reading - - Want to learn more about testing Mongoose apps? The - [RESTful Web Services with Node.js and Express](https://pluralsight.pxf.io/c/1321469/424552/7490?u=https%3A%2F%2Fapp.pluralsight.com%2Flibrary%2Fcourses%2Fnode-js-express-rest-web-services%2Ftable-of-contents) - course on Pluralsight has a great section on testing Mongoose apps with [Mocha](http://npmjs.com/package/mocha). - - - - diff --git a/docs/js/search.js b/docs/js/search.js index 9b824fd40d0..71c623502e2 100644 --- a/docs/js/search.js +++ b/docs/js/search.js @@ -1,4 +1,4 @@ -var root = 'https://8jekzv4pyd.execute-api.us-east-1.amazonaws.com/prod'; +var root = 'https://mongoosejs.azurewebsites.net/api'; var pairs = window.location.search.replace(/^\?/, '').split('&'); var q = null; diff --git a/docs/lambda.html b/docs/lambda.html deleted file mode 100644 index 1b9556b305a..00000000000 --- a/docs/lambda.html +++ /dev/null @@ -1,122 +0,0 @@ -Mongoose v5.6.0: Using Mongoose With AWS Lambda

    Using Mongoose With AWS Lambda

    - - - -

    AWS Lambda is a popular service for running -arbitrary functions without managing individual servers. Using Mongoose in your -AWS Lambda functions is easy. Here's a sample function that connects to a -MongoDB instance and finds a single document:

    -
    const mongoose = require('mongoose');
    -
    -let conn = null;
    -
    -const uri = 'YOUR CONNECTION STRING HERE';
    -
    -exports.handler = async function(event, context) {
    -  // Make sure to add this so you can re-use `conn` between function calls.
    -  // See https://www.mongodb.com/blog/post/serverless-development-with-nodejs-aws-lambda-mongodb-atlas
    -  context.callbackWaitsForEmptyEventLoop = false;
    -
    -  // Because `conn` is in the global scope, Lambda may retain it between
    -  // function calls thanks to `callbackWaitsForEmptyEventLoop`.
    -  // This means your Lambda function doesn't have to go through the
    -  // potentially expensive process of connecting to MongoDB every time.
    -  if (conn == null) {
    -    conn = await mongoose.createConnection(uri, {
    -      // Buffering means mongoose will queue up operations if it gets
    -      // disconnected from MongoDB and send them when it reconnects.
    -      // With serverless, better to fail fast if not connected.
    -      bufferCommands: false, // Disable mongoose buffering
    -      bufferMaxEntries: 0 // and MongoDB driver buffering
    -    });
    -    conn.model('Test', new mongoose.Schema({ name: String }));
    -  }
    -
    -  const M = conn.model('Test');
    -
    -  const doc = await M.findOne();
    -  console.log(doc);
    -
    -  return doc;
    -};
    -

    To import this function into Lambda, go the AWS Lambda console -and click "Create Function".

    -

    -

    Create a function called "mongoose-test" with the below settings:

    -

    -

    Copy the source code into a file called lambda.js. Then run npm install mongoose co. -Finally, run zip -r mongoose-test.zip node_modules/ lambda.js to create a -zip that you can upload to Lambda using the "Upload a Zip File" option under "Function code". -Make sure you also change the "Handler" input to lambda.handler to match the lambda.js file's handler function.

    -

    -

    Next, click the "Save" button and then the "Test" button. The "Test" button will -ask you to create a new test event, just create one because your inputs don't matter -for this example. Then, hit "Test" again to actually run your function:

    -

    -

    If your MongoDB database goes down in between function calls, you may -see the below error message:

    -
    cannot find account after reload: could not find config for <hostname>

    Lambda's JavaScript framework recently added support for -async/await as long as you're using Node 8.x, so make sure you're not -using Node.js 6.x.

    -

    Want to learn how to check whether your favorite JavaScript frameworks, like Express or React, work with async/await? Spoiler alert: neither Express nor React support async/await. Chapter 4 of Mastering Async/Await explains the basic principles for determining whether a framework supports async/await. Get your copy!

    - - -
    \ No newline at end of file diff --git a/docs/lambda.md b/docs/lambda.md new file mode 100644 index 00000000000..61c060828f8 --- /dev/null +++ b/docs/lambda.md @@ -0,0 +1,119 @@ +# Using Mongoose With AWS Lambda + +[AWS Lambda](https://aws.amazon.com/lambda/) is a popular service for running +arbitrary functions without managing individual servers. Using Mongoose in your +AWS Lambda functions is easy. Here's a sample function that connects to a +MongoDB instance and finds a single document: + +```javascript +const mongoose = require('mongoose'); + +let conn = null; + +const uri = 'YOUR CONNECTION STRING HERE'; + +exports.handler = async function(event, context) { + // Make sure to add this so you can re-use `conn` between function calls. + // See https://www.mongodb.com/blog/post/serverless-development-with-nodejs-aws-lambda-mongodb-atlas + context.callbackWaitsForEmptyEventLoop = false; + + // Because `conn` is in the global scope, Lambda may retain it between + // function calls thanks to `callbackWaitsForEmptyEventLoop`. + // This means your Lambda function doesn't have to go through the + // potentially expensive process of connecting to MongoDB every time. + if (conn == null) { + conn = mongoose.createConnection(uri, { + useNewUrlParser: true, + useUnifiedTopology: true, + // Buffering means mongoose will queue up operations if it gets + // disconnected from MongoDB and send them when it reconnects. + // With serverless, better to fail fast if not connected. + bufferCommands: false, // Disable mongoose buffering + // and tell the MongoDB driver to not wait more than 5 seconds + // before erroring out if it isn't connected + serverSelectionTimeoutMS: 5000 + }); + + // `await`ing connection after assigning to the `conn` variable + // to avoid multiple function calls creating new connections + await conn; + conn.model('Test', new mongoose.Schema({ name: String })); + } + + const M = conn.model('Test'); + + const doc = await M.findOne(); + console.log(doc); + + return doc; +}; +``` + +## Connection Helper + +The above code works fine for a single Lambda function, but what if you want to reuse the same connection logic in multiple Lambda functions? +You can export the below function. + +```javascript +'use strict'; + +const mongoose = require('mongoose'); + +let conn = null; + +const uri = 'YOUR CONNECTION STRING HERE'; + +exports.connect = async function() { + if (conn == null) { + conn = mongoose.createConnection(uri, { + useNewUrlParser: true, + useUnifiedTopology: true, + bufferCommands: false, // Disable mongoose buffering + serverSelectionTimeoutMS: 5000 + }); + + // `await`ing connection after assigning to the `conn` variable + // to avoid multiple function calls creating new connections + await conn; + } + + return conn; +}; +``` + +## Using `mongoose.connect()` + +You can also use `mongoose.connect()`, so you can use `mongoose.model()` to create models. + +```javascript +'use strict'; + +const mongoose = require('mongoose'); + +let conn = null; + +const uri = 'YOUR CONNECTION STRING HERE'; + +exports.connect = async function() { + if (conn == null) { + conn = mongoose.connect(uri, { + useNewUrlParser: true, + useUnifiedTopology: true, + bufferCommands: false, // Disable mongoose buffering + serverSelectionTimeoutMS: 5000 + }).then(() => mongoose); + + // `await`ing connection after assigning to the `conn` variable + // to avoid multiple function calls creating new connections + await conn; + } + + return conn; +}; +``` + +*Want to learn how to check whether your favorite JavaScript frameworks, like [Express](http://expressjs.com/) or [React](https://reactjs.org/), work with async/await? Spoiler alert: neither Express nor React support async/await. Chapter 4 of Mastering Async/Await explains the basic principles for determining whether a framework supports async/await. [Get your copy!](http://asyncawait.net/?utm_source=mongoosejs&utm_campaign=lambda)* + + + + diff --git a/docs/lambda.pug b/docs/lambda.pug deleted file mode 100644 index 1b5b952058a..00000000000 --- a/docs/lambda.pug +++ /dev/null @@ -1,106 +0,0 @@ -extends layout - -block append style - link(rel="stylesheet" href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - h2 Using Mongoose With AWS Lambda - :markdown - - - - - [AWS Lambda](https://aws.amazon.com/lambda/) is a popular service for running - arbitrary functions without managing individual servers. Using Mongoose in your - AWS Lambda functions is easy. Here's a sample function that connects to a - MongoDB instance and finds a single document: - - ```javascript - const mongoose = require('mongoose'); - - let conn = null; - - const uri = 'YOUR CONNECTION STRING HERE'; - - exports.handler = async function(event, context) { - // Make sure to add this so you can re-use `conn` between function calls. - // See https://www.mongodb.com/blog/post/serverless-development-with-nodejs-aws-lambda-mongodb-atlas - context.callbackWaitsForEmptyEventLoop = false; - - // Because `conn` is in the global scope, Lambda may retain it between - // function calls thanks to `callbackWaitsForEmptyEventLoop`. - // This means your Lambda function doesn't have to go through the - // potentially expensive process of connecting to MongoDB every time. - if (conn == null) { - conn = mongoose.createConnection(uri, { - // Buffering means mongoose will queue up operations if it gets - // disconnected from MongoDB and send them when it reconnects. - // With serverless, better to fail fast if not connected. - bufferCommands: false, // Disable mongoose buffering - bufferMaxEntries: 0 // and MongoDB driver buffering - }); - - // `await`ing connection after assigning to the `conn` variable - // to avoid multiple function calls creating new connections - await conn; - conn.model('Test', new mongoose.Schema({ name: String })); - } - - const M = conn.model('Test'); - - const doc = await M.findOne(); - console.log(doc); - - return doc; - }; - ``` - - To import this function into Lambda, go [the AWS Lambda console](https://console.aws.amazon.com/lambda) - and click "Create Function". - - - - Create a function called "mongoose-test" with the below settings: - - - - Copy the source code into a file called `lambda.js`. Then run `npm install mongoose co`. - Finally, run `zip -r mongoose-test.zip node_modules/ lambda.js` to create a - zip that you can upload to Lambda using the "Upload a Zip File" option under "Function code". - Make sure you also change the "Handler" input to `lambda.handler` to match the `lambda.js` file's `handler` function. - - - - Next, click the "Save" button and then the "Test" button. The "Test" button will - ask you to create a new test event, just create one because your inputs don't matter - for this example. Then, hit "Test" again to actually run your function: - - - - If your MongoDB database goes down in between function calls, you may - see the below error message: - - ``` - cannot find account after reload: could not find config for - ``` - - Lambda's JavaScript framework recently added support for - async/await as long as you're using Node 8.x, so make sure you're not - using Node.js 6.x. - - *Want to learn how to check whether your favorite JavaScript frameworks, like [Express](http://expressjs.com/) or [React](https://reactjs.org/), work with async/await? Spoiler alert: neither Express nor React support async/await. Chapter 4 of Mastering Async/Await explains the basic principles for determining whether a framework supports async/await. [Get your copy!](http://asyncawait.net/?utm_source=mongoosejs&utm_campaign=lambda)* - - - - diff --git a/docs/layout.pug b/docs/layout.pug index 19dd84ce0a7..2d377c75aa7 100644 --- a/docs/layout.pug +++ b/docs/layout.pug @@ -51,6 +51,8 @@ html(lang='en') li.pure-menu-horizontal.pure-menu-item.pure-menu-has-children.pure-menu-allow-hover.version a(href="#").pure-menu-link Version #{package.version} ul.pure-menu-children + li.pure-menu-item + a.pure-menu-link(href="/docs") Version 6.x li.pure-menu-item a.pure-menu-link(href="/docs/4.x") Version #{package.latest4x} li.pure-menu-item @@ -60,73 +62,86 @@ html(lang='en') button#search-button-nav img(src="/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="/docs/5.x/docs/index.html", class=outputUrl === '/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 + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/docs/models.html", class=outputUrl === '/docs/models.html' ? 'selected' : '') Models li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/documents.html", class=outputUrl === '/docs/documents.html' ? 'selected' : '') Documents + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/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/5.x/docs/validation.html", class=outputUrl === '/docs/validation.html' ? 'selected' : '') Validation li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/validation.html", class=outputUrl === '/docs/validation.html' ? 'selected' : '') Validation + a.pure-menu-link(href="/docs/5.x/docs/middleware.html", class=outputUrl === '/docs/middleware.html' ? 'selected' : '') Middleware li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/middleware.html", class=outputUrl === '/docs/middleware.html' ? 'selected' : '') Middleware + a.pure-menu-link(href="/docs/5.x/docs/populate.html", class=outputUrl === '/docs/populate.html' ? 'selected' : '') Populate li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/populate.html", class=outputUrl === '/docs/populate.html' ? 'selected' : '') Populate + a.pure-menu-link(href="/docs/5.x/docs/discriminators.html", class=outputUrl === '/docs/discriminators.html' ? 'selected' : '') Discriminators li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/discriminators.html", class=outputUrl === '/docs/discriminators.html' ? 'selected' : '') Discriminators + a.pure-menu-link(href="/docs/5.x/docs/plugins.html", class=outputUrl === '/docs/plugins.html' ? 'selected' : '') Plugins li.pure-menu-item.sub-item - a.pure-menu-link(href="/docs/plugins.html", class=outputUrl === '/docs/plugins.html' ? 'selected' : '') Plugins + a.pure-menu-link(href="/docs/5.x/docs/transactions.html", class=outputUrl === '/docs/transactions.html' ? 'selected' : '') Transactions + li.pure-menu-item.sub-item + a.pure-menu-link(href="/docs/5.x/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/5.x/docs/typescript/schemas.html", class=outputUrl === '/docs/typescript/schemas.html' ? 'selected' : '') Schemas + li.pure-menu-item.tertiary-item + a.pure-menu-link(href="/docs/5.x/docs/typescript/statics.html", class=outputUrl === '/docs/typescript/statics.html' ? 'selected' : '') Statics + li.pure-menu-item.tertiary-item + a.pure-menu-link(href="/docs/5.x/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/5.x/docs/typescript/populate.html", class=outputUrl === '/docs/typescript/populate.html' ? 'selected' : '') Populate li.pure-menu-item - a.pure-menu-link(href="/docs/api.html", class=outputUrl === '/docs/api.html' ? 'selected' : '') API + a.pure-menu-link(href="/docs/5.x/docs/api.html", class=outputUrl === '/docs/api.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 + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/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 + a.pure-menu-link(href="/docs/5.x/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="/docs/5.x/docs/api/virtualtype.html", class=outputUrl === '/docs/api/virtualtype.html' ? 'selected' : '') VirtualType 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="/docs/5.x/docs/compatibility.html", class=outputUrl === '/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="/docs/5.x/docs/faq.html", class=outputUrl === '/docs/faq.html' ? 'selected' : '') FAQ 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="/docs/5.x/docs/further_reading.html", class=outputUrl === '/docs/further_reading.html' ? 'selected' : '') Further Reading 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="/docs/5.x/docs/enterprise.html", class=outputUrl === '/docs/enterprise.html' ? 'selected' : '') For Enterprise li.pure-menu-item - a.pure-menu-link(href="/docs/built-with-mongoose.html", , class=outputUrl === '/docs/built-with-mongoose.html' ? 'selected' : '') Built with Mongoose + a.pure-menu-link(href="/docs/5.x/docs/built-with-mongoose.html", , class=outputUrl === '/docs/built-with-mongoose.html' ? 'selected' : '') Built with Mongoose div.cpc-ad .container diff --git a/docs/middleware.html b/docs/middleware.html deleted file mode 100644 index ccb1cbcb8b8..00000000000 --- a/docs/middleware.html +++ /dev/null @@ -1,429 +0,0 @@ -Mongoose v5.6.0: Middleware

    Middleware

    - - - - -

    Middleware (also called pre and post hooks) are functions which are passed -control during execution of asynchronous functions. Middleware is specified -on the schema level and is useful for writing plugins.

    - - -

    Types of Middleware

    -

    Mongoose has 4 types -of middleware: document middleware, model middleware, aggregate middleware, and query middleware. -Document middleware is supported for the following document functions. -In document middleware functions, this refers to the document.

    - -

    Query middleware is supported for the following Model and Query functions. -In query middleware functions, this refers to the query.

    - -

    Aggregate middleware is for MyModel.aggregate(). Aggregate middleware -executes when you call exec() on an aggregate object. -In aggregate middleware, this refers to the aggregation object.

    - -

    Model middleware is supported for the following model functions. -In model middleware functions, this refers to the model.

    - -

    All middleware types support pre and post hooks. -How pre and post hooks work is described in more detail below.

    -

    Note: If you specify schema.pre('remove'), Mongoose will register this -middleware for doc.remove() by default. If you -want to your middleware to run on Query.remove() -use schema.pre('remove', { query: true, document: false }, fn).

    -

    Note: The create() function fires save() hooks.

    -

    Pre

    - -

    Pre middleware functions are executed one after another, when each -middleware calls next.

    -
    var schema = new Schema(..);
    -schema.pre('save', function(next) {
    -  // do stuff
    -  next();
    -});
    -

    In mongoose 5.x, instead of calling next() manually, you can use a -function that returns a promise. In particular, you can use async/await.

    -
    schema.pre('save', function() {
    -  return doStuff().
    -    then(() => doMoreStuff());
    -});
    -
    -// Or, in Node.js >= 7.6.0:
    -schema.pre('save', async function() {
    -  await doStuff();
    -  await doMoreStuff();
    -});
    -

    If you use next(), the next() call does not stop the rest of the code in your middleware function from executing. Use -the early return pattern -to prevent the rest of your middleware function from running when you call next().

    -
    var schema = new Schema(..);
    -schema.pre('save', function(next) {
    -  if (foo()) {
    -    console.log('calling next!');
    -    // `return next();` will make sure the rest of this function doesn't run
    -    /*return*/ next();
    -  }
    -  // Unless you comment out the `return` above, 'after next' will print
    -  console.log('after next');
    -});
    -

    Use Cases

    - -

    Middleware are useful for atomizing model logic. Here are some other ideas:

    -
      -
    • complex validation
    • -
    • removing dependent documents (removing a user removes all his blogposts)
    • -
    • asynchronous defaults
    • -
    • asynchronous tasks that a certain action triggers
    • -
    -

    Errors in Pre Hooks

    - -

    If any pre hook errors out, mongoose will not execute subsequent middleware -or the hooked function. Mongoose will instead pass an error to the callback -and/or reject the returned promise. There are several ways to report an -error in middleware:

    -
    schema.pre('save', function(next) {
    -  const err = new Error('something went wrong');
    -  // If you call `next()` with an argument, that argument is assumed to be
    -  // an error.
    -  next(err);
    -});
    -
    -schema.pre('save', function() {
    -  // You can also return a promise that rejects
    -  return new Promise((resolve, reject) => {
    -    reject(new Error('something went wrong'));
    -  });
    -});
    -
    -schema.pre('save', function() {
    -  // You can also throw a synchronous error
    -  throw new Error('something went wrong');
    -});
    -
    -schema.pre('save', async function() {
    -  await Promise.resolve();
    -  // You can also throw an error in an `async` function
    -  throw new Error('something went wrong');
    -});
    -
    -// later...
    -
    -// Changes will not be persisted to MongoDB because a pre hook errored out
    -myDoc.save(function(err) {
    -  console.log(err.message); // something went wrong
    -});
    -

    Calling next() multiple times is a no-op. If you call next() with an -error err1 and then throw an error err2, mongoose will report err1.

    -

    Post middleware

    - -

    post middleware are executed after -the hooked method and all of its pre middleware have completed.

    -
    schema.post('init', function(doc) {
    -  console.log('%s has been initialized from the db', doc._id);
    -});
    -schema.post('validate', function(doc) {
    -  console.log('%s has been validated (but not saved yet)', doc._id);
    -});
    -schema.post('save', function(doc) {
    -  console.log('%s has been saved', doc._id);
    -});
    -schema.post('remove', function(doc) {
    -  console.log('%s has been removed', doc._id);
    -});
    -

    Asynchronous Post Hooks

    - -

    If your post hook function takes at least 2 parameters, mongoose will -assume the second parameter is a next() function that you will call to -trigger the next middleware in the sequence.

    -
    // Takes 2 parameters: this is an asynchronous post hook
    -schema.post('save', function(doc, next) {
    -  setTimeout(function() {
    -    console.log('post1');
    -    // Kick off the second post hook
    -    next();
    -  }, 10);
    -});
    -
    -// Will not execute until the first middleware calls `next()`
    -schema.post('save', function(doc, next) {
    -  console.log('post2');
    -  next();
    -});
    -

    Save/Validate Hooks

    - -

    The save() function triggers validate() hooks, because mongoose -has a built-in pre('save') hook that calls validate(). This means -that all pre('validate') and post('validate') hooks get called -before any pre('save') hooks.

    -
    schema.pre('validate', function() {
    -  console.log('this gets printed first');
    -});
    -schema.post('validate', function() {
    -  console.log('this gets printed second');
    -});
    -schema.pre('save', function() {
    -  console.log('this gets printed third');
    -});
    -schema.post('save', function() {
    -  console.log('this gets printed fourth');
    -});
    -

    Naming Conflicts

    - -

    Mongoose has both query and document hooks for remove().

    -
    schema.pre('remove', function() { console.log('Removing!'); });
    -
    -// Prints "Removing!"
    -doc.remove();
    -
    -// Does **not** print "Removing!". Query middleware for `remove` is not
    -// executed by default.
    -Model.remove();
    -

    You can pass options to Schema.pre() -and Schema.post() to switch whether -Mongoose calls your remove() hook for Document.remove() -or Model.remove():

    -
    // Only document middleware
    -schema.pre('remove', { document: true }, function() {
    -  console.log('Removing doc!');
    -});
    -
    -// Only query middleware. This will get called when you do `Model.remove()`
    -// but not `doc.remove()`.
    -schema.pre('remove', { query: true }, function() {
    -  console.log('Removing!');
    -});
    -

    Notes on findAndUpdate() and Query Middleware

    - -

    Pre and post save() hooks are not executed on update(), -findOneAndUpdate(), etc. You can see a more detailed discussion why in -this GitHub issue. -Mongoose 4.0 introduced distinct hooks for these functions.

    -
    schema.pre('find', function() {
    -  console.log(this instanceof mongoose.Query); // true
    -  this.start = Date.now();
    -});
    -
    -schema.post('find', function(result) {
    -  console.log(this instanceof mongoose.Query); // true
    -  // prints returned documents
    -  console.log('find() returned ' + JSON.stringify(result));
    -  // prints number of milliseconds the query took
    -  console.log('find() took ' + (Date.now() - this.start) + ' millis');
    -});
    -

    Query middleware differs from document middleware in a subtle but -important way: in document middleware, this refers to the document -being updated. In query middleware, mongoose doesn't necessarily have -a reference to the document being updated, so this refers to the -query object rather than the document being updated.

    -

    For instance, if you wanted to add an updatedAt timestamp to every -update() call, you would use the following pre hook.

    -
    schema.pre('update', function() {
    -  this.update({},{ $set: { updatedAt: new Date() } });
    -});
    -

    Error Handling Middleware

    - -

    New in 4.5.0

    -

    Middleware execution normally stops the first time a piece of middleware -calls next() with an error. However, there is a special kind of post -middleware called "error handling middleware" that executes specifically -when an error occurs. Error handling middleware is useful for reporting -errors and making error messages more readable.

    -

    Error handling middleware is defined as middleware that takes one extra -parameter: the 'error' that occurred as the first parameter to the function. -Error handling middleware can then transform the error however you want.

    -
    var schema = new Schema({
    -  name: {
    -    type: String,
    -    // Will trigger a MongoError with code 11000 when
    -    // you save a duplicate
    -    unique: true
    -  }
    -});
    -
    -// Handler **must** take 3 parameters: the error that occurred, the document
    -// in question, and the `next()` function
    -schema.post('save', function(error, doc, next) {
    -  if (error.name === 'MongoError' && error.code === 11000) {
    -    next(new Error('There was a duplicate key error'));
    -  } else {
    -    next();
    -  }
    -});
    -
    -// Will trigger the `post('save')` error handler
    -Person.create([{ name: 'Axl Rose' }, { name: 'Axl Rose' }]);
    -

    Error handling middleware also works with query middleware. You can -also define a post update() hook that will catch MongoDB duplicate key -errors.

    -
    // The same E11000 error can occur when you call `update()`
    -// This function **must** take 3 parameters. If you use the
    -// `passRawResult` function, this function **must** take 4
    -// parameters
    -schema.post('update', function(error, res, next) {
    -  if (error.name === 'MongoError' && error.code === 11000) {
    -    next(new Error('There was a duplicate key error'));
    -  } else {
    -    next(); // The `update()` call will still error out.
    -  }
    -});
    -
    -var people = [{ name: 'Axl Rose' }, { name: 'Slash' }];
    -Person.create(people, function(error) {
    -  Person.update({ name: 'Slash' }, { $set: { name: 'Axl Rose' } }, function(error) {
    -    // `error.message` will be "There was a duplicate key error"
    -  });
    -});
    -

    Error handling middleware can transform an error, but it can't remove the -error. Even if you call next() with no error as shown above, the -function call will still error out.

    -

    Aggregation Hooks

    - -

    You can also define hooks for the Model.aggregate() function. -In aggregation middleware functions, this refers to the Mongoose Aggregate object. -For example, suppose you're implementing soft deletes on a Customer model -by adding an isDeleted property. To make sure aggregate() calls only look -at customers that aren't soft deleted, you can use the below middleware to -add a $match stage to the beginning -of each aggregation pipeline.

    -
    customerSchema.pre('aggregate', function() {
    -  // Add a $match state to the beginning of each pipeline.
    -  this.pipeline().unshift({ $match: { isDeleted: { $ne: true } } });
    -});
    -

    The Aggregate#pipeline() function -lets you access the MongoDB aggregation pipeline that Mongoose will send to -the MongoDB server. It is useful for adding stages to the beginning of the -pipeline from middleware.

    -

    Synchronous Hooks

    - -

    Certain Mongoose hooks are synchronous, which means they do not support -functions that return promises or receive a next() callback. Currently, -only init hooks are synchronous, because the init() function -is synchronous. Below is an example of using pre and post init hooks.

    -
    const schema = new Schema({ title: String, loadedAt: Date });
    -
    -schema.pre('init', pojo => {
    -  assert.equal(pojo.constructor.name, 'Object'); // Plain object before init
    -});
    -
    -const now = new Date();
    -schema.post('init', doc => {
    -  assert.ok(doc instanceof mongoose.Document); // Mongoose doc after init
    -  doc.loadedAt = now;
    -});
    -
    -const Test = db.model('TestPostInitMiddleware', schema);
    -
    -return Test.create({ title: 'Casino Royale' }).
    -  then(doc => Test.findById(doc)).
    -  then(doc => assert.equal(doc.loadedAt.valueOf(), now.valueOf()));
    -

    To report an error in an init hook, you must throw a synchronous error. -Unlike all other middleware, init middleware does not handle promise -rejections.

    -
    const schema = new Schema({ title: String });
    -
    -const swallowedError = new Error('will not show');
    -// init hooks do **not** handle async errors or any sort of async behavior
    -schema.pre('init', () => Promise.reject(swallowedError));
    -schema.post('init', () => { throw Error('will show'); });
    -
    -const Test = db.model('PostInitBook', schema);
    -
    -return Test.create({ title: 'Casino Royale' }).
    -  then(doc => Test.findById(doc)).
    -  catch(error => assert.equal(error.message, 'will show'));
    -

    Next Up

    - -

    Now that we've covered middleware, let's take a look at Mongoose's approach -to faking JOINs with its query population helper.

    -
    \ No newline at end of file diff --git a/docs/middleware.md b/docs/middleware.md new file mode 100644 index 00000000000..be5bcec8075 --- /dev/null +++ b/docs/middleware.md @@ -0,0 +1,503 @@ +## Middleware + +Middleware (also called pre and post *hooks*) are functions which are passed +control during execution of asynchronous functions. Middleware is specified +on the schema level and is useful for writing [plugins](./plugins.html). + + + +### Types of Middleware + +Mongoose has 4 types +of middleware: document middleware, model middleware, aggregate middleware, and query middleware. +Document middleware is supported for the following document functions. +In document middleware functions, `this` refers to the document. + +* [validate](/docs/api/document.html#document_Document-validate) +* [save](/docs/api/model.html#model_Model-save) +* [remove](/docs/api/model.html#model_Model-remove) +* [updateOne](/docs/api/document.html#document_Document-updateOne) +* [deleteOne](/docs/api/model.html#model_Model-deleteOne) +* [init](/docs/api/document.html#document_Document-init) (note: init hooks are [synchronous](#synchronous)) + +Query middleware is supported for the following Model and Query functions. +In query middleware functions, `this` refers to the query. + +* [count](./api.html#query_Query-count) +* [deleteMany](./api.html#query_Query-deleteMany) +* [deleteOne](./api.html#query_Query-deleteOne) +* [find](./api.html#query_Query-find) +* [findOne](./api.html#query_Query-findOne) +* [findOneAndDelete](./api.html#query_Query-findOneAndDelete) +* [findOneAndRemove](./api.html#query_Query-findOneAndRemove) +* [findOneAndUpdate](./api.html#query_Query-findOneAndUpdate) +* [remove](./api.html#model_Model.remove) +* [update](./api.html#query_Query-update) +* [updateOne](./api.html#query_Query-updateOne) +* [updateMany](./api.html#query_Query-updateMany) + +Aggregate middleware is for `MyModel.aggregate()`. Aggregate middleware +executes when you call `exec()` on an aggregate object. +In aggregate middleware, `this` refers to the [aggregation object](./api.html#model_Model.aggregate). + +* [aggregate](./api.html#model_Model.aggregate) + +Model middleware is supported for the following model functions. +In model middleware functions, `this` refers to the model. + +* [insertMany](./api.html#model_Model.insertMany) + +All middleware types support pre and post hooks. +How pre and post hooks work is described in more detail below. + +**Note:** If you specify `schema.pre('remove')`, Mongoose will register this +middleware for [`doc.remove()`](./api.html#model_Model-remove) by default. If you +want to your middleware to run on [`Query.remove()`](./api.html#query_Query-remove) +use [`schema.pre('remove', { query: true, document: false }, fn)`](./api.html#schema_Schema-pre). + +**Note:** Unlike `schema.pre('remove')`, Mongoose registers `updateOne` and +`deleteOne` middleware on `Query#updateOne()` and `Query#deleteOne()` by default. +This means that both `doc.updateOne()` and `Model.updateOne()` trigger +`updateOne` hooks, but `this` refers to a query, not a document. To register +`updateOne` or `deleteOne` middleware as document middleware, use +`schema.pre('updateOne', { document: true, query: false })`. + +**Note:** The [`create()`](./api.html#model_Model.create) function fires `save()` hooks. + +

    Pre

    + +Pre middleware functions are executed one after another, when each +middleware calls `next`. + +```javascript +const schema = new Schema(..); +schema.pre('save', function(next) { + // do stuff + next(); +}); +``` + +In [mongoose 5.x](http://thecodebarbarian.com/introducing-mongoose-5.html#promises-and-async-await-with-middleware), instead of calling `next()` manually, you can use a +function that returns a promise. In particular, you can use [`async/await`](http://thecodebarbarian.com/common-async-await-design-patterns-in-node.js.html). + +```javascript +schema.pre('save', function() { + return doStuff(). + then(() => doMoreStuff()); +}); + +// Or, in Node.js >= 7.6.0: +schema.pre('save', async function() { + await doStuff(); + await doMoreStuff(); +}); +``` + +If you use `next()`, the `next()` call does **not** stop the rest of the code in your middleware function from executing. Use +[the early `return` pattern](https://www.bennadel.com/blog/2323-use-a-return-statement-when-invoking-callbacks-especially-in-a-guard-statement.htm) +to prevent the rest of your middleware function from running when you call `next()`. + +```javascript +const schema = new Schema(..); +schema.pre('save', function(next) { + if (foo()) { + console.log('calling next!'); + // `return next();` will make sure the rest of this function doesn't run + /*return*/ next(); + } + // Unless you comment out the `return` above, 'after next' will print + console.log('after next'); +}); +``` + +

    Use Cases

    + +Middleware are useful for atomizing model logic. Here are some other ideas: + +* complex validation +* removing dependent documents (removing a user removes all their blogposts) +* asynchronous defaults +* asynchronous tasks that a certain action triggers + +

    Errors in Pre Hooks

    + +If any pre hook errors out, mongoose will not execute subsequent middleware +or the hooked function. Mongoose will instead pass an error to the callback +and/or reject the returned promise. There are several ways to report an +error in middleware: + +```javascript +schema.pre('save', function(next) { + const err = new Error('something went wrong'); + // If you call `next()` with an argument, that argument is assumed to be + // an error. + next(err); +}); + +schema.pre('save', function() { + // You can also return a promise that rejects + return new Promise((resolve, reject) => { + reject(new Error('something went wrong')); + }); +}); + +schema.pre('save', function() { + // You can also throw a synchronous error + throw new Error('something went wrong'); +}); + +schema.pre('save', async function() { + await Promise.resolve(); + // You can also throw an error in an `async` function + throw new Error('something went wrong'); +}); + +// later... + +// Changes will not be persisted to MongoDB because a pre hook errored out +myDoc.save(function(err) { + console.log(err.message); // something went wrong +}); +``` + +Calling `next()` multiple times is a no-op. If you call `next()` with an +error `err1` and then throw an error `err2`, mongoose will report `err1`. + +

    Post middleware

    + +[post](/docs/api.html#schema_Schema-post) middleware are executed _after_ +the hooked method and all of its `pre` middleware have completed. + +```javascript +schema.post('init', function(doc) { + console.log('%s has been initialized from the db', doc._id); +}); +schema.post('validate', function(doc) { + console.log('%s has been validated (but not saved yet)', doc._id); +}); +schema.post('save', function(doc) { + console.log('%s has been saved', doc._id); +}); +schema.post('remove', function(doc) { + console.log('%s has been removed', doc._id); +}); +``` + +

    Asynchronous Post Hooks

    + +If your post hook function takes at least 2 parameters, mongoose will +assume the second parameter is a `next()` function that you will call to +trigger the next middleware in the sequence. + +```javascript +// Takes 2 parameters: this is an asynchronous post hook +schema.post('save', function(doc, next) { + setTimeout(function() { + console.log('post1'); + // Kick off the second post hook + next(); + }, 10); +}); + +// Will not execute until the first middleware calls `next()` +schema.post('save', function(doc, next) { + console.log('post2'); + next(); +}); +``` + +

    Define Middleware Before Compiling Models

    + +Calling `pre()` or `post()` after [compiling a model](/docs/models.html#compiling) +does **not** work in Mongoose in general. For example, the below `pre('save')` +middleware will not fire. + +```javascript +const schema = new mongoose.Schema({ name: String }); + +// Compile a model from the schema +const User = mongoose.model('User', schema); + +// Mongoose will **not** call the middleware function, because +// this middleware was defined after the model was compiled +schema.pre('save', () => console.log('Hello from pre save')); + +new User({ name: 'test' }).save(); +``` + +This means that you must add all middleware and [plugins](/docs/plugins.html) +**before** calling [`mongoose.model()`](/docs/api/mongoose.html#mongoose_Mongoose-model). +The below script will print out "Hello from pre save": + +```javascript +const schema = new mongoose.Schema({ name: String }); +// Mongoose will call this middleware function, because this script adds +// the middleware to the schema before compiling the model. +schema.pre('save', () => console.log('Hello from pre save')); + +// Compile a model from the schema +const User = mongoose.model('User', schema); + +new User({ name: 'test' }).save(); +``` + +As a consequence, be careful about exporting Mongoose models from the same +file that you define your schema. If you choose to use this pattern, you +must define [global plugins](/docs/api/mongoose.html#mongoose_Mongoose-plugin) +**before** calling `require()` on your model file. + +```javascript +const schema = new mongoose.Schema({ name: String }); + +// Once you `require()` this file, you can no longer add any middleware +// to this schema. +module.exports = mongoose.model('User', schema); +``` + +

    Save/Validate Hooks

    + +The `save()` function triggers `validate()` hooks, because mongoose +has a built-in `pre('save')` hook that calls `validate()`. This means +that all `pre('validate')` and `post('validate')` hooks get called +**before** any `pre('save')` hooks. + +```javascript +schema.pre('validate', function() { + console.log('this gets printed first'); +}); +schema.post('validate', function() { + console.log('this gets printed second'); +}); +schema.pre('save', function() { + console.log('this gets printed third'); +}); +schema.post('save', function() { + console.log('this gets printed fourth'); +}); +``` + +

    Naming Conflicts

    + +Mongoose has both query and document hooks for `remove()`. + +```javascript +schema.pre('remove', function() { console.log('Removing!'); }); + +// Prints "Removing!" +doc.remove(); + +// Does **not** print "Removing!". Query middleware for `remove` is not +// executed by default. +Model.remove(); +``` + +You can pass options to [`Schema.pre()`](/docs/api.html#schema_Schema-pre) +and [`Schema.post()`](/docs/api.html#schema_Schema-post) to switch whether +Mongoose calls your `remove()` hook for [`Document.remove()`](/docs/api.html#model_Model-remove) +or [`Model.remove()`](/docs/api.html#model_Model.remove). Note here that you need to set both `document` and `query` properties in the passed object: + +```javascript +// Only document middleware +schema.pre('remove', { document: true, query: false }, function() { + console.log('Removing doc!'); +}); + +// Only query middleware. This will get called when you do `Model.remove()` +// but not `doc.remove()`. +schema.pre('remove', { query: true, document: false }, function() { + console.log('Removing!'); +}); +``` + +

    Notes on findAndUpdate() and Query Middleware

    + +Pre and post `save()` hooks are **not** executed on `update()`, +`findOneAndUpdate()`, etc. You can see a more detailed discussion why in +[this GitHub issue](http://github.com/Automattic/mongoose/issues/964). +Mongoose 4.0 introduced distinct hooks for these functions. + +```javascript +schema.pre('find', function() { + console.log(this instanceof mongoose.Query); // true + this.start = Date.now(); +}); + +schema.post('find', function(result) { + console.log(this instanceof mongoose.Query); // true + // prints returned documents + console.log('find() returned ' + JSON.stringify(result)); + // prints number of milliseconds the query took + console.log('find() took ' + (Date.now() - this.start) + ' millis'); +}); +``` + +Query middleware differs from document middleware in a subtle but +important way: in document middleware, `this` refers to the document +being updated. In query middleware, mongoose doesn't necessarily have +a reference to the document being updated, so `this` refers to the +**query** object rather than the document being updated. + +For instance, if you wanted to add an `updatedAt` timestamp to every +`updateOne()` call, you would use the following pre hook. + +```javascript +schema.pre('updateOne', function() { + this.set({ updatedAt: new Date() }); +}); +``` + +You **cannot** access the document being updated in `pre('updateOne')` or +`pre('findOneAndUpdate')` query middleware. If you need to access the document +that will be updated, you need to execute an explicit query for the document. + +```javascript +schema.pre('findOneAndUpdate', async function() { + const docToUpdate = await this.model.findOne(this.getQuery()); + console.log(docToUpdate); // The document that `findOneAndUpdate()` will modify +}); +``` + +However, if you define `pre('updateOne')` document middleware, +`this` will be the document being updated. That's because `pre('updateOne')` +document middleware hooks into [`Document#updateOne()`](/docs/api/document.html#document_Document-updateOne) +rather than `Query#updateOne()`. + +```javascript +schema.pre('updateOne', { document: true, query: false }, function() { + console.log('Updating'); +}); +const Model = mongoose.model('Test', schema); + +const doc = new Model(); +await doc.updateOne({ $set: { name: 'test' } }); // Prints "Updating" + +// Doesn't print "Updating", because `Query#updateOne()` doesn't fire +// document middleware. +await Model.updateOne({}, { $set: { name: 'test' } }); +``` + +

    Error Handling Middleware

    + +_New in 4.5.0_ + +Middleware execution normally stops the first time a piece of middleware +calls `next()` with an error. However, there is a special kind of post +middleware called "error handling middleware" that executes specifically +when an error occurs. Error handling middleware is useful for reporting +errors and making error messages more readable. + +Error handling middleware is defined as middleware that takes one extra +parameter: the 'error' that occurred as the first parameter to the function. +Error handling middleware can then transform the error however you want. + +```javascript +const schema = new Schema({ + name: { + type: String, + // Will trigger a MongoError with code 11000 when + // you save a duplicate + unique: true + } +}); + +// Handler **must** take 3 parameters: the error that occurred, the document +// in question, and the `next()` function +schema.post('save', function(error, doc, next) { + if (error.name === 'MongoError' && error.code === 11000) { + next(new Error('There was a duplicate key error')); + } else { + next(); + } +}); + +// Will trigger the `post('save')` error handler +Person.create([{ name: 'Axl Rose' }, { name: 'Axl Rose' }]); +``` + +Error handling middleware also works with query middleware. You can +also define a post `update()` hook that will catch MongoDB duplicate key +errors. + +```javascript +// The same E11000 error can occur when you call `update()` +// This function **must** take 3 parameters. If you use the +// `passRawResult` function, this function **must** take 4 +// parameters +schema.post('update', function(error, res, next) { + if (error.name === 'MongoError' && error.code === 11000) { + next(new Error('There was a duplicate key error')); + } else { + next(); // The `update()` call will still error out. + } +}); + +const people = [{ name: 'Axl Rose' }, { name: 'Slash' }]; +Person.create(people, function(error) { + Person.update({ name: 'Slash' }, { $set: { name: 'Axl Rose' } }, function(error) { + // `error.message` will be "There was a duplicate key error" + }); +}); +``` + +Error handling middleware can transform an error, but it can't remove the +error. Even if you call `next()` with no error as shown above, the +function call will still error out. + +

    Aggregation Hooks

    + +You can also define hooks for the [`Model.aggregate()` function](api.html#model_Model.aggregate). +In aggregation middleware functions, `this` refers to the [Mongoose `Aggregate` object](api.html#Aggregate). +For example, suppose you're implementing soft deletes on a `Customer` model +by adding an `isDeleted` property. To make sure `aggregate()` calls only look +at customers that aren't soft deleted, you can use the below middleware to +add a [`$match` stage](api.html#aggregate_Aggregate-match) to the beginning +of each [aggregation pipeline](https://docs.mongodb.com/manual/core/aggregation-pipeline/). + +```javascript +customerSchema.pre('aggregate', function() { + // Add a $match state to the beginning of each pipeline. + this.pipeline().unshift({ $match: { isDeleted: { $ne: true } } }); +}); +``` + +The [`Aggregate#pipeline()` function](api.html#aggregate_Aggregate-pipeline) +lets you access the MongoDB aggregation pipeline that Mongoose will send to +the MongoDB server. It is useful for adding stages to the beginning of the +pipeline from middleware. + +

    Synchronous Hooks

    + +Certain Mongoose hooks are synchronous, which means they do **not** support +functions that return promises or receive a `next()` callback. Currently, +only `init` hooks are synchronous, because the [`init()` function](./api.html#document_Document-init) +is synchronous. Below is an example of using pre and post init hooks. + +```javascript +[require:post init hooks.*success] +``` + +To report an error in an init hook, you must throw a **synchronous** error. +Unlike all other middleware, init middleware does **not** handle promise +rejections. + +```javascript +[require:post init hooks.*error] +``` + +

    Next Up

    + +Now that we've covered middleware, let's take a look at Mongoose's approach +to faking JOINs with its query [population](/docs/populate.html) helper. diff --git a/docs/middleware.pug b/docs/middleware.pug deleted file mode 100644 index 862d7462a0b..00000000000 --- a/docs/middleware.pug +++ /dev/null @@ -1,525 +0,0 @@ -extends layout - -append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown - ## Middleware - - - - - - Middleware (also called pre and post *hooks*) are functions which are passed - control during execution of asynchronous functions. Middleware is specified - on the schema level and is useful for writing [plugins](./plugins.html). - - - - ### Types of Middleware - - Mongoose has 4 types - of middleware: document middleware, model middleware, aggregate middleware, and query middleware. - Document middleware is supported for the following document functions. - In document middleware functions, `this` refers to the document. - - * [validate](/docs/api/document.html#document_Document-validate) - * [save](/docs/api/model.html#model_Model-save) - * [remove](/docs/api/model.html#model_Model-remove) - * [updateOne](/docs/api/document.html#document_Document-updateOne) - * [deleteOne](/docs/api/model.html#model_Model-deleteOne) - * [init](/docs/api/document.html#document_Document-init) (note: init hooks are [synchronous](#synchronous)) - - Query middleware is supported for the following Model and Query functions. - In query middleware functions, `this` refers to the query. - - * [count](./api.html#query_Query-count) - * [deleteMany](./api.html#query_Query-deleteMany) - * [deleteOne](./api.html#query_Query-deleteOne) - * [find](./api.html#query_Query-find) - * [findOne](./api.html#query_Query-findOne) - * [findOneAndDelete](./api.html#query_Query-findOneAndDelete) - * [findOneAndRemove](./api.html#query_Query-findOneAndRemove) - * [findOneAndUpdate](./api.html#query_Query-findOneAndUpdate) - * [remove](./api.html#model_Model.remove) - * [update](./api.html#query_Query-update) - * [updateOne](./api.html#query_Query-updateOne) - * [updateMany](./api.html#query_Query-updateMany) - - Aggregate middleware is for `MyModel.aggregate()`. Aggregate middleware - executes when you call `exec()` on an aggregate object. - In aggregate middleware, `this` refers to the [aggregation object](./api.html#model_Model.aggregate). - - * [aggregate](./api.html#model_Model.aggregate) - - Model middleware is supported for the following model functions. - In model middleware functions, `this` refers to the model. - - * [insertMany](./api.html#model_Model.insertMany) - - All middleware types support pre and post hooks. - How pre and post hooks work is described in more detail below. - - **Note:** If you specify `schema.pre('remove')`, Mongoose will register this - middleware for [`doc.remove()`](./api.html#model_Model-remove) by default. If you - want to your middleware to run on [`Query.remove()`](./api.html#query_Query-remove) - use [`schema.pre('remove', { query: true, document: false }, fn)`](./api.html#schema_Schema-pre). - - **Note:** Unlike `schema.pre('remove')`, Mongoose registers `updateOne` and - `deleteOne` middleware on `Query#updateOne()` and `Query#deleteOne()` by default. - This means that both `doc.updateOne()` and `Model.updateOne()` trigger - `updateOne` hooks, but `this` refers to a query, not a document. To register - `updateOne` or `deleteOne` middleware as document middleware, use - `schema.pre('updateOne', { document: true, query: false })`. - - **Note:** The [`create()`](./api.html#model_Model.create) function fires `save()` hooks. - -

    Pre

    - - Pre middleware functions are executed one after another, when each - middleware calls `next`. - - ```javascript - const schema = new Schema(..); - schema.pre('save', function(next) { - // do stuff - next(); - }); - ``` - - In [mongoose 5.x](http://thecodebarbarian.com/introducing-mongoose-5.html#promises-and-async-await-with-middleware), instead of calling `next()` manually, you can use a - function that returns a promise. In particular, you can use [`async/await`](http://thecodebarbarian.com/common-async-await-design-patterns-in-node.js.html). - - ```javascript - schema.pre('save', function() { - return doStuff(). - then(() => doMoreStuff()); - }); - - // Or, in Node.js >= 7.6.0: - schema.pre('save', async function() { - await doStuff(); - await doMoreStuff(); - }); - ``` - - If you use `next()`, the `next()` call does **not** stop the rest of the code in your middleware function from executing. Use - [the early `return` pattern](https://www.bennadel.com/blog/2323-use-a-return-statement-when-invoking-callbacks-especially-in-a-guard-statement.htm) - to prevent the rest of your middleware function from running when you call `next()`. - - ```javascript - const schema = new Schema(..); - schema.pre('save', function(next) { - if (foo()) { - console.log('calling next!'); - // `return next();` will make sure the rest of this function doesn't run - /*return*/ next(); - } - // Unless you comment out the `return` above, 'after next' will print - console.log('after next'); - }); - ``` - -

    Use Cases

    - - Middleware are useful for atomizing model logic. Here are some other ideas: - - * complex validation - * removing dependent documents (removing a user removes all their blogposts) - * asynchronous defaults - * asynchronous tasks that a certain action triggers - -

    Errors in Pre Hooks

    - - If any pre hook errors out, mongoose will not execute subsequent middleware - or the hooked function. Mongoose will instead pass an error to the callback - and/or reject the returned promise. There are several ways to report an - error in middleware: - - ```javascript - schema.pre('save', function(next) { - const err = new Error('something went wrong'); - // If you call `next()` with an argument, that argument is assumed to be - // an error. - next(err); - }); - - schema.pre('save', function() { - // You can also return a promise that rejects - return new Promise((resolve, reject) => { - reject(new Error('something went wrong')); - }); - }); - - schema.pre('save', function() { - // You can also throw a synchronous error - throw new Error('something went wrong'); - }); - - schema.pre('save', async function() { - await Promise.resolve(); - // You can also throw an error in an `async` function - throw new Error('something went wrong'); - }); - - // later... - - // Changes will not be persisted to MongoDB because a pre hook errored out - myDoc.save(function(err) { - console.log(err.message); // something went wrong - }); - ``` - - Calling `next()` multiple times is a no-op. If you call `next()` with an - error `err1` and then throw an error `err2`, mongoose will report `err1`. - -

    Post middleware

    - - [post](/docs/api.html#schema_Schema-post) middleware are executed _after_ - the hooked method and all of its `pre` middleware have completed. - - ```javascript - schema.post('init', function(doc) { - console.log('%s has been initialized from the db', doc._id); - }); - schema.post('validate', function(doc) { - console.log('%s has been validated (but not saved yet)', doc._id); - }); - schema.post('save', function(doc) { - console.log('%s has been saved', doc._id); - }); - schema.post('remove', function(doc) { - console.log('%s has been removed', doc._id); - }); - ``` - -

    Asynchronous Post Hooks

    - - If your post hook function takes at least 2 parameters, mongoose will - assume the second parameter is a `next()` function that you will call to - trigger the next middleware in the sequence. - - ```javascript - // Takes 2 parameters: this is an asynchronous post hook - schema.post('save', function(doc, next) { - setTimeout(function() { - console.log('post1'); - // Kick off the second post hook - next(); - }, 10); - }); - - // Will not execute until the first middleware calls `next()` - schema.post('save', function(doc, next) { - console.log('post2'); - next(); - }); - ``` - -

    Define Middleware Before Compiling Models

    - - Calling `pre()` or `post()` after [compiling a model](/docs/models.html#compiling) - does **not** work in Mongoose in general. For example, the below `pre('save')` - middleware will not fire. - - ```javascript - const schema = new mongoose.Schema({ name: String }); - - // Compile a model from the schema - const User = mongoose.model('User', schema); - - // Mongoose will **not** call the middleware function, because - // this middleware was defined after the model was compiled - schema.pre('save', () => console.log('Hello from pre save')); - - new User({ name: 'test' }).save(); - ``` - - This means that you must add all middleware and [plugins](/docs/plugins.html) - **before** calling [`mongoose.model()`](/docs/api/mongoose.html#mongoose_Mongoose-model). - The below script will print out "Hello from pre save": - - ```javascript - const schema = new mongoose.Schema({ name: String }); - // Mongoose will call this middleware function, because this script adds - // the middleware to the schema before compiling the model. - schema.pre('save', () => console.log('Hello from pre save')); - - // Compile a model from the schema - const User = mongoose.model('User', schema); - - new User({ name: 'test' }).save(); - ``` - - As a consequence, be careful about exporting Mongoose models from the same - file that you define your schema. If you choose to use this pattern, you - must define [global plugins](/docs/api/mongoose.html#mongoose_Mongoose-plugin) - **before** calling `require()` on your model file. - - ```javascript - const schema = new mongoose.Schema({ name: String }); - - // Once you `require()` this file, you can no longer add any middleware - // to this schema. - module.exports = mongoose.model('User', schema); - ``` - -

    Save/Validate Hooks

    - - The `save()` function triggers `validate()` hooks, because mongoose - has a built-in `pre('save')` hook that calls `validate()`. This means - that all `pre('validate')` and `post('validate')` hooks get called - **before** any `pre('save')` hooks. - - ```javascript - schema.pre('validate', function() { - console.log('this gets printed first'); - }); - schema.post('validate', function() { - console.log('this gets printed second'); - }); - schema.pre('save', function() { - console.log('this gets printed third'); - }); - schema.post('save', function() { - console.log('this gets printed fourth'); - }); - ``` - -

    Naming Conflicts

    - - Mongoose has both query and document hooks for `remove()`. - - ```javascript - schema.pre('remove', function() { console.log('Removing!'); }); - - // Prints "Removing!" - doc.remove(); - - // Does **not** print "Removing!". Query middleware for `remove` is not - // executed by default. - Model.remove(); - ``` - - You can pass options to [`Schema.pre()`](/docs/api.html#schema_Schema-pre) - and [`Schema.post()`](/docs/api.html#schema_Schema-post) to switch whether - Mongoose calls your `remove()` hook for [`Document.remove()`](/docs/api.html#model_Model-remove) - or [`Model.remove()`](/docs/api.html#model_Model.remove). Note here that you need to set both `document` and `query` properties in the passed object: - - ```javascript - // Only document middleware - schema.pre('remove', { document: true, query: false }, function() { - console.log('Removing doc!'); - }); - - // Only query middleware. This will get called when you do `Model.remove()` - // but not `doc.remove()`. - schema.pre('remove', { query: true, document: false }, function() { - console.log('Removing!'); - }); - ``` - -

    Notes on findAndUpdate() and Query Middleware

    - - Pre and post `save()` hooks are **not** executed on `update()`, - `findOneAndUpdate()`, etc. You can see a more detailed discussion why in - [this GitHub issue](http://github.com/Automattic/mongoose/issues/964). - Mongoose 4.0 introduced distinct hooks for these functions. - - ```javascript - schema.pre('find', function() { - console.log(this instanceof mongoose.Query); // true - this.start = Date.now(); - }); - - schema.post('find', function(result) { - console.log(this instanceof mongoose.Query); // true - // prints returned documents - console.log('find() returned ' + JSON.stringify(result)); - // prints number of milliseconds the query took - console.log('find() took ' + (Date.now() - this.start) + ' millis'); - }); - ``` - - Query middleware differs from document middleware in a subtle but - important way: in document middleware, `this` refers to the document - being updated. In query middleware, mongoose doesn't necessarily have - a reference to the document being updated, so `this` refers to the - **query** object rather than the document being updated. - - For instance, if you wanted to add an `updatedAt` timestamp to every - `updateOne()` call, you would use the following pre hook. - - ```javascript - schema.pre('updateOne', function() { - this.set({ updatedAt: new Date() }); - }); - ``` - - You **cannot** access the document being updated in `pre('updateOne')` or - `pre('findOneAndUpdate')` query middleware. If you need to access the document - that will be updated, you need to execute an explicit query for the document. - - ```javascript - schema.pre('findOneAndUpdate', async function() { - const docToUpdate = await this.model.findOne(this.getQuery()); - console.log(docToUpdate); // The document that `findOneAndUpdate()` will modify - }); - ``` - - However, if you define `pre('updateOne')` document middleware, - `this` will be the document being updated. That's because `pre('updateOne')` - document middleware hooks into [`Document#updateOne()`](/docs/api/document.html#document_Document-updateOne) - rather than `Query#updateOne()`. - - ```javascript - schema.pre('updateOne', { document: true, query: false }, function() { - console.log('Updating'); - }); - const Model = mongoose.model('Test', schema); - - const doc = new Model(); - await doc.updateOne({ $set: { name: 'test' } }); // Prints "Updating" - - // Doesn't print "Updating", because `Query#updateOne()` doesn't fire - // document middleware. - await Model.updateOne({}, { $set: { name: 'test' } }); - ``` - -

    Error Handling Middleware

    - - _New in 4.5.0_ - - Middleware execution normally stops the first time a piece of middleware - calls `next()` with an error. However, there is a special kind of post - middleware called "error handling middleware" that executes specifically - when an error occurs. Error handling middleware is useful for reporting - errors and making error messages more readable. - - Error handling middleware is defined as middleware that takes one extra - parameter: the 'error' that occurred as the first parameter to the function. - Error handling middleware can then transform the error however you want. - - ```javascript - const schema = new Schema({ - name: { - type: String, - // Will trigger a MongoError with code 11000 when - // you save a duplicate - unique: true - } - }); - - // Handler **must** take 3 parameters: the error that occurred, the document - // in question, and the `next()` function - schema.post('save', function(error, doc, next) { - if (error.name === 'MongoError' && error.code === 11000) { - next(new Error('There was a duplicate key error')); - } else { - next(); - } - }); - - // Will trigger the `post('save')` error handler - Person.create([{ name: 'Axl Rose' }, { name: 'Axl Rose' }]); - ``` - - Error handling middleware also works with query middleware. You can - also define a post `update()` hook that will catch MongoDB duplicate key - errors. - - ```javascript - // The same E11000 error can occur when you call `update()` - // This function **must** take 3 parameters. If you use the - // `passRawResult` function, this function **must** take 4 - // parameters - schema.post('update', function(error, res, next) { - if (error.name === 'MongoError' && error.code === 11000) { - next(new Error('There was a duplicate key error')); - } else { - next(); // The `update()` call will still error out. - } - }); - - const people = [{ name: 'Axl Rose' }, { name: 'Slash' }]; - Person.create(people, function(error) { - Person.update({ name: 'Slash' }, { $set: { name: 'Axl Rose' } }, function(error) { - // `error.message` will be "There was a duplicate key error" - }); - }); - ``` - - Error handling middleware can transform an error, but it can't remove the - error. Even if you call `next()` with no error as shown above, the - function call will still error out. - -

    Aggregation Hooks

    - - You can also define hooks for the [`Model.aggregate()` function](api.html#model_Model.aggregate). - In aggregation middleware functions, `this` refers to the [Mongoose `Aggregate` object](api.html#Aggregate). - For example, suppose you're implementing soft deletes on a `Customer` model - by adding an `isDeleted` property. To make sure `aggregate()` calls only look - at customers that aren't soft deleted, you can use the below middleware to - add a [`$match` stage](api.html#aggregate_Aggregate-match) to the beginning - of each [aggregation pipeline](https://docs.mongodb.com/manual/core/aggregation-pipeline/). - - ```javascript - customerSchema.pre('aggregate', function() { - // Add a $match state to the beginning of each pipeline. - this.pipeline().unshift({ $match: { isDeleted: { $ne: true } } }); - }); - ``` - - The [`Aggregate#pipeline()` function](api.html#aggregate_Aggregate-pipeline) - lets you access the MongoDB aggregation pipeline that Mongoose will send to - the MongoDB server. It is useful for adding stages to the beginning of the - pipeline from middleware. - -

    Synchronous Hooks

    - - Certain Mongoose hooks are synchronous, which means they do **not** support - functions that return promises or receive a `next()` callback. Currently, - only `init` hooks are synchronous, because the [`init()` function](./api.html#document_Document-init) - is synchronous. Below is an example of using pre and post init hooks. - - ```javascript - [require:post init hooks.*success] - ``` - - To report an error in an init hook, you must throw a **synchronous** error. - Unlike all other middleware, init middleware does **not** handle promise - rejections. - - ```javascript - [require:post init hooks.*error] - ``` - -

    Next Up

    - - Now that we've covered middleware, let's take a look at Mongoose's approach - to faking JOINs with its query [population](/docs/populate.html) helper. diff --git a/docs/migrating_to_5.html b/docs/migrating_to_5.html deleted file mode 100644 index 72098c9fd83..00000000000 --- a/docs/migrating_to_5.html +++ /dev/null @@ -1,493 +0,0 @@ -Mongoose v5.6.0: Migrating to Mongoose 5

    Migrating from 4.x to 5.x

    - - - - -

    There are several backwards-breaking changes -you should be aware of when migrating from Mongoose 4.x to Mongoose 5.x.

    -

    If you're still on Mongoose 3.x, please read the Mongoose 3.x to 4.x migration guide.

    - -

    Version Requirements

    - -

    Mongoose now requires Node.js >= 4.0.0 and MongoDB >= 3.0.0. -MongoDB 2.6 and -Node.js < 4 where both EOL-ed in 2016.

    -

    Query Middleware

    - -

    Query middleware is now compiled when you call mongoose.model() or db.model(). If you add query middleware after calling mongoose.model(), that middleware will not get called.

    -
    const schema = new Schema({ name: String });
    -const MyModel = mongoose.model('Test', schema);
    -schema.pre('find', () => { console.log('find!'); });
    -
    -MyModel.find().exec(function() {
    -  // In mongoose 4.x, the above `.find()` will print "find!"
    -  // In mongoose 5.x, "find!" will **not** be printed.
    -  // Call `pre('find')` **before** calling `mongoose.model()` to make the middleware apply.
    -});
    -

    - Promises and Callbacks for `mongoose.connect()` -

    - -

    mongoose.connect() and mongoose.disconnect() now return a promise if no callback specified, or null otherwise. It does not return the mongoose singleton.

    -
    // Worked in mongoose 4. Does **not** work in mongoose 5, `mongoose.connect()`
    -// now returns a promise consistently. This is to avoid the horrible things
    -// we've done to allow mongoose to be a thenable that resolves to itself.
    -mongoose.connect('mongodb://localhost:27017/test').model('Test', new Schema({}));
    -
    -// Do this instead
    -mongoose.connect('mongodb://localhost:27017/test');
    -mongoose.model('Test', new Schema({}));
    -

    - Connection Logic and `useMongoClient` -

    - -

    The useMongoClient option was -removed in Mongoose 5, it is now always true. As a consequence, Mongoose 5 -no longer supports several function signatures for mongoose.connect() that -worked in Mongoose 4.x if the useMongoClient option was off. Below are some -examples of mongoose.connect() calls that do not work in Mongoose 5.x.

    -
      -
    • mongoose.connect('localhost', 27017);
    • -
    • mongoose.connect('localhost', 'mydb', 27017);
    • -
    • mongoose.connect('mongodb://host1:27017,mongodb://host2:27017');
    • -
    -

    In Mongoose 5.x, the first parameter to mongoose.connect() and mongoose.createConnection(), if specified, must be a MongoDB connection string. The -connection string and options are then passed down to the MongoDB Node.js driver's MongoClient.connect() function. Mongoose does not modify the connection string, although mongoose.connect() and mongoose.createConnection() support a few additional options in addition to the ones the MongoDB driver supports.

    -

    - Setter Order -

    - -

    Setters run in reverse order in 4.x:

    -
    const schema = new Schema({ name: String });
    -schema.path('name').
    -  set(() => console.log('This will print 2nd')).
    -  set(() => console.log('This will print first'));
    -

    In 5.x, setters run in the order they're declared.

    -
    const schema = new Schema({ name: String });
    -schema.path('name').
    -  set(() => console.log('This will print first')).
    -  set(() => console.log('This will print 2nd'));
    -

    - Checking if a path is populated -

    - -

    Mongoose 5.1.0 introduced an _id getter to ObjectIds that lets you get an ObjectId regardless of whether a path -is populated.

    -
    const blogPostSchema = new Schema({
    -  title: String,
    -  author: {
    -    type: mongoose.Schema.Types.ObjectId,
    -    ref: 'Author'
    -  }
    -});
    -const BlogPost = mongoose.model('BlogPost', blogPostSchema);
    -
    -await BlogPost.create({ title: 'test', author: author._id });
    -const blogPost = await BlogPost.findOne();
    -
    -console.log(blogPost.author); // '5b207f84e8061d1d2711b421'
    -// New in Mongoose 5.1.0: this will print '5b207f84e8061d1d2711b421' as well
    -console.log(blogPost.author._id);
    -
    -await blogPost.populate('author');
    -console.log(blogPost.author._id); '5b207f84e8061d1d2711b421'
    -

    As a consequence, checking whether blogPost.author._id is no longer viable as a way to check whether author is populated. Use blogPost.populated('author') != null or blogPost.author instanceof mongoose.Types.ObjectId to check whether author is populated instead.

    -

    Note that you can call mongoose.set('objectIdGetter', false) to change this behavior.

    -

    - Return Values for `remove()` and `deleteX()` -

    - -

    deleteOne(), deleteMany(), and remove() now resolve to the result object -rather than the full driver WriteOpResult object.

    -
    // In 4.x, this is how you got the number of documents deleted
    -MyModel.deleteMany().then(res => console.log(res.result.n));
    -// In 5.x this is how you get the number of documents deleted
    -MyModel.deleteMany().then(res => res.n);
    -

    - Aggregation Cursors -

    - -

    The useMongooseAggCursor option from 4.x is now always on. This is the new syntax for aggregation cursors in mongoose 5:

    -
    // When you call `.cursor()`, `.exec()` will now return a mongoose aggregation
    -// cursor.
    -const cursor = MyModel.aggregate([{ $match: { name: 'Val' } }]).cursor().exec();
    -// No need to `await` on the cursor or wait for a promise to resolve
    -cursor.eachAsync(doc => console.log(doc));
    -
    -// Can also pass options to `cursor()`
    -const cursorWithOptions = MyModel.
    -  aggregate([{ $match: { name: 'Val' } }]).
    -  cursor({ batchSize: 10 }).
    -  exec();
    -

    - geoNear -

    - -

    Model.geoNear() has been removed because the MongoDB driver no longer supports it

    -

    - Required URI encoding of connection strings -

    - -

    Due to changes in the MongoDB driver, connection strings must be URI encoded.

    -

    If they are not, connections may fail with an illegal character message.

    -

    - Passwords which contain certain characters -

    - -

    See a full list of affected characters.

    -

    If your app is used by a lot of different connection strings, it's possible -that your test cases will pass, but production passwords will fail. Encode all your connection -strings to be safe.

    -

    If you want to continue to use unencoded connection strings, the easiest fix is to use -the mongodb-uri module to parse the connection strings, and then produce the properly encoded -versions. You can use a function like this:

    -
    const uriFormat = require('mongodb-uri')
    -function encodeMongoURI (urlString) {
    -    if (urlString) {
    -      let parsed = uriFormat.parse(urlString)
    -      urlString = uriFormat.format(parsed);
    -    }
    -    return urlString;
    -  }
    -}
    -
    -// Your un-encoded string.
    -const mongodbConnectString = "mongodb://...";
    -mongoose.connect(encodeMongoURI(mongodbConnectString))
    -

    The function above is safe to use whether the existing string is already encoded or not.

    -

    - Domain sockets -

    - -

    Domain sockets must be URI encoded. For example:

    -
    // Works in mongoose 4. Does **not** work in mongoose 5 because of more
    -// stringent URI parsing.
    -const host = '/tmp/mongodb-27017.sock';
    -mongoose.createConnection(`mongodb://aaron:psw@${host}/fake`);
    -
    -// Do this instead
    -const host = encodeURIComponent('/tmp/mongodb-27017.sock');
    -mongoose.createConnection(`mongodb://aaron:psw@${host}/fake`);
    -

    - `toObject()` Options -

    - -

    The options parameter to toObject() and toJSON() merge defaults rather than overwriting them.

    -
    // Note the `toObject` option below
    -const schema = new Schema({ name: String }, { toObject: { virtuals: true } });
    -schema.virtual('answer').get(() => 42);
    -const MyModel = db.model('MyModel', schema);
    -
    -const doc = new MyModel({ name: 'test' });
    -// In mongoose 4.x this prints "undefined", because `{ minimize: false }`
    -// overwrites the entire schema-defined options object.
    -// In mongoose 5.x this prints "42", because `{ minimize: false }` gets
    -// merged with the schema-defined options.
    -console.log(doc.toJSON({ minimize: false }).answer);
    -

    - Aggregate Parameters -

    - -

    aggregate() no longer accepts a spread, you must pass your aggregation pipeline as an array. The below code worked in 4.x:

    -
    MyModel.aggregate({ $match: { isDeleted: false } }, { $skip: 10 }).exec(cb);
    -

    The above code does not work in 5.x, you must wrap the $match and $skip stages in an array.

    -
    MyModel.aggregate([{ $match: { isDeleted: false } }, { $skip: 10 }]).exec(cb);
    -

    - Boolean Casting -

    - -

    By default, mongoose 4 would coerce any value to a boolean without error.

    -
    // Fine in mongoose 4, would save a doc with `boolField = true`
    -const MyModel = mongoose.model('Test', new Schema({
    -  boolField: Boolean
    -}));
    -
    -MyModel.create({ boolField: 'not a boolean' });
    -

    Mongoose 5 only casts the following values to true:

    -
      -
    • true
    • -
    • 'true'
    • -
    • 1
    • -
    • '1'
    • -
    • 'yes'
    • -
    -

    And the following values to false:

    -
      -
    • false
    • -
    • 'false'
    • -
    • 0
    • -
    • '0'
    • -
    • 'no'
    • -
    -

    All other values will cause a CastError

    -

    - Query Casting -

    - -

    Casting for update(), updateOne(), updateMany(), replaceOne(), -remove(), deleteOne(), and deleteMany() doesn't happen until exec(). -This makes it easier for hooks and custom query helpers to modify data, because -mongoose won't restructure the data you passed in until after your hooks and -query helpers have ran. It also makes it possible to set the overwrite option -after passing in an update.

    -
    // In mongoose 4.x, this becomes `{ $set: { name: 'Baz' } }` despite the `overwrite`
    -// In mongoose 5.x, this overwrite is respected and the first document with
    -// `name = 'Bar'` will be replaced with `{ name: 'Baz' }`
    -User.where({ name: 'Bar' }).update({ name: 'Baz' }).setOptions({ overwrite: true });
    -

    - Post Save Hooks Get Flow Control -

    - -

    Post hooks now get flow control, which means async post save hooks and child document post save hooks execute before your save() callback.

    -
    const ChildModelSchema = new mongoose.Schema({
    -  text: {
    -    type: String
    -  }
    -});
    -ChildModelSchema.post('save', function(doc) {
    -  // In mongoose 5.x this will print **before** the `console.log()`
    -  // in the `save()` callback. In mongoose 4.x this was reversed.
    -  console.log('Child post save');
    -});
    -const ParentModelSchema = new mongoose.Schema({
    -  children: [ChildModelSchema]
    -});
    -
    -const Model = mongoose.model('Parent', ParentModelSchema);
    -const m = new Model({ children: [{ text: 'test' }] });
    -m.save(function() {
    -  // In mongoose 5.xm this prints **after** the "Child post save" message.
    -  console.log('Save callback');
    -});
    -

    - The `$pushAll` Operator -

    - -

    $pushAll is no longer supported and no longer used internally for save(), since it has been deprecated since MongoDB 2.4. Use $push with $each instead.

    -

    - Always Use Forward Key Order -

    - -

    The retainKeyOrder option was removed, mongoose will now always retain the same key position when cloning objects. If you have queries or indexes that rely on reverse key order, you will have to change them.

    -

    - Run setters on queries -

    - -

    Setters now run on queries by default, and the old runSettersOnQuery option -has been removed.

    -
    const schema = new Schema({
    -  email: { type: String, lowercase: true }
    -});
    -const Model = mongoose.model('Test', schema);
    -Model.find({ email: 'FOO@BAR.BAZ' }); // Converted to `find({ email: 'foo@bar.baz' })`
    -

    - Pre-compiled Browser Bundle -

    - -

    We no longer have a pre-compiled version of mongoose for the browser. If you want to use mongoose schemas in the browser, you need to build your own bundle with browserify/webpack.

    -

    - Save Errors -

    - -

    The saveErrorIfNotFound option was removed, mongoose will now always error out from save() if the underlying document was not found

    -

    - Init hook signatures -

    - -

    init hooks are now fully synchronous and do not receive next() as a parameter.

    -

    Document.prototype.init() no longer takes a callback as a parameter. It -was always synchronous, just had a callback for legacy reasons.

    -

    - `numAffected` and `save()` -

    - -

    doc.save() no longer passes numAffected as a 3rd param to its callback.

    -

    - `remove()` and debouncing -

    - -

    doc.remove() no longer debounces

    -

    - `getPromiseConstructor()` -

    - -

    getPromiseConstructor() is gone, just use mongoose.Promise.

    -

    - Passing Parameters from Pre Hooks -

    - -

    You cannot pass parameters to the next pre middleware in the chain using next() in mongoose 5.x. In mongoose 4, next('Test') in pre middleware would call the -next middleware with 'Test' as a parameter. Mongoose 5.x has removed support for this.

    -

    - `required` validator for arrays -

    - -

    In mongoose 5 the required validator only verifies if the value is an -array. That is, it will not fail for empty arrays as it would in -mongoose 4.

    -

    - debug output defaults to stdout instead of stderr -

    - -

    In mongoose 5 the default debug function uses console.info() to display messages instead of console.error().

    -

    - Overwriting filter properties -

    - -

    In Mongoose 4.x, overwriting a filter property that's a primitive with one that is an object would silently fail. For example, the below code would ignore the where() and be equivalent to Sport.find({ name: 'baseball' })

    -
    Sport.find({ name: 'baseball' }).where({name: {$ne: 'softball'}});
    -

    In Mongoose 5.x, the above code will correctly overwrite 'baseball' with { $ne: 'softball' }

    -

    - `bulkWrite()` results -

    - -

    Mongoose 5.x uses version 3.x of the MongoDB Node.js driver. MongoDB driver 3.x changed the format of -the result of bulkWrite() calls so there is no longer a top-level nInserted, nModified, etc. property. The new result object structure is described here.

    -
    const Model = mongoose.model('Test', new Schema({ name: String }));
    -
    -const res = await Model.bulkWrite([{ insertOne: { document: { name: 'test' } } }]);
    -
    -console.log(res);
    -

    In Mongoose 4.x, the above will print:

    -
    BulkWriteResult {
    -  ok: [Getter],
    -  nInserted: [Getter],
    -  nUpserted: [Getter],
    -  nMatched: [Getter],
    -  nModified: [Getter],
    -  nRemoved: [Getter],
    -  getInsertedIds: [Function],
    -  getUpsertedIds: [Function],
    -  getUpsertedIdAt: [Function],
    -  getRawResponse: [Function],
    -  hasWriteErrors: [Function],
    -  getWriteErrorCount: [Function],
    -  getWriteErrorAt: [Function],
    -  getWriteErrors: [Function],
    -  getLastOp: [Function],
    -  getWriteConcernError: [Function],
    -  toJSON: [Function],
    -  toString: [Function],
    -  isOk: [Function],
    -  insertedCount: 1,
    -  matchedCount: 0,
    -  modifiedCount: 0,
    -  deletedCount: 0,
    -  upsertedCount: 0,
    -  upsertedIds: {},
    -  insertedIds: { '0': 5be9a3101638a066702a0d38 },
    -  n: 1 }

    In Mongoose 5.x, the script will print:

    -
    BulkWriteResult {
    -  result: 
    -  { ok: 1,
    -    writeErrors: [],
    -    writeConcernErrors: [],
    -    insertedIds: [ [Object] ],
    -    nInserted: 1,
    -    nUpserted: 0,
    -    nMatched: 0,
    -    nModified: 0,
    -    nRemoved: 0,
    -    upserted: [],
    -    lastOp: { ts: [Object], t: 1 } },
    -  insertedCount: 1,
    -  matchedCount: 0,
    -  modifiedCount: 0,
    -  deletedCount: 0,
    -  upsertedCount: 0,
    -  upsertedIds: {},
    -  insertedIds: { '0': 5be9a1c87decfc6443dd9f18 },
    -  n: 1 }
    \ No newline at end of file diff --git a/docs/migrating_to_5.md b/docs/migrating_to_5.md new file mode 100644 index 00000000000..53048ab69e9 --- /dev/null +++ b/docs/migrating_to_5.md @@ -0,0 +1,548 @@ +## Migrating from 4.x to 5.x + +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. + +If you're still on Mongoose 3.x, please read the [Mongoose 3.x to 4.x migration guide](migration.html). + +* [Version Requirements](#version-requirements) +* [Query Middleware](#query-middleware) +* [Promises and Callbacks for `mongoose.connect()`](#promises-and-callbacks) +* [Connection Logic and `useMongoClient`](#connection-logic) +* [Setter Order](#setter-order) +* [Checking if a path is populated](#id-getter) +* [Return Values for `remove()` and `deleteX()`](#return-value-for-delete) +* [Aggregation Cursors](#aggregation-cursors) +* [geoNear](#geonear) +* [Required URI encoding of connection strings](#uri-encoding) +* [Passwords which contain certain characters](#password-characters) +* [Domain sockets](#domain-sockets) +* [`toObject()` Options](#toobject-options) +* [Aggregate Parameters](#aggregate-parameters) +* [Boolean Casting](#boolean-casting) +* [Query Casting](#query-casting) +* [Post Save Hooks Get Flow Control](#post-save-flow-control) +* [The `$pushAll` Operator](#pushall) +* [Always Use Forward Key Order](#retain-key-order) +* [Run setters on queries](#run-setters-on-queries) +* [Pre-compiled Browser Bundle](#browser-bundle) +* [Save Errors](#save-errors) +* [Init hook signatures](#init-hooks) +* [`numAffected` and `save()`](#save-num-affected) +* [`remove()` and debouncing](#remove-debounce) +* [`getPromiseConstructor()`](#get-promise-constructor) +* [Passing Parameters from Pre Hooks](#pre-hook-params) +* [`required` validator for arrays](#array-required) +* [debug output defaults to stdout instead of stderr](#debug-output) +* [Overwriting filter properties](#overwrite-filter) +* [`bulkWrite()` results](#bulkwrite-results) +* [Strict SSL validation](#strict-ssl-validation) + +

    Version Requirements

    + +Mongoose now requires Node.js >= 4.0.0 and MongoDB >= 3.0.0. +[MongoDB 2.6](https://www.mongodb.com/blog/post/mongodb-2-6-end-of-life) and +[Node.js < 4](https://github.com/nodejs/Release) where both EOL-ed in 2016. + +

    Query Middleware

    + +Query middleware is now compiled when you call `mongoose.model()` or `db.model()`. If you add query middleware after calling `mongoose.model()`, that middleware will **not** get called. + +```javascript +const schema = new Schema({ name: String }); +const MyModel = mongoose.model('Test', schema); +schema.pre('find', () => { console.log('find!'); }); + +MyModel.find().exec(function() { + // In mongoose 4.x, the above `.find()` will print "find!" + // In mongoose 5.x, "find!" will **not** be printed. + // Call `pre('find')` **before** calling `mongoose.model()` to make the middleware apply. +}); +``` + +

    + Promises and Callbacks for `mongoose.connect()` +

    + +`mongoose.connect()` and `mongoose.disconnect()` now return a promise if no callback specified, or `null` otherwise. It does **not** return the mongoose singleton. + +```javascript +// Worked in mongoose 4. Does **not** work in mongoose 5, `mongoose.connect()` +// now returns a promise consistently. This is to avoid the horrible things +// we've done to allow mongoose to be a thenable that resolves to itself. +mongoose.connect('mongodb://localhost:27017/test').model('Test', new Schema({})); + +// Do this instead +mongoose.connect('mongodb://localhost:27017/test'); +mongoose.model('Test', new Schema({})); +``` + +

    + Connection Logic and `useMongoClient` +

    + +The [`useMongoClient` option](/docs/4.x/docs/connections.html#use-mongo-client) was +removed in Mongoose 5, it is now always `true`. As a consequence, Mongoose 5 +no longer supports several function signatures for `mongoose.connect()` that +worked in Mongoose 4.x if the `useMongoClient` option was off. Below are some +examples of `mongoose.connect()` calls that do **not** work in Mongoose 5.x. + +* `mongoose.connect('localhost', 27017);` +* `mongoose.connect('localhost', 'mydb', 27017);` +* `mongoose.connect('mongodb://host1:27017,mongodb://host2:27017');` + +In Mongoose 5.x, the first parameter to `mongoose.connect()` and `mongoose.createConnection()`, if specified, **must** be a [MongoDB connection string](https://docs.mongodb.com/manual/reference/connection-string/). The +connection string and options are then passed down to [the MongoDB Node.js driver's `MongoClient.connect()` function](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html#.connect). Mongoose does not modify the connection string, although `mongoose.connect()` and `mongoose.createConnection()` support a [few additional options in addition to the ones the MongoDB driver supports](http://mongoosejs.com/docs/connections.html#options). + +

    + Setter Order +

    + +Setters run in reverse order in 4.x: + +```javascript +const schema = new Schema({ name: String }); +schema.path('name'). + set(() => console.log('This will print 2nd')). + set(() => console.log('This will print first')); +``` + +In 5.x, setters run in the order they're declared. + +```javascript +const schema = new Schema({ name: String }); +schema.path('name'). + set(() => console.log('This will print first')). + set(() => console.log('This will print 2nd')); +``` + +

    + Checking if a path is populated +

    + +Mongoose 5.1.0 introduced an `_id` getter to ObjectIds that lets you get an ObjectId regardless of whether a path +is populated. + +```javascript +const blogPostSchema = new Schema({ + title: String, + author: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Author' + } +}); +const BlogPost = mongoose.model('BlogPost', blogPostSchema); + +await BlogPost.create({ title: 'test', author: author._id }); +const blogPost = await BlogPost.findOne(); + +console.log(blogPost.author); // '5b207f84e8061d1d2711b421' +// New in Mongoose 5.1.0: this will print '5b207f84e8061d1d2711b421' as well +console.log(blogPost.author._id); + +await blogPost.populate('author'); +console.log(blogPost.author._id); '5b207f84e8061d1d2711b421' +``` + +As a consequence, checking whether `blogPost.author._id` is [no longer viable as a way to check whether `author` is populated](https://github.com/Automattic/mongoose/issues/6415#issuecomment-388579185). Use `blogPost.populated('author') != null` or `blogPost.author instanceof mongoose.Types.ObjectId` to check whether `author` is populated instead. + +Note that you can call `mongoose.set('objectIdGetter', false)` to change this behavior. + +

    + Return Values for `remove()` and `deleteX()` +

    + +`deleteOne()`, `deleteMany()`, and `remove()` now resolve to the result object +rather than the full [driver `WriteOpResult` object](http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~writeOpCallback). + +```javascript +// In 4.x, this is how you got the number of documents deleted +MyModel.deleteMany().then(res => console.log(res.result.n)); +// In 5.x this is how you get the number of documents deleted +MyModel.deleteMany().then(res => res.n); +``` + +

    + Aggregation Cursors +

    + +The `useMongooseAggCursor` option from 4.x is now always on. This is the new syntax for aggregation cursors in mongoose 5: + +```javascript +// When you call `.cursor()`, `.exec()` will now return a mongoose aggregation +// cursor. +const cursor = MyModel.aggregate([{ $match: { name: 'Val' } }]).cursor().exec(); +// No need to `await` on the cursor or wait for a promise to resolve +cursor.eachAsync(doc => console.log(doc)); + +// Can also pass options to `cursor()` +const cursorWithOptions = MyModel. + aggregate([{ $match: { name: 'Val' } }]). + cursor({ batchSize: 10 }). + exec(); +``` + +

    + geoNear +

    + +`Model.geoNear()` has been removed because the [MongoDB driver no longer supports it](https://github.com/mongodb/node-mongodb-native/blob/master/CHANGES_3.0.0.md#geonear-command-helper) + +

    + Required URI encoding of connection strings +

    + +Due to changes in the MongoDB driver, connection strings must be URI encoded. + +If they are not, connections may fail with an illegal character message. + +

    + Passwords which contain certain characters +

    + +See a [full list of affected characters](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding). + +If your app is used by a lot of different connection strings, it's possible +that your test cases will pass, but production passwords will fail. Encode all your connection +strings to be safe. + +If you want to continue to use unencoded connection strings, the easiest fix is to use +the `mongodb-uri` module to parse the connection strings, and then produce the properly encoded +versions. You can use a function like this: + +```javascript +const uriFormat = require('mongodb-uri') +function encodeMongoURI (urlString) { + if (urlString) { + let parsed = uriFormat.parse(urlString) + urlString = uriFormat.format(parsed); + } + return urlString; + } +} + +// Your un-encoded string. +const mongodbConnectString = "mongodb://..."; +mongoose.connect(encodeMongoURI(mongodbConnectString)) +``` + +The function above is safe to use whether the existing string is already encoded or not. + +

    + Domain sockets +

    + +Domain sockets must be URI encoded. For example: + +```javascript +// Works in mongoose 4. Does **not** work in mongoose 5 because of more +// stringent URI parsing. +const host = '/tmp/mongodb-27017.sock'; +mongoose.createConnection(`mongodb://aaron:psw@${host}/fake`); + +// Do this instead +const host = encodeURIComponent('/tmp/mongodb-27017.sock'); +mongoose.createConnection(`mongodb://aaron:psw@${host}/fake`); +``` + +

    + `toObject()` Options +

    + +The `options` parameter to `toObject()` and `toJSON()` merge defaults rather than overwriting them. + +```javascript +// Note the `toObject` option below +const schema = new Schema({ name: String }, { toObject: { virtuals: true } }); +schema.virtual('answer').get(() => 42); +const MyModel = db.model('MyModel', schema); + +const doc = new MyModel({ name: 'test' }); +// In mongoose 4.x this prints "undefined", because `{ minimize: false }` +// overwrites the entire schema-defined options object. +// In mongoose 5.x this prints "42", because `{ minimize: false }` gets +// merged with the schema-defined options. +console.log(doc.toJSON({ minimize: false }).answer); +``` + +

    + Aggregate Parameters +

    + +`aggregate()` no longer accepts a spread, you **must** pass your aggregation pipeline as an array. The below code worked in 4.x: + +```javascript +MyModel.aggregate({ $match: { isDeleted: false } }, { $skip: 10 }).exec(cb); +``` + +The above code does **not** work in 5.x, you **must** wrap the `$match` and `$skip` stages in an array. + +```javascript +MyModel.aggregate([{ $match: { isDeleted: false } }, { $skip: 10 }]).exec(cb); +``` + +

    + Boolean Casting +

    + +By default, mongoose 4 would coerce any value to a boolean without error. + +```javascript +// Fine in mongoose 4, would save a doc with `boolField = true` +const MyModel = mongoose.model('Test', new Schema({ + boolField: Boolean +})); + +MyModel.create({ boolField: 'not a boolean' }); +``` + +Mongoose 5 only casts the following values to `true`: + +* `true` +* `'true'` +* `1` +* `'1'` +* `'yes'` + +And the following values to `false`: + +* `false` +* `'false'` +* `0` +* `'0'` +* `'no'` + +All other values will cause a `CastError` + +

    + Query Casting +

    + +Casting for `update()`, `updateOne()`, `updateMany()`, `replaceOne()`, +`remove()`, `deleteOne()`, and `deleteMany()` doesn't happen until `exec()`. +This makes it easier for hooks and custom query helpers to modify data, because +mongoose won't restructure the data you passed in until after your hooks and +query helpers have ran. It also makes it possible to set the `overwrite` option +_after_ passing in an update. + +```javascript +// In mongoose 4.x, this becomes `{ $set: { name: 'Baz' } }` despite the `overwrite` +// In mongoose 5.x, this overwrite is respected and the first document with +// `name = 'Bar'` will be replaced with `{ name: 'Baz' }` +User.where({ name: 'Bar' }).update({ name: 'Baz' }).setOptions({ overwrite: true }); +``` + +

    + Post Save Hooks Get Flow Control +

    + +Post hooks now get flow control, which means async post save hooks and child document post save hooks execute **before** your `save()` callback. + +```javascript +const ChildModelSchema = new mongoose.Schema({ + text: { + type: String + } +}); +ChildModelSchema.post('save', function(doc) { + // In mongoose 5.x this will print **before** the `console.log()` + // in the `save()` callback. In mongoose 4.x this was reversed. + console.log('Child post save'); +}); +const ParentModelSchema = new mongoose.Schema({ + children: [ChildModelSchema] +}); + +const Model = mongoose.model('Parent', ParentModelSchema); +const m = new Model({ children: [{ text: 'test' }] }); +m.save(function() { + // In mongoose 5.xm this prints **after** the "Child post save" message. + console.log('Save callback'); +}); +``` + +

    + The `$pushAll` Operator +

    + +`$pushAll` is no longer supported and no longer used internally for `save()`, since it has been [deprecated since MongoDB 2.4](https://docs.mongodb.com/manual/reference/operator/update/pushAll/). Use `$push` with `$each` instead. + +

    + Always Use Forward Key Order +

    + +The `retainKeyOrder` option was removed, mongoose will now always retain the same key position when cloning objects. If you have queries or indexes that rely on reverse key order, you will have to change them. + +

    + Run setters on queries +

    + +Setters now run on queries by default, and the old `runSettersOnQuery` option +has been removed. + +```javascript +const schema = new Schema({ + email: { type: String, lowercase: true } +}); +const Model = mongoose.model('Test', schema); +Model.find({ email: 'FOO@BAR.BAZ' }); // Converted to `find({ email: 'foo@bar.baz' })` +``` + +

    + Pre-compiled Browser Bundle +

    + +We no longer have a pre-compiled version of mongoose for the browser. If you want to use mongoose schemas in the browser, you need to build your own bundle with browserify/webpack. + +

    + Save Errors +

    + +The `saveErrorIfNotFound` option was removed, mongoose will now always error out from `save()` if the underlying document was not found + +

    + Init hook signatures +

    + +`init` hooks are now fully synchronous and do not receive `next()` as a parameter. + +`Document.prototype.init()` no longer takes a callback as a parameter. It +was always synchronous, just had a callback for legacy reasons. + +

    + `numAffected` and `save()` +

    + +`doc.save()` no longer passes `numAffected` as a 3rd param to its callback. + +

    + `remove()` and debouncing +

    + +`doc.remove()` no longer debounces + +

    + `getPromiseConstructor()` +

    + +`getPromiseConstructor()` is gone, just use `mongoose.Promise`. + +

    + Passing Parameters from Pre Hooks +

    + +You cannot pass parameters to the next pre middleware in the chain using `next()` in mongoose 5.x. In mongoose 4, `next('Test')` in pre middleware would call the +next middleware with 'Test' as a parameter. Mongoose 5.x has removed support for this. + +

    + `required` validator for arrays +

    + +In mongoose 5 the `required` validator only verifies if the value is an +array. That is, it will **not** fail for _empty_ arrays as it would in +mongoose 4. + +

    + debug output defaults to stdout instead of stderr +

    + +In mongoose 5 the default debug function uses `console.info()` to display messages instead of `console.error()`. + +

    + Overwriting filter properties +

    + +In Mongoose 4.x, overwriting a filter property that's a primitive with one that is an object would silently fail. For example, the below code would ignore the `where()` and be equivalent to `Sport.find({ name: 'baseball' })` + +```javascript +Sport.find({ name: 'baseball' }).where({name: {$ne: 'softball'}}); +``` + +In Mongoose 5.x, the above code will correctly overwrite `'baseball'` with `{ $ne: 'softball' }` + +

    + `bulkWrite()` results +

    + +Mongoose 5.x uses version 3.x of the [MongoDB Node.js driver](http://npmjs.com/package/mongodb). MongoDB driver 3.x changed the format of +the result of [`bulkWrite()` calls](/docs/api.html#model_Model.bulkWrite) so there is no longer a top-level `nInserted`, `nModified`, etc. property. The new result object structure is [described here](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#~BulkWriteOpResult). + +```javascript +const Model = mongoose.model('Test', new Schema({ name: String })); + +const res = await Model.bulkWrite([{ insertOne: { document: { name: 'test' } } }]); + +console.log(res); +``` + +In Mongoose 4.x, the above will print: + +``` +BulkWriteResult { + ok: [Getter], + nInserted: [Getter], + nUpserted: [Getter], + nMatched: [Getter], + nModified: [Getter], + nRemoved: [Getter], + getInsertedIds: [Function], + getUpsertedIds: [Function], + getUpsertedIdAt: [Function], + getRawResponse: [Function], + hasWriteErrors: [Function], + getWriteErrorCount: [Function], + getWriteErrorAt: [Function], + getWriteErrors: [Function], + getLastOp: [Function], + getWriteConcernError: [Function], + toJSON: [Function], + toString: [Function], + isOk: [Function], + insertedCount: 1, + matchedCount: 0, + modifiedCount: 0, + deletedCount: 0, + upsertedCount: 0, + upsertedIds: {}, + insertedIds: { '0': 5be9a3101638a066702a0d38 }, + n: 1 } +``` + +In Mongoose 5.x, the script will print: + +``` +BulkWriteResult { + result: + { ok: 1, + writeErrors: [], + writeConcernErrors: [], + insertedIds: [ [Object] ], + nInserted: 1, + nUpserted: 0, + nMatched: 0, + nModified: 0, + nRemoved: 0, + upserted: [], + lastOp: { ts: [Object], t: 1 } }, + insertedCount: 1, + matchedCount: 0, + modifiedCount: 0, + deletedCount: 0, + upsertedCount: 0, + upsertedIds: {}, + insertedIds: { '0': 5be9a1c87decfc6443dd9f18 }, + n: 1 } +``` + +

    + Strict SSL Validation +

    + +The most recent versions of the [MongoDB Node.js driver use strict SSL validation by default](http://mongodb.github.io/node-mongodb-native/3.5/tutorials/connect/tls/), +which may lead to errors if you're using [self-signed certificates](https://github.com/Automattic/mongoose/issues/9147). + +If this is blocking you from upgrading, you can set the `tlsInsecure` option to `true`. + +```javascript +mongoose.connect(uri, { tlsInsecure: false }); // Opt out of additional SSL validation +``` \ No newline at end of file diff --git a/docs/migrating_to_5.pug b/docs/migrating_to_5.pug deleted file mode 100644 index 9d675af697c..00000000000 --- a/docs/migrating_to_5.pug +++ /dev/null @@ -1,572 +0,0 @@ -extends layout - -block append style - style. - table td { - padding-right: 15px; - } - -append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - :markdown - ## Migrating from 4.x to 5.x - - - - - - 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. - - If you're still on Mongoose 3.x, please read the [Mongoose 3.x to 4.x migration guide](migration.html). - - * [Version Requirements](#version-requirements) - * [Query Middleware](#query-middleware) - * [Promises and Callbacks for `mongoose.connect()`](#promises-and-callbacks) - * [Connection Logic and `useMongoClient`](#connection-logic) - * [Setter Order](#setter-order) - * [Checking if a path is populated](#id-getter) - * [Return Values for `remove()` and `deleteX()`](#return-value-for-delete) - * [Aggregation Cursors](#aggregation-cursors) - * [geoNear](#geonear) - * [Required URI encoding of connection strings](#uri-encoding) - * [Passwords which contain certain characters](#password-characters) - * [Domain sockets](#domain-sockets) - * [`toObject()` Options](#toobject-options) - * [Aggregate Parameters](#aggregate-parameters) - * [Boolean Casting](#boolean-casting) - * [Query Casting](#query-casting) - * [Post Save Hooks Get Flow Control](#post-save-flow-control) - * [The `$pushAll` Operator](#pushall) - * [Always Use Forward Key Order](#retain-key-order) - * [Run setters on queries](#run-setters-on-queries) - * [Pre-compiled Browser Bundle](#browser-bundle) - * [Save Errors](#save-errors) - * [Init hook signatures](#init-hooks) - * [`numAffected` and `save()`](#save-num-affected) - * [`remove()` and debouncing](#remove-debounce) - * [`getPromiseConstructor()`](#get-promise-constructor) - * [Passing Parameters from Pre Hooks](#pre-hook-params) - * [`required` validator for arrays](#array-required) - * [debug output defaults to stdout instead of stderr](#debug-output) - * [Overwriting filter properties](#overwrite-filter) - * [`bulkWrite()` results](#bulkwrite-results) - * [Strict SSL validation](#strict-ssl-validation) - -

    Version Requirements

    - - Mongoose now requires Node.js >= 4.0.0 and MongoDB >= 3.0.0. - [MongoDB 2.6](https://www.mongodb.com/blog/post/mongodb-2-6-end-of-life) and - [Node.js < 4](https://github.com/nodejs/Release) where both EOL-ed in 2016. - -

    Query Middleware

    - - Query middleware is now compiled when you call `mongoose.model()` or `db.model()`. If you add query middleware after calling `mongoose.model()`, that middleware will **not** get called. - - ```javascript - const schema = new Schema({ name: String }); - const MyModel = mongoose.model('Test', schema); - schema.pre('find', () => { console.log('find!'); }); - - MyModel.find().exec(function() { - // In mongoose 4.x, the above `.find()` will print "find!" - // In mongoose 5.x, "find!" will **not** be printed. - // Call `pre('find')` **before** calling `mongoose.model()` to make the middleware apply. - }); - ``` - -

    - Promises and Callbacks for `mongoose.connect()` -

    - - `mongoose.connect()` and `mongoose.disconnect()` now return a promise if no callback specified, or `null` otherwise. It does **not** return the mongoose singleton. - - ```javascript - // Worked in mongoose 4. Does **not** work in mongoose 5, `mongoose.connect()` - // now returns a promise consistently. This is to avoid the horrible things - // we've done to allow mongoose to be a thenable that resolves to itself. - mongoose.connect('mongodb://localhost:27017/test').model('Test', new Schema({})); - - // Do this instead - mongoose.connect('mongodb://localhost:27017/test'); - mongoose.model('Test', new Schema({})); - ``` - -

    - Connection Logic and `useMongoClient` -

    - - The [`useMongoClient` option](/docs/4.x/docs/connections.html#use-mongo-client) was - removed in Mongoose 5, it is now always `true`. As a consequence, Mongoose 5 - no longer supports several function signatures for `mongoose.connect()` that - worked in Mongoose 4.x if the `useMongoClient` option was off. Below are some - examples of `mongoose.connect()` calls that do **not** work in Mongoose 5.x. - - * `mongoose.connect('localhost', 27017);` - * `mongoose.connect('localhost', 'mydb', 27017);` - * `mongoose.connect('mongodb://host1:27017,mongodb://host2:27017');` - - In Mongoose 5.x, the first parameter to `mongoose.connect()` and `mongoose.createConnection()`, if specified, **must** be a [MongoDB connection string](https://docs.mongodb.com/manual/reference/connection-string/). The - connection string and options are then passed down to [the MongoDB Node.js driver's `MongoClient.connect()` function](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html#.connect). Mongoose does not modify the connection string, although `mongoose.connect()` and `mongoose.createConnection()` support a [few additional options in addition to the ones the MongoDB driver supports](http://mongoosejs.com/docs/connections.html#options). - -

    - Setter Order -

    - - Setters run in reverse order in 4.x: - - ```javascript - const schema = new Schema({ name: String }); - schema.path('name'). - set(() => console.log('This will print 2nd')). - set(() => console.log('This will print first')); - ``` - - In 5.x, setters run in the order they're declared. - - ```javascript - const schema = new Schema({ name: String }); - schema.path('name'). - set(() => console.log('This will print first')). - set(() => console.log('This will print 2nd')); - ``` - -

    - Checking if a path is populated -

    - - Mongoose 5.1.0 introduced an `_id` getter to ObjectIds that lets you get an ObjectId regardless of whether a path - is populated. - - ```javascript - const blogPostSchema = new Schema({ - title: String, - author: { - type: mongoose.Schema.Types.ObjectId, - ref: 'Author' - } - }); - const BlogPost = mongoose.model('BlogPost', blogPostSchema); - - await BlogPost.create({ title: 'test', author: author._id }); - const blogPost = await BlogPost.findOne(); - - console.log(blogPost.author); // '5b207f84e8061d1d2711b421' - // New in Mongoose 5.1.0: this will print '5b207f84e8061d1d2711b421' as well - console.log(blogPost.author._id); - - await blogPost.populate('author'); - console.log(blogPost.author._id); '5b207f84e8061d1d2711b421' - ``` - - As a consequence, checking whether `blogPost.author._id` is [no longer viable as a way to check whether `author` is populated](https://github.com/Automattic/mongoose/issues/6415#issuecomment-388579185). Use `blogPost.populated('author') != null` or `blogPost.author instanceof mongoose.Types.ObjectId` to check whether `author` is populated instead. - - Note that you can call `mongoose.set('objectIdGetter', false)` to change this behavior. - -

    - Return Values for `remove()` and `deleteX()` -

    - - `deleteOne()`, `deleteMany()`, and `remove()` now resolve to the result object - rather than the full [driver `WriteOpResult` object](http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~writeOpCallback). - - ```javascript - // In 4.x, this is how you got the number of documents deleted - MyModel.deleteMany().then(res => console.log(res.result.n)); - // In 5.x this is how you get the number of documents deleted - MyModel.deleteMany().then(res => res.n); - ``` - -

    - Aggregation Cursors -

    - - The `useMongooseAggCursor` option from 4.x is now always on. This is the new syntax for aggregation cursors in mongoose 5: - - ```javascript - // When you call `.cursor()`, `.exec()` will now return a mongoose aggregation - // cursor. - const cursor = MyModel.aggregate([{ $match: { name: 'Val' } }]).cursor().exec(); - // No need to `await` on the cursor or wait for a promise to resolve - cursor.eachAsync(doc => console.log(doc)); - - // Can also pass options to `cursor()` - const cursorWithOptions = MyModel. - aggregate([{ $match: { name: 'Val' } }]). - cursor({ batchSize: 10 }). - exec(); - ``` - -

    - geoNear -

    - - `Model.geoNear()` has been removed because the [MongoDB driver no longer supports it](https://github.com/mongodb/node-mongodb-native/blob/master/CHANGES_3.0.0.md#geonear-command-helper) - -

    - Required URI encoding of connection strings -

    - - Due to changes in the MongoDB driver, connection strings must be URI encoded. - - If they are not, connections may fail with an illegal character message. - -

    - Passwords which contain certain characters -

    - - See a [full list of affected characters](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding). - - If your app is used by a lot of different connection strings, it's possible - that your test cases will pass, but production passwords will fail. Encode all your connection - strings to be safe. - - If you want to continue to use unencoded connection strings, the easiest fix is to use - the `mongodb-uri` module to parse the connection strings, and then produce the properly encoded - versions. You can use a function like this: - - ```javascript - const uriFormat = require('mongodb-uri') - function encodeMongoURI (urlString) { - if (urlString) { - let parsed = uriFormat.parse(urlString) - urlString = uriFormat.format(parsed); - } - return urlString; - } - } - - // Your un-encoded string. - const mongodbConnectString = "mongodb://..."; - mongoose.connect(encodeMongoURI(mongodbConnectString)) - ``` - - The function above is safe to use whether the existing string is already encoded or not. - -

    - Domain sockets -

    - - Domain sockets must be URI encoded. For example: - - ```javascript - // Works in mongoose 4. Does **not** work in mongoose 5 because of more - // stringent URI parsing. - const host = '/tmp/mongodb-27017.sock'; - mongoose.createConnection(`mongodb://aaron:psw@${host}/fake`); - - // Do this instead - const host = encodeURIComponent('/tmp/mongodb-27017.sock'); - mongoose.createConnection(`mongodb://aaron:psw@${host}/fake`); - ``` - -

    - `toObject()` Options -

    - - The `options` parameter to `toObject()` and `toJSON()` merge defaults rather than overwriting them. - - ```javascript - // Note the `toObject` option below - const schema = new Schema({ name: String }, { toObject: { virtuals: true } }); - schema.virtual('answer').get(() => 42); - const MyModel = db.model('MyModel', schema); - - const doc = new MyModel({ name: 'test' }); - // In mongoose 4.x this prints "undefined", because `{ minimize: false }` - // overwrites the entire schema-defined options object. - // In mongoose 5.x this prints "42", because `{ minimize: false }` gets - // merged with the schema-defined options. - console.log(doc.toJSON({ minimize: false }).answer); - ``` - -

    - Aggregate Parameters -

    - - `aggregate()` no longer accepts a spread, you **must** pass your aggregation pipeline as an array. The below code worked in 4.x: - - ```javascript - MyModel.aggregate({ $match: { isDeleted: false } }, { $skip: 10 }).exec(cb); - ``` - - The above code does **not** work in 5.x, you **must** wrap the `$match` and `$skip` stages in an array. - - ```javascript - MyModel.aggregate([{ $match: { isDeleted: false } }, { $skip: 10 }]).exec(cb); - ``` - -

    - Boolean Casting -

    - - By default, mongoose 4 would coerce any value to a boolean without error. - - ```javascript - // Fine in mongoose 4, would save a doc with `boolField = true` - const MyModel = mongoose.model('Test', new Schema({ - boolField: Boolean - })); - - MyModel.create({ boolField: 'not a boolean' }); - ``` - - Mongoose 5 only casts the following values to `true`: - - * `true` - * `'true'` - * `1` - * `'1'` - * `'yes'` - - And the following values to `false`: - - * `false` - * `'false'` - * `0` - * `'0'` - * `'no'` - - All other values will cause a `CastError` - -

    - Query Casting -

    - - Casting for `update()`, `updateOne()`, `updateMany()`, `replaceOne()`, - `remove()`, `deleteOne()`, and `deleteMany()` doesn't happen until `exec()`. - This makes it easier for hooks and custom query helpers to modify data, because - mongoose won't restructure the data you passed in until after your hooks and - query helpers have ran. It also makes it possible to set the `overwrite` option - _after_ passing in an update. - - ```javascript - // In mongoose 4.x, this becomes `{ $set: { name: 'Baz' } }` despite the `overwrite` - // In mongoose 5.x, this overwrite is respected and the first document with - // `name = 'Bar'` will be replaced with `{ name: 'Baz' }` - User.where({ name: 'Bar' }).update({ name: 'Baz' }).setOptions({ overwrite: true }); - ``` - -

    - Post Save Hooks Get Flow Control -

    - - Post hooks now get flow control, which means async post save hooks and child document post save hooks execute **before** your `save()` callback. - - ```javascript - const ChildModelSchema = new mongoose.Schema({ - text: { - type: String - } - }); - ChildModelSchema.post('save', function(doc) { - // In mongoose 5.x this will print **before** the `console.log()` - // in the `save()` callback. In mongoose 4.x this was reversed. - console.log('Child post save'); - }); - const ParentModelSchema = new mongoose.Schema({ - children: [ChildModelSchema] - }); - - const Model = mongoose.model('Parent', ParentModelSchema); - const m = new Model({ children: [{ text: 'test' }] }); - m.save(function() { - // In mongoose 5.xm this prints **after** the "Child post save" message. - console.log('Save callback'); - }); - ``` - -

    - The `$pushAll` Operator -

    - - `$pushAll` is no longer supported and no longer used internally for `save()`, since it has been [deprecated since MongoDB 2.4](https://docs.mongodb.com/manual/reference/operator/update/pushAll/). Use `$push` with `$each` instead. - -

    - Always Use Forward Key Order -

    - - The `retainKeyOrder` option was removed, mongoose will now always retain the same key position when cloning objects. If you have queries or indexes that rely on reverse key order, you will have to change them. - -

    - Run setters on queries -

    - - Setters now run on queries by default, and the old `runSettersOnQuery` option - has been removed. - - ```javascript - const schema = new Schema({ - email: { type: String, lowercase: true } - }); - const Model = mongoose.model('Test', schema); - Model.find({ email: 'FOO@BAR.BAZ' }); // Converted to `find({ email: 'foo@bar.baz' })` - ``` - -

    - Pre-compiled Browser Bundle -

    - - We no longer have a pre-compiled version of mongoose for the browser. If you want to use mongoose schemas in the browser, you need to build your own bundle with browserify/webpack. - -

    - Save Errors -

    - - The `saveErrorIfNotFound` option was removed, mongoose will now always error out from `save()` if the underlying document was not found - -

    - Init hook signatures -

    - - `init` hooks are now fully synchronous and do not receive `next()` as a parameter. - - `Document.prototype.init()` no longer takes a callback as a parameter. It - was always synchronous, just had a callback for legacy reasons. - -

    - `numAffected` and `save()` -

    - - `doc.save()` no longer passes `numAffected` as a 3rd param to its callback. - -

    - `remove()` and debouncing -

    - - `doc.remove()` no longer debounces - -

    - `getPromiseConstructor()` -

    - - `getPromiseConstructor()` is gone, just use `mongoose.Promise`. - -

    - Passing Parameters from Pre Hooks -

    - - You cannot pass parameters to the next pre middleware in the chain using `next()` in mongoose 5.x. In mongoose 4, `next('Test')` in pre middleware would call the - next middleware with 'Test' as a parameter. Mongoose 5.x has removed support for this. - -

    - `required` validator for arrays -

    - - In mongoose 5 the `required` validator only verifies if the value is an - array. That is, it will **not** fail for _empty_ arrays as it would in - mongoose 4. - -

    - debug output defaults to stdout instead of stderr -

    - - In mongoose 5 the default debug function uses `console.info()` to display messages instead of `console.error()`. - -

    - Overwriting filter properties -

    - - In Mongoose 4.x, overwriting a filter property that's a primitive with one that is an object would silently fail. For example, the below code would ignore the `where()` and be equivalent to `Sport.find({ name: 'baseball' })` - - ```javascript - Sport.find({ name: 'baseball' }).where({name: {$ne: 'softball'}}); - ``` - - In Mongoose 5.x, the above code will correctly overwrite `'baseball'` with `{ $ne: 'softball' }` - -

    - `bulkWrite()` results -

    - - Mongoose 5.x uses version 3.x of the [MongoDB Node.js driver](http://npmjs.com/package/mongodb). MongoDB driver 3.x changed the format of - the result of [`bulkWrite()` calls](/docs/api.html#model_Model.bulkWrite) so there is no longer a top-level `nInserted`, `nModified`, etc. property. The new result object structure is [described here](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#~BulkWriteOpResult). - - ```javascript - const Model = mongoose.model('Test', new Schema({ name: String })); - - const res = await Model.bulkWrite([{ insertOne: { document: { name: 'test' } } }]); - - console.log(res); - ``` - - In Mongoose 4.x, the above will print: - - ``` - BulkWriteResult { - ok: [Getter], - nInserted: [Getter], - nUpserted: [Getter], - nMatched: [Getter], - nModified: [Getter], - nRemoved: [Getter], - getInsertedIds: [Function], - getUpsertedIds: [Function], - getUpsertedIdAt: [Function], - getRawResponse: [Function], - hasWriteErrors: [Function], - getWriteErrorCount: [Function], - getWriteErrorAt: [Function], - getWriteErrors: [Function], - getLastOp: [Function], - getWriteConcernError: [Function], - toJSON: [Function], - toString: [Function], - isOk: [Function], - insertedCount: 1, - matchedCount: 0, - modifiedCount: 0, - deletedCount: 0, - upsertedCount: 0, - upsertedIds: {}, - insertedIds: { '0': 5be9a3101638a066702a0d38 }, - n: 1 } - ``` - - In Mongoose 5.x, the script will print: - - ``` - BulkWriteResult { - result: - { ok: 1, - writeErrors: [], - writeConcernErrors: [], - insertedIds: [ [Object] ], - nInserted: 1, - nUpserted: 0, - nMatched: 0, - nModified: 0, - nRemoved: 0, - upserted: [], - lastOp: { ts: [Object], t: 1 } }, - insertedCount: 1, - matchedCount: 0, - modifiedCount: 0, - deletedCount: 0, - upsertedCount: 0, - upsertedIds: {}, - insertedIds: { '0': 5be9a1c87decfc6443dd9f18 }, - n: 1 } - ``` - -

    - Strict SSL Validation -

    - - The most recent versions of the [MongoDB Node.js driver use strict SSL validation by default](http://mongodb.github.io/node-mongodb-native/3.5/tutorials/connect/tls/), - which may lead to errors if you're using [self-signed certificates](https://github.com/Automattic/mongoose/issues/9147). - - If this is blocking you from upgrading, you can set the `tlsInsecure` option to `true`. - - ```javascript - mongoose.connect(uri, { tlsInsecure: false }); // Opt out of additional SSL validation - ``` diff --git a/docs/migration.html b/docs/migration.html deleted file mode 100644 index 5f4632abd3e..00000000000 --- a/docs/migration.html +++ /dev/null @@ -1,87 +0,0 @@ -Mongoose v5.6.0: Migration Guide

    Migrating from 3.x to 4.x

    There are several backwards-breaking changes to be aware of when migrating from Mongoose 3 to Mongoose 4.

    -

    `findOneAndUpdate()` new field is now `false` by default

    - -

    Mongoose's findOneAndUpdate(), findOneAndRemove(), -findByIdAndUpdate(), and findByIdAndRemove() functions are just -wrappers around MongoDB's -findAndModify command. -Both the MongoDB server and the MongoDB NodeJS driver set the new option -to false by default, but mongoose 3 overwrote this default. In order to be -more consistent with the MongoDB server's documentation, mongoose will -use false by default. That is, -findOneAndUpdate({}, { $set: { test: 1 } }, callback); will return the -document as it was before the $set operation was applied.

    -

    To return the document with modifications made on the update, use the new: true option.

    -
    MyModel.findOneAndUpdate({}, { $set: { test: 1 } }, { new: true }, callback);
    -

    CastError and ValidationError now use kind instead of type to report error types

    -

    In Mongoose 3, CastError and ValidationError had a type field. For instance, user defined validation errors would have a type property that contained the string 'user defined'. In Mongoose 4, this property has been renamed to kind due to the V8 JavaScript engine using the Error.type property internally.

    -

    Query now has a `.then()` function

    - -

    In mongoose 3, you needed to call .exec() on a query chain to get a -promise back, like MyModel.find().exec().then();. Mongoose 4 queries are -promises, so you can do MyModel.find().then() instead. Be careful if -you're using functions like -q's Q.ninvoke() or -otherwise returning a mongoose query from a promise.

    -

    More Info

    - -

    Related blog posts:

    - -
    \ No newline at end of file diff --git a/docs/migration.md b/docs/migration.md new file mode 100644 index 00000000000..f4103f73d33 --- /dev/null +++ b/docs/migration.md @@ -0,0 +1,41 @@ +## Migrating from 3.x to 4.x + +There are several [backwards-breaking changes](https://github.com/Automattic/mongoose/wiki/4.0-Release-Notes) to be aware of when migrating from Mongoose 3 to Mongoose 4. + +

    `findOneAndUpdate()` new field is now `false` by default

    + +Mongoose's `findOneAndUpdate()`, `findOneAndRemove()`, +`findByIdAndUpdate()`, and `findByIdAndRemove()` functions are just +wrappers around MongoDB's +[`findAndModify` command](http://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/). +Both the MongoDB server and the MongoDB NodeJS driver set the `new` option +to false by default, but mongoose 3 overwrote this default. In order to be +more consistent with the MongoDB server's documentation, mongoose will +use false by default. That is, +`findOneAndUpdate({}, { $set: { test: 1 } }, callback);` will return the +document as it was *before* the `$set` operation was applied. + +To return the document with modifications made on the update, use the `new: true` option. + +```javascript +MyModel.findOneAndUpdate({}, { $set: { test: 1 } }, { new: true }, callback); +``` + +### CastError and ValidationError now use `kind` instead of `type` to report error types + +In Mongoose 3, CastError and ValidationError had a `type` field. For instance, user defined validation errors would have a `type` property that contained the string 'user defined'. In Mongoose 4, this property has been renamed to `kind` due to [the V8 JavaScript engine using the Error.type property internally](https://code.google.com/p/v8/issues/detail?id=2397). + +

    Query now has a `.then()` function

    + +In mongoose 3, you needed to call `.exec()` on a query chain to get a +promise back, like `MyModel.find().exec().then();`. Mongoose 4 queries are +promises, so you can do `MyModel.find().then()` instead. Be careful if +you're using functions like +[q's `Q.ninvoke()`](https://github.com/kriskowal/q#adapting-node) or +otherwise returning a mongoose query from a promise. + +

    More Info

    + +Related blog posts: + +- [Introducing Version 4.0 of the Mongoose NodeJS ODM](http://www.mongodb.com/blog/post/introducing-version-40-mongoose-nodejs-odm) diff --git a/docs/migration.pug b/docs/migration.pug deleted file mode 100644 index e60b227e808..00000000000 --- a/docs/migration.pug +++ /dev/null @@ -1,54 +0,0 @@ -extends layout - -block append style - style. - table td { - padding-right: 15px; - } - -block content - - - - - h2 Migrating from 3.x to 4.x - :markdown - There are several [backwards-breaking changes](https://github.com/Automattic/mongoose/wiki/4.0-Release-Notes) to be aware of when migrating from Mongoose 3 to Mongoose 4. - -

    `findOneAndUpdate()` new field is now `false` by default

    - - Mongoose's `findOneAndUpdate()`, `findOneAndRemove()`, - `findByIdAndUpdate()`, and `findByIdAndRemove()` functions are just - wrappers around MongoDB's - [`findAndModify` command](http://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/). - Both the MongoDB server and the MongoDB NodeJS driver set the `new` option - to false by default, but mongoose 3 overwrote this default. In order to be - more consistent with the MongoDB server's documentation, mongoose will - use false by default. That is, - `findOneAndUpdate({}, { $set: { test: 1 } }, callback);` will return the - document as it was *before* the `$set` operation was applied. - - To return the document with modifications made on the update, use the `new: true` option. - - ```javascript - MyModel.findOneAndUpdate({}, { $set: { test: 1 } }, { new: true }, callback); - ``` - - ### CastError and ValidationError now use `kind` instead of `type` to report error types - - In Mongoose 3, CastError and ValidationError had a `type` field. For instance, user defined validation errors would have a `type` property that contained the string 'user defined'. In Mongoose 4, this property has been renamed to `kind` due to [the V8 JavaScript engine using the Error.type property internally](https://code.google.com/p/v8/issues/detail?id=2397). - -

    Query now has a `.then()` function

    - - In mongoose 3, you needed to call `.exec()` on a query chain to get a - promise back, like `MyModel.find().exec().then();`. Mongoose 4 queries are - promises, so you can do `MyModel.find().then()` instead. Be careful if - you're using functions like - [q's `Q.ninvoke()`](https://github.com/kriskowal/q#adapting-node) or - otherwise returning a mongoose query from a promise. - -

    More Info

    - - Related blog posts: - - - [Introducing Version 4.0 of the Mongoose NodeJS ODM](http://www.mongodb.com/blog/post/introducing-version-40-mongoose-nodejs-odm) diff --git a/docs/models.html b/docs/models.html deleted file mode 100644 index a5bdfad2d7c..00000000000 --- a/docs/models.html +++ /dev/null @@ -1,158 +0,0 @@ -Mongoose v5.6.0: Models

    Models

    - - - - -

    Models are fancy constructors compiled from -Schema definitions. An instance of a model is called a -document. Models are responsible for creating and -reading documents from the underlying MongoDB database.

    -

    Compiling your first model

    When you call mongoose.model() on a schema, Mongoose compiles a model -for you.

    -
    var schema = new mongoose.Schema({ name: 'string', size: 'string' });
    -var Tank = mongoose.model('Tank', schema);
    -

    The first argument is the singular name of the collection your model is -for. ** Mongoose automatically looks for the plural, lowercased version of your model name. ** -Thus, for the example above, the model Tank is for the tanks collection -in the database.

    -

    Note: The .model() function makes a copy of schema. Make sure that -you've added everything you want to schema, including hooks, -before calling .model()!

    -

    Constructing Documents

    An instance of a model is called a document. Creating -them and saving to the database is easy.

    -
    var Tank = mongoose.model('Tank', yourSchema);
    -
    -var small = new Tank({ size: 'small' });
    -small.save(function (err) {
    -  if (err) return handleError(err);
    -  // saved!
    -});
    -
    -// or
    -
    -Tank.create({ size: 'small' }, function (err, small) {
    -  if (err) return handleError(err);
    -  // saved!
    -});
    -
    -// or, for inserting large batches of documents
    -Tank.insertMany([{ size: 'small' }], function(err) {
    -
    -});
    -

    Note that no tanks will be created/removed until the connection your model -uses is open. Every model has an associated connection. When you use -mongoose.model(), your model will use the default mongoose connection.

    -
    mongoose.connect('mongodb://localhost/gettingstarted', {useNewUrlParser: true});
    -

    If you create a custom connection, use that connection's model() function -instead.

    -
    var connection = mongoose.createConnection('mongodb://localhost:27017/test');
    -var Tank = connection.model('Tank', yourSchema);
    -

    Querying

    Finding documents is easy with Mongoose, which supports the rich query syntax of MongoDB. Documents can be retreived using each models find, findById, findOne, or where static methods.

    -
    Tank.find({ size: 'small' }).where('createdDate').gt(oneYearAgo).exec(callback);
    -

    See the chapter on queries for more details on how to use the Query api.

    -

    Deleting

    Models have static deleteOne() and deleteMany() functions -for removing all documents matching the given filter.

    -
    Tank.deleteOne({ size: 'large' }, function (err) {
    -  if (err) return handleError(err);
    -  // deleted at most one tank document
    -});
    -

    Updating

    Each model has its own update method for modifying documents in the -database without returning them to your application. See the -API docs for more detail.

    -
    Tank.updateOne({ size: 'large' }, { name: 'T-90' }, function(err, res) {
    -  // Updated at most one doc, `res.modifiedCount` contains the number
    -  // of docs that MongoDB updated
    -});
    -

    If you want to update a single document in the db and return it to your -application, use findOneAndUpdate -instead.

    -

    Change Streams

    New in MongoDB 3.6.0 and Mongoose 5.0.0

    -

    Change streams provide -a way for you to listen to all inserts and updates going through your -MongoDB database. Note that change streams do not work unless you're -connected to a MongoDB replica set.

    -
    async function run() {
    -  // Create a new mongoose model
    -  const personSchema = new mongoose.Schema({
    -    name: String
    -  });
    -  const Person = mongoose.model('Person', personSchema, 'Person');
    -
    -  // Create a change stream. The 'change' event gets emitted when there's a
    -  // change in the database
    -  Person.watch().
    -    on('change', data => console.log(new Date(), data));
    -
    -  // Insert a doc, will trigger the change stream handler above
    -  console.log(new Date(), 'Inserting doc');
    -  await Person.create({ name: 'Axl Rose' });
    -}
    -

    The output from the above async function will look like what you see below.

    -
    2018-05-11T15:05:35.467Z 'Inserting doc'
    -2018-05-11T15:05:35.487Z 'Inserted doc'
    -2018-05-11T15:05:35.491Z { _id: { _data: ... },
    -  operationType: 'insert',
    -  fullDocument: { _id: 5af5b13fe526027666c6bf83, name: 'Axl Rose', __v: 0 },
    -  ns: { db: 'test', coll: 'Person' },
    -  documentKey: { _id: 5af5b13fe526027666c6bf83 } }

    You can read more about change streams in mongoose in this blog post.

    -

    Yet more

    The API docs cover many additional methods available like count, mapReduce, aggregate, and more.

    -

    Next Up

    Now that we've covered Models, let's take a look at Documents.

    -
    \ No newline at end of file diff --git a/docs/models.md b/docs/models.md new file mode 100644 index 00000000000..257ea18bdce --- /dev/null +++ b/docs/models.md @@ -0,0 +1,163 @@ +## Models + +[Models](./api.html#model-js) are fancy constructors compiled from +`Schema` definitions. An instance of a model is called a +[document](./documents.html). Models are responsible for creating and +reading documents from the underlying MongoDB database. + +* [Compiling your first model](#compiling) +* [Constructing Documents](#constructing-documents) +* [Querying](#querying) +* [Deleting](#deleting) +* [Updating](#updating) +* [Change Streams](#change-streams) + +

    Compiling your first model

    + +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 Tank = mongoose.model('Tank', schema); +``` + +The first argument is the _singular_ name of the collection your model is +for. **Mongoose automatically looks for the plural, lowercased version of your model name.** +Thus, for the example above, the model Tank is for the **tanks** collection +in the database. + +**Note:** The `.model()` function makes a copy of `schema`. Make sure that +you've added everything you want to `schema`, including hooks, +before calling `.model()`! + +### Constructing Documents + +An instance of a model is called a [document](./documents.html). Creating +them and saving to the database is easy. + +```javascript +const Tank = mongoose.model('Tank', yourSchema); + +const small = new Tank({ size: 'small' }); +small.save(function (err) { + if (err) return handleError(err); + // saved! +}); + +// or + +Tank.create({ size: 'small' }, function (err, small) { + if (err) return handleError(err); + // saved! +}); + +// or, for inserting large batches of documents +Tank.insertMany([{ size: 'small' }], function(err) { + +}); +``` + +Note that no tanks will be created/removed until the connection your model +uses is open. Every model has an associated connection. When you use +`mongoose.model()`, your model will use the default mongoose connection. + +```javascript +mongoose.connect('mongodb://localhost/gettingstarted', {useNewUrlParser: true}); +``` + +If you create a custom connection, use that connection's `model()` function +instead. +```javascript +const connection = mongoose.createConnection('mongodb://localhost:27017/test'); +const Tank = connection.model('Tank', yourSchema); +``` + +### Querying + +Finding documents is easy with Mongoose, which supports the [rich](http://www.mongodb.org/display/DOCS/Advanced+Queries) query syntax of MongoDB. Documents can be retreived using each `models` [find](./api.html#model_Model.find), [findById](./api.html#model_Model.findById), [findOne](./api.html#model_Model.findOne), or [where](./api.html#model_Model.where) static methods. + +```javascript +Tank.find({ size: 'small' }).where('createdDate').gt(oneYearAgo).exec(callback); +``` + +See the chapter on [queries](./queries.html) for more details on how to use the [Query](./api.html#query-js) api. + +### Deleting + +Models have static `deleteOne()` and `deleteMany()` functions +for removing all documents matching the given `filter`. + +```javascript +Tank.deleteOne({ size: 'large' }, function (err) { + if (err) return handleError(err); + // deleted at most one tank document +}); +``` + +### Updating + +Each `model` has its own `update` method for modifying documents in the +database without returning them to your application. See the +[API](./api.html#model_Model.updateOne) docs for more detail. + +```javascript +Tank.updateOne({ size: 'large' }, { name: 'T-90' }, function(err, res) { + // Updated at most one doc, `res.nModified` contains the number + // of docs that MongoDB updated +}); +``` + +_If you want to update a single document in the db and return it to your +application, use [findOneAndUpdate](./api.html#model_Model.findOneAndUpdate) +instead._ + +### Change Streams + +_New in MongoDB 3.6.0 and Mongoose 5.0.0_ + +[Change streams](https://docs.mongodb.com/manual/changeStreams/) provide +a way for you to listen to all inserts and updates going through your +MongoDB database. Note that change streams do **not** work unless you're +connected to a [MongoDB replica set](https://docs.mongodb.com/manual/replication/). + +```javascript +async function run() { + // Create a new mongoose model + const personSchema = new mongoose.Schema({ + name: String + }); + const Person = mongoose.model('Person', personSchema, 'Person'); + + // Create a change stream. The 'change' event gets emitted when there's a + // change in the database + Person.watch(). + on('change', data => console.log(new Date(), data)); + + // Insert a doc, will trigger the change stream handler above + console.log(new Date(), 'Inserting doc'); + await Person.create({ name: 'Axl Rose' }); +} +``` + +The output from the above [async function](http://thecodebarbarian.com/80-20-guide-to-async-await-in-node.js.html) will look like what you see below. + +``` +2018-05-11T15:05:35.467Z 'Inserting doc' +2018-05-11T15:05:35.487Z 'Inserted doc' +2018-05-11T15:05:35.491Z { _id: { _data: ... }, + operationType: 'insert', + fullDocument: { _id: 5af5b13fe526027666c6bf83, name: 'Axl Rose', __v: 0 }, + ns: { db: 'test', coll: 'Person' }, + documentKey: { _id: 5af5b13fe526027666c6bf83 } } +``` + +You can read more about [change streams in mongoose in this blog post](http://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-change-streams.html#change-streams-in-mongoose). + +### Yet more + +The [API docs](./api.html#model_Model) cover many additional methods available like [count](./api.html#model_Model.count), [mapReduce](./api.html#model_Model.mapReduce), [aggregate](./api.html#model_Model.aggregate), and [more](./api.html#model_Model.findOneAndRemove). + +### Next Up + +Now that we've covered `Models`, let's take a look at [Documents](/docs/documents.html). \ No newline at end of file diff --git a/docs/models.pug b/docs/models.pug deleted file mode 100644 index 7c4b27ff2e0..00000000000 --- a/docs/models.pug +++ /dev/null @@ -1,191 +0,0 @@ -extends layout - -append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown - ## Models - - - - - - [Models](./api.html#model-js) are fancy constructors compiled from - `Schema` definitions. An instance of a model is called a - [document](./documents.html). Models are responsible for creating and - reading documents from the underlying MongoDB database. - - ul - li - a(href="#compiling") Compiling your first model - li - a(href="#constructing-documents") Constructing Documents - li - a(href="#querying") Querying - li - a(href="#deleting") Deleting - li - a(href="#updating") Updating - li - a(href="#change-streams") Change Streams - - h3(id="compiling") Compiling your first model - :markdown - 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 Tank = mongoose.model('Tank', schema); - ``` - :markdown - The first argument is the _singular_ name of the collection your model is - for. **Mongoose automatically looks for the plural, lowercased version of your model name.** - Thus, for the example above, the model Tank is for the **tanks** collection - in the database. - - **Note:** The `.model()` function makes a copy of `schema`. Make sure that - you've added everything you want to `schema`, including hooks, - before calling `.model()`! - - h3(id="constructing-documents") Constructing Documents - :markdown - An instance of a model is called a [document](./documents.html). Creating - them and saving to the database is easy. - - ```javascript - const Tank = mongoose.model('Tank', yourSchema); - - const small = new Tank({ size: 'small' }); - small.save(function (err) { - if (err) return handleError(err); - // saved! - }); - - // or - - Tank.create({ size: 'small' }, function (err, small) { - if (err) return handleError(err); - // saved! - }); - - // or, for inserting large batches of documents - Tank.insertMany([{ size: 'small' }], function(err) { - - }); - ``` - :markdown - Note that no tanks will be created/removed until the connection your model - uses is open. Every model has an associated connection. When you use - `mongoose.model()`, your model will use the default mongoose connection. - ```javascript - mongoose.connect('mongodb://localhost/gettingstarted', {useNewUrlParser: true}); - ``` - :markdown - If you create a custom connection, use that connection's `model()` function - instead. - ```javascript - const connection = mongoose.createConnection('mongodb://localhost:27017/test'); - const Tank = connection.model('Tank', yourSchema); - ``` - h3(id="querying") Querying - :markdown - Finding documents is easy with Mongoose, which supports the [rich](http://www.mongodb.org/display/DOCS/Advanced+Queries) query syntax of MongoDB. Documents can be retreived using each `models` [find](./api.html#model_Model.find), [findById](./api.html#model_Model.findById), [findOne](./api.html#model_Model.findOne), or [where](./api.html#model_Model.where) static methods. - - ```javascript - Tank.find({ size: 'small' }).where('createdDate').gt(oneYearAgo).exec(callback); - ``` - :markdown - See the chapter on [queries](./queries.html) for more details on how to use the [Query](./api.html#query-js) api. - - h3(id="deleting") Deleting - :markdown - Models have static `deleteOne()` and `deleteMany()` functions - for removing all documents matching the given `filter`. - - ```javascript - Tank.deleteOne({ size: 'large' }, function (err) { - if (err) return handleError(err); - // deleted at most one tank document - }); - ``` - - h3(id="updating") Updating - :markdown - Each `model` has its own `update` method for modifying documents in the - database without returning them to your application. See the - [API](./api.html#model_Model.updateOne) docs for more detail. - - ```javascript - Tank.updateOne({ size: 'large' }, { name: 'T-90' }, function(err, res) { - // Updated at most one doc, `res.modifiedCount` contains the number - // of docs that MongoDB updated - }); - ``` - - _If you want to update a single document in the db and return it to your - application, use [findOneAndUpdate](./api.html#model_Model.findOneAndUpdate) - instead._ - - h3(id="change-streams") Change Streams - - :markdown - _New in MongoDB 3.6.0 and Mongoose 5.0.0_ - - [Change streams](https://docs.mongodb.com/manual/changeStreams/) provide - a way for you to listen to all inserts and updates going through your - MongoDB database. Note that change streams do **not** work unless you're - connected to a [MongoDB replica set](https://docs.mongodb.com/manual/replication/). - - ```javascript - async function run() { - // Create a new mongoose model - const personSchema = new mongoose.Schema({ - name: String - }); - const Person = mongoose.model('Person', personSchema, 'Person'); - - // Create a change stream. The 'change' event gets emitted when there's a - // change in the database - Person.watch(). - on('change', data => console.log(new Date(), data)); - - // Insert a doc, will trigger the change stream handler above - console.log(new Date(), 'Inserting doc'); - await Person.create({ name: 'Axl Rose' }); - } - ``` - - The output from the above [async function](http://thecodebarbarian.com/80-20-guide-to-async-await-in-node.js.html) will look like what you see below. - - ``` - 2018-05-11T15:05:35.467Z 'Inserting doc' - 2018-05-11T15:05:35.487Z 'Inserted doc' - 2018-05-11T15:05:35.491Z { _id: { _data: ... }, - operationType: 'insert', - fullDocument: { _id: 5af5b13fe526027666c6bf83, name: 'Axl Rose', __v: 0 }, - ns: { db: 'test', coll: 'Person' }, - documentKey: { _id: 5af5b13fe526027666c6bf83 } } - ``` - - You can read more about [change streams in mongoose in this blog post](http://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-change-streams.html#change-streams-in-mongoose). - - h3 Yet more - :markdown - The [API docs](./api.html#model_Model) cover many additional methods available like [count](./api.html#model_Model.count), [mapReduce](./api.html#model_Model.mapReduce), [aggregate](./api.html#model_Model.aggregate), and [more](./api.html#model_Model.findOneAndRemove). - - h3#next Next Up - :markdown - Now that we've covered `Models`, let's take a look at [Documents](/docs/documents.html). diff --git a/docs/plugins.html b/docs/plugins.html deleted file mode 100644 index 3d8d6b24eb5..00000000000 --- a/docs/plugins.html +++ /dev/null @@ -1,124 +0,0 @@ -Mongoose v5.6.0: Plugins

    Plugins

    - - - - -

    Schemas are pluggable, that is, they allow for applying pre-packaged capabilities to extend their functionality. This is a very powerful feature.

    - - -

    Example

    -

    Suppose that we have several collections in our database and want to add last-modified functionality to each one. With plugins this is easy. Just create a plugin once and apply it to each Schema:

    -
    // lastMod.js
    -module.exports = exports = function lastModifiedPlugin (schema, options) {
    -  schema.add({ lastMod: Date });
    -
    -  schema.pre('save', function (next) {
    -    this.lastMod = new Date();
    -    next();
    -  });
    -
    -  if (options && options.index) {
    -    schema.path('lastMod').index(options.index);
    -  }
    -}
    -
    -// game-schema.js
    -var lastMod = require('./lastMod');
    -var Game = new Schema({ ... });
    -Game.plugin(lastMod, { index: true });
    -
    -// player-schema.js
    -var lastMod = require('./lastMod');
    -var Player = new Schema({ ... });
    -Player.plugin(lastMod);
    -

    We just added last-modified behavior to both our Game and Player schemas and declared an index on the lastMod path of our Games to boot. Not bad for a few lines of code.

    -

    Global Plugins

    - -

    Want to register a plugin for all schemas? The mongoose singleton has a -.plugin() function that registers a plugin for every schema. For -example:

    -
    var mongoose = require('mongoose');
    -mongoose.plugin(require('./lastMod'));
    -
    -var gameSchema = new Schema({ ... });
    -var playerSchema = new Schema({ ... });
    -// `lastModifiedPlugin` gets attached to both schemas
    -var Game = mongoose.model('Game', gameSchema);
    -var Player = mongoose.model('Player', playerSchema);
    -

    Officially Supported Plugins

    - -

    The Mongoose team maintains several plugins that add cool new features to -Mongoose. Here's a few:

    - -

    Community!

    -

    Not only can you re-use schema functionality in your own projects, but you -also reap the benefits of the Mongoose community as well. Any plugin -published to npm and -tagged with mongoose will show up on -our search results page.

    -
    \ No newline at end of file diff --git a/docs/plugins.md b/docs/plugins.md new file mode 100644 index 00000000000..23a015b4ff7 --- /dev/null +++ b/docs/plugins.md @@ -0,0 +1,116 @@ +## Plugins + +Schemas are pluggable, that is, they allow for applying pre-packaged capabilities to extend their functionality. This is a very powerful feature. + + + +

    Example

    + +Plugins are a tool for reusing logic in multiple schemas. Suppose you have +several models in your database and want to add a `loadedAt` property +to each one. Just create a plugin once and apply it to each `Schema`: + +```javascript +// loadedAt.js +module.exports = function loadedAtPlugin(schema, options) { + schema.virtual('loadedAt'). + get(function() { return this._loadedAt; }). + set(function(v) { this._loadedAt = v; }); + + schema.post(['find', 'findOne'], function(docs) { + if (!Array.isArray(docs)) { + docs = [docs]; + } + const now = new Date(); + for (const doc of docs) { + doc.loadedAt = now; + } + }); +}; + +// game-schema.js +const loadedAtPlugin = require('./loadedAt'); +const gameSchema = new Schema({ ... }); +gameSchema.plugin(loadedAtPlugin); + +// player-schema.js +const loadedAtPlugin = require('./loadedAt'); +const playerSchema = new Schema({ ... }); +playerSchema.plugin(loadedAtPlugin); +``` + +We just added last-modified behavior to both our `Game` and `Player` schemas and declared an index on the `lastMod` path of our Games to boot. Not bad for a few lines of code. + +

    Global Plugins

    + +Want to register a plugin for all schemas? The mongoose singleton has a +`.plugin()` function that registers a plugin for every schema. For +example: + +```javascript +const mongoose = require('mongoose'); +mongoose.plugin(require('./loadedAt')); + +const gameSchema = new Schema({ ... }); +const playerSchema = new Schema({ ... }); +// `loadedAtPlugin` gets attached to both schemas +const Game = mongoose.model('Game', gameSchema); +const Player = mongoose.model('Player', playerSchema); +``` + +

    Apply Plugins Before Compiling Models

    + +Because many plugins rely on [middleware](/docs/middleware.html), you should make sure to apply plugins **before** +you call `mongoose.model()` or `conn.model()`. Otherwise, [any middleware the plugin registers won't get applied](/docs/middleware.html#defining). + +```javascript +// loadedAt.js +module.exports = function loadedAtPlugin(schema, options) { + schema.virtual('loadedAt'). + get(function() { return this._loadedAt; }). + set(function(v) { this._loadedAt = v; }); + + schema.post(['find', 'findOne'], function(docs) { + if (!Array.isArray(docs)) { + docs = [docs]; + } + const now = new Date(); + for (const doc of docs) { + doc.loadedAt = now; + } + }); +}; + +// game-schema.js +const loadedAtPlugin = require('./loadedAt'); +const gameSchema = new Schema({ ... }); +const Game = mongoose.model('Game', gameSchema); + +// `find()` and `findOne()` hooks from `loadedAtPlugin()` won't get applied +// because `mongoose.model()` was already called! +gameSchema.plugin(loadedAtPlugin); +``` + +

    Officially Supported Plugins

    + +The Mongoose team maintains several plugins that add cool new features to +Mongoose. Here's a couple: + +* [mongoose-autopopulate](http://plugins.mongoosejs.io/plugins/autopopulate): Always [`populate()`](/docs/populate.html) certain fields in your Mongoose schemas. +* [mongoose-lean-virtuals](http://plugins.mongoosejs.io/plugins/lean-virtuals): Attach virtuals to the results of Mongoose queries when using [`.lean()`](/docs/api.html#query_Query-lean). +* [mongoose-cast-aggregation](https://www.npmjs.com/package/mongoose-cast-aggregation) + +You can find a full list of officially supported plugins on [Mongoose's plugins search site](https://plugins.mongoosejs.io/). + +### Community! + +Not only can you re-use schema functionality in your own projects, but you +also reap the benefits of the Mongoose community as well. Any plugin +published to [npm](https://npmjs.org/) and with +'mongoose' as an [npm keyword](https://docs.npmjs.com/files/package.json#keywords) +will show up on our [search results](http://plugins.mongoosejs.io) page. diff --git a/docs/plugins.pug b/docs/plugins.pug deleted file mode 100644 index 0c8208740c2..00000000000 --- a/docs/plugins.pug +++ /dev/null @@ -1,138 +0,0 @@ -extends layout - -append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown - ## Plugins - - - - - - Schemas are pluggable, that is, they allow for applying pre-packaged capabilities to extend their functionality. This is a very powerful feature. - - - - ### Example - - Plugins are a tool for reusing logic in multiple schemas. Suppose you have - several models in your database and want to add a `loadedAt` property - to each one. Just create a plugin once and apply it to each `Schema`: - - ```javascript - // loadedAt.js - module.exports = function loadedAtPlugin(schema, options) { - schema.virtual('loadedAt'). - get(function() { return this._loadedAt; }). - set(function(v) { this._loadedAt = v; }); - - schema.post(['find', 'findOne'], function(docs) { - if (!Array.isArray(docs)) { - docs = [docs]; - } - const now = new Date(); - for (const doc of docs) { - doc.loadedAt = now; - } - }); - }; - - // game-schema.js - const loadedAtPlugin = require('./loadedAt'); - const gameSchema = new Schema({ ... }); - gameSchema.plugin(loadedAtPlugin); - - // player-schema.js - const loadedAtPlugin = require('./loadedAt'); - const playerSchema = new Schema({ ... }); - playerSchema.plugin(loadedAtPlugin); - ``` - - We just added last-modified behavior to both our `Game` and `Player` schemas and declared an index on the `lastMod` path of our Games to boot. Not bad for a few lines of code. - -

    Global Plugins

    - - Want to register a plugin for all schemas? The mongoose singleton has a - `.plugin()` function that registers a plugin for every schema. For - example: - - ```javascript - const mongoose = require('mongoose'); - mongoose.plugin(require('./loadedAt')); - - const gameSchema = new Schema({ ... }); - const playerSchema = new Schema({ ... }); - // `loadedAtPlugin` gets attached to both schemas - const Game = mongoose.model('Game', gameSchema); - const Player = mongoose.model('Player', playerSchema); - ``` - -

    Apply Plugins Before Compiling Models

    - - Because many plugins rely on [middleware](/docs/middleware.html), you should make sure to apply plugins **before** - you call `mongoose.model()` or `conn.model()`. Otherwise, [any middleware the plugin registers won't get applied](/docs/middleware.html#defining). - - ```javascript - // loadedAt.js - module.exports = function loadedAtPlugin(schema, options) { - schema.virtual('loadedAt'). - get(function() { return this._loadedAt; }). - set(function(v) { this._loadedAt = v; }); - - schema.post(['find', 'findOne'], function(docs) { - if (!Array.isArray(docs)) { - docs = [docs]; - } - const now = new Date(); - for (const doc of docs) { - doc.loadedAt = now; - } - }); - }; - - // game-schema.js - const loadedAtPlugin = require('./loadedAt'); - const gameSchema = new Schema({ ... }); - const Game = mongoose.model('Game', gameSchema); - - // `find()` and `findOne()` hooks from `loadedAtPlugin()` won't get applied - // because `mongoose.model()` was already called! - gameSchema.plugin(loadedAtPlugin); - ``` - -

    Officially Supported Plugins

    - - The Mongoose team maintains several plugins that add cool new features to - Mongoose. Here's a couple: - - * [mongoose-autopopulate](http://plugins.mongoosejs.io/plugins/autopopulate): Always [`populate()`](/docs/populate.html) certain fields in your Mongoose schemas. - * [mongoose-lean-virtuals](http://plugins.mongoosejs.io/plugins/lean-virtuals): Attach virtuals to the results of Mongoose queries when using [`.lean()`](/docs/api.html#query_Query-lean). - * [mongoose-cast-aggregation](https://www.npmjs.com/package/mongoose-cast-aggregation) - - You can find a full list of officially supported plugins on [Mongoose's plugins search site](https://plugins.mongoosejs.io/). - - ### Community! - - Not only can you re-use schema functionality in your own projects, but you - also reap the benefits of the Mongoose community as well. Any plugin - published to [npm](https://npmjs.org/) and with - 'mongoose' as an [npm keyword](https://docs.npmjs.com/files/package.json#keywords) - will show up on our [search results](http://plugins.mongoosejs.io) page. diff --git a/docs/populate.html b/docs/populate.html deleted file mode 100644 index 3ecf047fe72..00000000000 --- a/docs/populate.html +++ /dev/null @@ -1,521 +0,0 @@ -Mongoose v5.6.0: Query Population

    Populate

    - - - - -

    MongoDB has the join-like $lookup aggregation operator in versions >= 3.2. Mongoose has a more powerful alternative called populate(), which lets you reference documents in other collections.

    -

    Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s). We may populate a single document, multiple documents, plain object, multiple plain objects, or all objects returned from a query. Let's look at some examples.

    -
    const mongoose = require('mongoose');
    -const Schema = mongoose.Schema;
    -
    -const personSchema = Schema({
    -  _id: Schema.Types.ObjectId,
    -  name: String,
    -  age: Number,
    -  stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
    -});
    -
    -const storySchema = Schema({
    -  author: { type: Schema.Types.ObjectId, ref: 'Person' },
    -  title: String,
    -  fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
    -});
    -
    -const Story = mongoose.model('Story', storySchema);
    -const Person = mongoose.model('Person', personSchema);
    -

    So far we've created two Models. Our Person model has -its stories field set to an array of ObjectIds. The ref option is -what tells Mongoose which model to use during population, in our case -the Story model. All _ids we store here must be document _ids from -the Story model.

    -

    Note: ObjectId, Number, String, and Buffer are valid for use -as refs. However, you should use ObjectId unless you are an advanced -user and have a good reason for doing so.

    - - -

    Saving refs

    - -

    Saving refs to other documents works the same way you normally save -properties, just assign the _id value:

    -
    const author = new Person({
    -  _id: new mongoose.Types.ObjectId(),
    -  name: 'Ian Fleming',
    -  age: 50
    -});
    -
    -author.save(function (err) {
    -  if (err) return handleError(err);
    -
    -  const story1 = new Story({
    -    title: 'Casino Royale',
    -    author: author._id    // assign the _id from the person
    -  });
    -
    -  story1.save(function (err) {
    -    if (err) return handleError(err);
    -    // thats it!
    -  });
    -});
    -

    Population

    - -

    So far we haven't done anything much different. We've merely created a -Person and a Story. Now let's take a look at populating our story's -author using the query builder:

    -
    Story.
    -  findOne({ title: 'Casino Royale' }).
    -  populate('author').
    -  exec(function (err, story) {
    -    if (err) return handleError(err);
    -    console.log('The author is %s', story.author.name);
    -    // prints "The author is Ian Fleming"
    -  });
    -

    Populated paths are no longer set to their original _id , their value -is replaced with the mongoose document returned from the database by -performing a separate query before returning the results.

    -

    Arrays of refs work the same way. Just call the -populate method on the query and an -array of documents will be returned in place of the original _ids.

    -

    Setting Populated Fields

    - -

    You can manually populate a property by setting it to a document. The document -must be an instance of the model your ref property refers to.

    -
    Story.findOne({ title: 'Casino Royale' }, function(error, story) {
    -  if (error) {
    -    return handleError(error);
    -  }
    -  story.author = author;
    -  console.log(story.author.name); // prints "Ian Fleming"
    -});
    -

    What If There's No Foreign Document?

    - -

    Mongoose populate doesn't behave like conventional -SQL joins. When there's no -document, story.author will be null. This is analogous to a -left join in SQL.

    -
    await Person.deleteMany({ name: 'Ian Fleming' });
    -
    -const story = await Story.findOne({ title: 'Casino Royale' }).populate('author');
    -story.author; // `null`
    -

    If you have an array of authors in your storySchema, populate() will -give you an empty array instead.

    -
    const storySchema = Schema({
    -  authors: [{ type: Schema.Types.ObjectId, ref: 'Person' }],
    -  title: String
    -});
    -
    -// Later
    -
    -const story = await Story.findOne({ title: 'Casino Royale' }).populate('authors');
    -story.authors; // `[]`
    -

    Field Selection

    - -

    What if we only want a few specific fields returned for the populated -documents? This can be accomplished by passing the usual -field name syntax as the second argument -to the populate method:

    -
    Story.
    -  findOne({ title: /casino royale/i }).
    -  populate('author', 'name'). // only return the Persons name
    -  exec(function (err, story) {
    -    if (err) return handleError(err);
    -
    -    console.log('The author is %s', story.author.name);
    -    // prints "The author is Ian Fleming"
    -
    -    console.log('The authors age is %s', story.author.age);
    -    // prints "The authors age is null'
    -  });
    -

    Populating Multiple Paths

    - -

    What if we wanted to populate multiple paths at the same time?

    -
    Story.
    -  find(...).
    -  populate('fans').
    -  populate('author').
    -  exec();
    -

    If you call populate() multiple times with the same path, only the last -one will take effect.

    -
    // The 2nd `populate()` call below overwrites the first because they
    -// both populate 'fans'.
    -Story.
    -  find().
    -  populate({ path: 'fans', select: 'name' }).
    -  populate({ path: 'fans', select: 'email' });
    -// The above is equivalent to:
    -Story.find().populate({ path: 'fans', select: 'email' });
    -

    Query conditions and other options

    - -

    What if we wanted to populate our fans array based on their age, select -just their names, and return at most, any 5 of them?

    -
    Story.
    -  find(...).
    -  populate({
    -    path: 'fans',
    -    match: { age: { $gte: 21 }},
    -    // Explicitly exclude `_id`, see http://bit.ly/2aEfTdB
    -    select: 'name -_id',
    -    options: { limit: 5 }
    -  }).
    -  exec();
    -

    Refs to children

    - -

    We may find however, if we use the author object, we are unable to get a -list of the stories. This is because no story objects were ever 'pushed' -onto author.stories.

    -

    There are two perspectives here. First, you may want the author know -which stories are theirs. Usually, your schema should resolve -one-to-many relationships by having a parent pointer in the 'many' side. -But, if you have a good reason to want an array of child pointers, you -can push() documents onto the array as shown below.

    -
    author.stories.push(story1);
    -author.save(callback);
    -

    This allows us to perform a find and populate combo:

    -
    Person.
    -  findOne({ name: 'Ian Fleming' }).
    -  populate('stories'). // only works if we pushed refs to children
    -  exec(function (err, person) {
    -    if (err) return handleError(err);
    -    console.log(person);
    -  });
    -

    It is debatable that we really want two sets of pointers as they may get -out of sync. Instead we could skip populating and directly find() the -stories we are interested in.

    -
    Story.
    -  find({ author: author._id }).
    -  exec(function (err, stories) {
    -    if (err) return handleError(err);
    -    console.log('The stories are an array: ', stories);
    -  });
    -

    The documents returned from -query population become fully -functional, removeable, saveable documents unless the -lean option is specified. Do not confuse -them with sub docs. Take caution when calling its -remove method because you'll be removing it from the database, not just -the array.

    -

    Populating an existing document

    - -

    If we have an existing mongoose document and want to populate some of its -paths, mongoose >= 3.6 supports the -document#populate() method.

    -

    Populating multiple existing documents

    - -

    If we have one or many mongoose documents or even plain objects -(like mapReduce output), we may -populate them using the Model.populate() -method available in mongoose >= 3.6. This is what document#populate() -and query#populate() use to populate documents.

    -

    Populating across multiple levels

    - -

    Say you have a user schema which keeps track of the user's friends.

    -
    var userSchema = new Schema({
    -  name: String,
    -  friends: [{ type: ObjectId, ref: 'User' }]
    -});
    -

    Populate lets you get a list of a user's friends, but what if you also -wanted a user's friends of friends? Specify the populate option to tell -mongoose to populate the friends array of all the user's friends:

    -
    User.
    -  findOne({ name: 'Val' }).
    -  populate({
    -    path: 'friends',
    -    // Get friends of friends - populate the 'friends' array for every friend
    -    populate: { path: 'friends' }
    -  });
    -

    Populating across Databases

    - -

    Let's say you have a schema representing events, and a schema representing -conversations. Each event has a corresponding conversation thread.

    -
    var eventSchema = new Schema({
    -  name: String,
    -  // The id of the corresponding conversation
    -  // Notice there's no ref here!
    -  conversation: ObjectId
    -});
    -var conversationSchema = new Schema({
    -  numMessages: Number
    -});
    -

    Also, suppose that events and conversations are stored in separate MongoDB -instances.

    -
    var db1 = mongoose.createConnection('localhost:27000/db1');
    -var db2 = mongoose.createConnection('localhost:27001/db2');
    -
    -var Event = db1.model('Event', eventSchema);
    -var Conversation = db2.model('Conversation', conversationSchema);
    -

    In this situation, you will not be able to populate() normally. -The conversation field will always be null, because populate() doesn't -know which model to use. However, -you can specify the model explicitly.

    -
    Event.
    -  find().
    -  populate({ path: 'conversation', model: Conversation }).
    -  exec(function(error, docs) { /* ... */ });
    -

    This is known as a "cross-database populate," because it enables you to -populate across MongoDB databases and even across MongoDB instances.

    -

    Dynamic References via `refPath`

    - -

    Mongoose can also populate from multiple collections based on the value -of a property in the document. Let's say you're building a schema for -storing comments. A user may comment on either a blog post or a product.

    -
    const commentSchema = new Schema({
    -  body: { type: String, required: true },
    -  on: {
    -    type: Schema.Types.ObjectId,
    -    required: true,
    -    // Instead of a hardcoded model name in `ref`, `refPath` means Mongoose
    -    // will look at the `onModel` property to find the right model.
    -    refPath: 'onModel'
    -  },
    -  onModel: {
    -    type: String,
    -    required: true,
    -    enum: ['BlogPost', 'Product']
    -  }
    -});
    -
    -const Product = mongoose.model('Product', new Schema({ name: String }));
    -const BlogPost = mongoose.model('BlogPost', new Schema({ title: String }));
    -const Comment = mongoose.model('Comment', commentSchema);
    -

    The refPath option is a more sophisticated alternative to ref. If ref -is just a string, Mongoose will always query the same model to find the -populated subdocs. With refPath, you can configure what model Mongoose -uses for each document.

    -
    const book = await Product.create({ name: 'The Count of Monte Cristo' });
    -const post = await BlogPost.create({ title: 'Top 10 French Novels' });
    -
    -const commentOnBook = await Comment.create({
    -  body: 'Great read',
    -  on: book._id,
    -  onModel: 'Product'
    -});
    -
    -const commentOnPost = await Comment.create({
    -  body: 'Very informative',
    -  on: post._id,
    -  onModel: 'BlogPost'
    -});
    -
    -// The below `populate()` works even though one comment references the
    -// 'Product' collection and the other references the 'BlogPost' collection.
    -const comments = await Comment.find().populate('on').sort({ body: 1 });
    -comments[0].on.name; // "The Count of Monte Cristo"
    -comments[1].on.title; // "Top 10 French Novels"
    -

    An alternative approach is to define separate blogPost and -product properties on commentSchema, and then populate() on both -properties.

    -
    const commentSchema = new Schema({
    -  body: { type: String, required: true },
    -  product: {
    -    type: Schema.Types.ObjectId,
    -    required: true,
    -    ref: 'Product'
    -  },
    -  blogPost: {
    -    type: Schema.Types.ObjectId,
    -    required: true,
    -    ref: 'BlogPost'
    -  }
    -});
    -
    -// ...
    -
    -// The below `populate()` is equivalent to the `refPath` approach, you
    -// just need to make sure you `populate()` both `product` and `blogPost`.
    -const comments = await Comment.find().
    -  populate('product').
    -  populate('blogPost').
    -  sort({ body: 1 });
    -comments[0].product.name; // "The Count of Monte Cristo"
    -comments[1].blogPost.title; // "Top 10 French Novels"
    -

    Defining separate blogPost and product properties works for this simple -example. But, if you decide to allow users to also comment on articles or -other comments, you'll need to add more properties to your schema. You'll -also need an extra populate() call for every property, unless you use -mongoose-autopopulate. -Using refPath means you only need 2 schema paths and one populate() call -regardless of how many models your commentSchema can point to.

    -

    Populate Virtuals

    - -

    So far you've only populated based on the _id field. However, that's -sometimes not the right choice. -In particular, arrays that grow without bound are a MongoDB anti-pattern. -Using mongoose virtuals, you can define more sophisticated relationships -between documents.

    -
    const PersonSchema = new Schema({
    -  name: String,
    -  band: String
    -});
    -
    -const BandSchema = new Schema({
    -  name: String
    -});
    -BandSchema.virtual('members', {
    -  ref: 'Person', // The model to use
    -  localField: 'name', // Find people where `localField`
    -  foreignField: 'band', // is equal to `foreignField`
    -  // If `justOne` is true, 'members' will be a single doc as opposed to
    -  // an array. `justOne` is false by default.
    -  justOne: false,
    -  options: { sort: { name: -1 }, limit: 5 } // Query options, see http://bit.ly/mongoose-query-options
    -});
    -
    -const Person = mongoose.model('Person', PersonSchema);
    -const Band = mongoose.model('Band', BandSchema);
    -
    -/**
    - * Suppose you have 2 bands: "Guns N' Roses" and "Motley Crue"
    - * And 4 people: "Axl Rose" and "Slash" with "Guns N' Roses", and
    - * "Vince Neil" and "Nikki Sixx" with "Motley Crue"
    - */
    -Band.find({}).populate('members').exec(function(error, bands) {
    -  /* `bands.members` is now an array of instances of `Person` */
    -});
    -

    Keep in mind that virtuals are not included in toJSON() output by default. If you want populate virtuals to show up when using functions -that rely on JSON.stringify(), like Express' -res.json() function, -set the virtuals: true option on your schema's toJSON options.

    -
    // Set `virtuals: true` so `res.json()` works
    -const BandSchema = new Schema({
    -  name: String
    -}, { toJSON: { virtuals: true } });
    -

    If you're using populate projections, make sure foreignField is included -in the projection.

    -
    Band.
    -  find({}).
    -  populate({ path: 'members', select: 'name' }).
    -  exec(function(error, bands) {
    -    // Won't work, foreign field `band` is not selected in the projection
    -  });
    -
    -Band.
    -  find({}).
    -  populate({ path: 'members', select: 'name band' }).
    -  exec(function(error, bands) {
    -    // Works, foreign field `band` is selected
    -  });
    -

    Populate Virtuals: The Count Option

    - -

    Populate virtuals also support counting the number of documents with -matching foreignField as opposed to the documents themselves. Set the -count option on your virtual:

    -
    const PersonSchema = new Schema({
    -  name: String,
    -  band: String
    -});
    -
    -const BandSchema = new Schema({
    -  name: String
    -});
    -BandSchema.virtual('numMembers', {
    -  ref: 'Person', // The model to use
    -  localField: 'name', // Find people where `localField`
    -  foreignField: 'band', // is equal to `foreignField`
    -  count: true // And only get the number of docs
    -});
    -
    -// Later
    -const doc = await Band.findOne({ name: 'Motley Crue' }).
    -  populate('numMembers');
    -doc.numMembers; // 2
    -

    Populate in Middleware

    - -

    You can populate in either pre or post hooks. If you want to -always populate a certain field, check out the mongoose-autopopulate plugin.

    -
    // Always attach `populate()` to `find()` calls
    -MySchema.pre('find', function() {
    -  this.populate('user');
    -});
    -
    // Always `populate()` after `find()` calls. Useful if you want to selectively populate
    -// based on the docs found.
    -MySchema.post('find', async function(docs) {
    -  for (let doc of docs) {
    -    if (doc.isPublic) {
    -      await doc.populate('user').execPopulate();
    -    }
    -  }
    -});
    -
    // `populate()` after saving. Useful for sending populated data back to the client in an
    -// update API endpoint
    -MySchema.post('save', function(doc, next) {
    -  doc.populate('user').execPopulate().then(function() {
    -    next();
    -  });
    -});
    -

    Next Up

    -

    Now that we've covered populate(), let's take a look at discriminators.

    -
    \ No newline at end of file diff --git a/docs/populate.md b/docs/populate.md new file mode 100644 index 00000000000..c7c8bdbd8c5 --- /dev/null +++ b/docs/populate.md @@ -0,0 +1,816 @@ +## Populate + +MongoDB has the join-like [$lookup](https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/) aggregation operator in versions >= 3.2. Mongoose has a more powerful alternative called `populate()`, which lets you reference documents in other collections. + +Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s). We may populate a single document, multiple documents, a plain object, multiple plain objects, or all objects returned from a query. Let's look at some examples. + +```javascript +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const personSchema = Schema({ + _id: Schema.Types.ObjectId, + name: String, + age: Number, + stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }] +}); + +const storySchema = Schema({ + author: { type: Schema.Types.ObjectId, ref: 'Person' }, + title: String, + fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }] +}); + +const Story = mongoose.model('Story', storySchema); +const Person = mongoose.model('Person', personSchema); +``` + +So far we've created two [Models](./models.html). Our `Person` model has +its `stories` field set to an array of `ObjectId`s. The `ref` option is +what tells Mongoose which model to use during population, in our case +the `Story` model. All `_id`s we store here must be document `_id`s from +the `Story` model. + +**Note**: `ObjectId`, `Number`, `String`, and `Buffer` are valid for use +as refs. However, you should use `ObjectId` unless you are an advanced +user and have a good reason for doing so. + + + +

    Saving refs

    + +Saving refs to other documents works the same way you normally save +properties, just assign the `_id` value: + +```javascript +const author = new Person({ + _id: new mongoose.Types.ObjectId(), + name: 'Ian Fleming', + age: 50 +}); + +author.save(function (err) { + if (err) return handleError(err); + + const story1 = new Story({ + title: 'Casino Royale', + author: author._id // assign the _id from the person + }); + + story1.save(function (err) { + if (err) return handleError(err); + // that's it! + }); +}); +``` + +

    Population

    + +So far we haven't done anything much different. We've merely created a +`Person` and a `Story`. Now let's take a look at populating our story's +`author` using the query builder: + +```javascript +Story. + findOne({ title: 'Casino Royale' }). + populate('author'). + exec(function (err, story) { + if (err) return handleError(err); + console.log('The author is %s', story.author.name); + // prints "The author is Ian Fleming" + }); +``` + +Populated paths are no longer set to their original `_id` , their value +is replaced with the mongoose document returned from the database by +performing a separate query before returning the results. + +Arrays of refs work the same way. Just call the +[populate](./api.html#query_Query-populate) method on the query and an +array of documents will be returned _in place_ of the original `_id`s. + +

    Setting Populated Fields

    + +You can manually populate a property by setting it to a document. The document +must be an instance of the model your `ref` property refers to. + +```javascript +Story.findOne({ title: 'Casino Royale' }, function(error, story) { + if (error) { + return handleError(error); + } + story.author = author; + console.log(story.author.name); // prints "Ian Fleming" +}); +``` + +

    Checking Whether a Field is Populated

    + +You can call the `populated()` function to check whether a field is populated. +If `populated()` returns a [truthy value](https://masteringjs.io/tutorials/fundamentals/truthy), +you can assume the field is populated. + +```javascript +story.populated('author'); // truthy + +story.depopulate('author'); // Make `author` not populated anymore +story.populated('author'); // undefined +``` + +A common reason for checking whether a path is populated is getting the `author` +id. However, for your convenience, Mongoose adds a [`_id` getter to ObjectId instances](/docs/api/mongoose.html#mongoose_Mongoose-set) +so you can use `story.author._id` regardless of whether `author` is populated. + +```javascript +story.populated('author'); // truthy +story.author._id; // ObjectId + +story.depopulate('author'); // Make `author` not populated anymore +story.populated('author'); // undefined + +story.author instanceof ObjectId; // true +story.author._id; // ObjectId, because Mongoose adds a special getter +``` + +

    What If There's No Foreign Document?

    + +Mongoose populate doesn't behave like conventional +[SQL joins](https://www.w3schools.com/sql/sql_join.asp). When there's no +document, `story.author` will be `null`. This is analogous to a +[left join](https://www.w3schools.com/sql/sql_join_left.asp) in SQL. + +```javascript +await Person.deleteMany({ name: 'Ian Fleming' }); + +const story = await Story.findOne({ title: 'Casino Royale' }).populate('author'); +story.author; // `null` +``` + +If you have an array of `authors` in your `storySchema`, `populate()` will +give you an empty array instead. + +```javascript +const storySchema = Schema({ + authors: [{ type: Schema.Types.ObjectId, ref: 'Person' }], + title: String +}); + +// Later + +const story = await Story.findOne({ title: 'Casino Royale' }).populate('authors'); +story.authors; // `[]` +``` + +

    Field Selection

    + +What if we only want a few specific fields returned for the populated +documents? This can be accomplished by passing the usual +[field name syntax](./api.html#query_Query-select) as the second argument +to the populate method: + +```javascript +Story. + findOne({ title: /casino royale/i }). + populate('author', 'name'). // only return the Persons name + exec(function (err, story) { + if (err) return handleError(err); + + console.log('The author is %s', story.author.name); + // prints "The author is Ian Fleming" + + console.log('The authors age is %s', story.author.age); + // prints "The authors age is null" + }); +``` + +

    Populating Multiple Paths

    + +What if we wanted to populate multiple paths at the same time? + +```javascript +Story. + find(...). + populate('fans'). + populate('author'). + exec(); +``` + +If you call `populate()` multiple times with the same path, only the last +one will take effect. + +```javascript +// The 2nd `populate()` call below overwrites the first because they +// both populate 'fans'. +Story. + find(). + populate({ path: 'fans', select: 'name' }). + populate({ path: 'fans', select: 'email' }); +// The above is equivalent to: +Story.find().populate({ path: 'fans', select: 'email' }); +``` + +

    Query conditions and other options

    + +What if we wanted to populate our fans array based on their age and +select just their names? + +```javascript +Story. + find(). + populate({ + path: 'fans', + match: { age: { $gte: 21 } }, + // Explicitly exclude `_id`, see http://bit.ly/2aEfTdB + select: 'name -_id' + }). + exec(); +``` + +The `match` option doesn't filter out `Story` documents. If there are no documents that satisfy `match`, +you'll get a `Story` document with an empty `fans` array. + +For example, suppose you `populate()` a story's `author` and the `author` doesn't satisfy `match`. Then +the story's `author` will be `null`. + +```javascript +const story = await Story. + findOne({ title: 'Casino Royale' }). + populate({ path: 'author', name: { $ne: 'Ian Fleming' } }). + exec(); +story.author; // `null` +``` + +In general, there is no way to make `populate()` filter stories based on properties of the story's `author`. +For example, the below query won't return any results, even though `author` is populated. + +```javascript +const story = await Story. + findOne({ 'author.name': 'Ian Fleming' }). + populate('author'). + exec(); +story; // null +``` + +If you want to filter stories by their author's name, you should use [denormalization](https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design-part-3). + +

    limit vs. perDocumentLimit

    + +Populate does support a `limit` option, however, it currently +does **not** limit on a per-document basis for backwards compatibility. For example, +suppose you have 2 stories: + +```javascript +Story.create([ + { title: 'Casino Royale', fans: [1, 2, 3, 4, 5, 6, 7, 8] }, + { title: 'Live and Let Die', fans: [9, 10] } +]); +``` + +If you were to `populate()` using the `limit` option, you +would find that the 2nd story has 0 fans: + +```javascript +const stories = Story.find().populate({ + path: 'fans', + options: { limit: 2 } +}); + +stories[0].name; // 'Casino Royale' +stories[0].fans.length; // 2 + +// 2nd story has 0 fans! +stories[1].name; // 'Live and Let Die' +stories[1].fans.length; // 0 +``` + +That's because, in order to avoid executing a separate query +for each document, Mongoose instead queries for fans using +`numDocuments * limit` as the limit. If you need the correct +`limit`, you should use the `perDocumentLimit` option (new in Mongoose 5.9.0). +Just keep in mind that `populate()` will execute a separate query +for each story, which may cause `populate()` to be slower. + +```javascript +const stories = await Story.find().populate({ + path: 'fans', + // Special option that tells Mongoose to execute a separate query + // for each `story` to make sure we get 2 fans for each story. + perDocumentLimit: 2 +}); + +stories[0].name; // 'Casino Royale' +stories[0].fans.length; // 2 + +stories[1].name; // 'Live and Let Die' +stories[1].fans.length; // 2 +``` + +

    Refs to children

    + +We may find however, if we use the `author` object, we are unable to get a +list of the stories. This is because no `story` objects were ever 'pushed' +onto `author.stories`. + +There are two perspectives here. First, you may want the `author` know +which stories are theirs. Usually, your schema should resolve +one-to-many relationships by having a parent pointer in the 'many' side. +But, if you have a good reason to want an array of child pointers, you +can `push()` documents onto the array as shown below. + +```javascript +author.stories.push(story1); +author.save(callback); +``` + +This allows us to perform a `find` and `populate` combo: + +```javascript +Person. + findOne({ name: 'Ian Fleming' }). + populate('stories'). // only works if we pushed refs to children + exec(function (err, person) { + if (err) return handleError(err); + console.log(person); + }); +``` + +It is debatable that we really want two sets of pointers as they may get +out of sync. Instead we could skip populating and directly `find()` the +stories we are interested in. + +```javascript +Story. + find({ author: author._id }). + exec(function (err, stories) { + if (err) return handleError(err); + console.log('The stories are an array: ', stories); + }); +``` + +The documents returned from +[query population](./api.html#query_Query-populate) become fully +functional, `remove`able, `save`able documents unless the +[lean](./api.html#query_Query-lean) option is specified. Do not confuse +them with [sub docs](./subdocs.html). Take caution when calling its +remove method because you'll be removing it from the database, not just +the array. + +

    Populating an existing document

    + +If you have an existing mongoose document and want to populate some of its +paths, you can use the +[Document#populate()](./api.html#document_Document-populate) method. +Just make sure you call [`Document#execPopulate()`](/docs/api/document.html#document_Document-execPopulate) +to execute the `populate()`. + +```javascript +const person = await Person.findOne({ name: 'Ian Fleming' }); + +person.populated('stories'); // null + +// Call the `populate()` method on a document to populate a path. +// Need to call `execPopulate()` to actually execute the `populate()`. +await person.populate('stories').execPopulate(); + +person.populated('stories'); // Array of ObjectIds +person.stories[0].name; // 'Casino Royale' +``` + +The `Document#populate()` method supports chaining, so you can chain +multiple `populate()` calls together. + +```javascript +await person.populate('stories').populate('fans').execPopulate(); +person.populated('fans'); // Array of ObjectIds +``` + +

    Populating multiple existing documents

    + +If we have one or many mongoose documents or even plain objects +(_like [mapReduce](./api.html#model_Model.mapReduce) output_), we may +populate them using the [Model.populate()](./api.html#model_Model.populate) +method. This is what `Document#populate()` +and `Query#populate()` use to populate documents. + +

    Populating across multiple levels

    + +Say you have a user schema which keeps track of the user's friends. + +```javascript +const userSchema = new Schema({ + name: String, + friends: [{ type: ObjectId, ref: 'User' }] +}); +``` + +Populate lets you get a list of a user's friends, but what if you also +wanted a user's friends of friends? Specify the `populate` option to tell +mongoose to populate the `friends` array of all the user's friends: + +```javascript +User. + findOne({ name: 'Val' }). + populate({ + path: 'friends', + // Get friends of friends - populate the 'friends' array for every friend + populate: { path: 'friends' } + }); +``` + +

    Cross Database Populate

    + +Let's say you have a schema representing events, and a schema representing +conversations. Each event has a corresponding conversation thread. + +```javascript +const db1 = mongoose.createConnection('mongodb://localhost:27000/db1'); +const db2 = mongoose.createConnection('mongodb://localhost:27001/db2'); + +const conversationSchema = new Schema({ numMessages: Number }); +const Conversation = db2.model('Conversation', conversationSchema); + +const eventSchema = new Schema({ + name: String, + conversation: { + type: ObjectId, + ref: Conversation // `ref` is a **Model class**, not a string + } +}); +const Event = db1.model('Event', eventSchema); +``` + +In the above example, events and conversations are stored in separate MongoDB +databases. String `ref` will not work in this situation, because Mongoose +assumes a string `ref` refers to a model name on the same connection. In +the above example, the conversation model is registered on `db2`, not `db1`. + +```javascript +// Works +const events = await Event. + find(). + populate('conversation'); +``` + +This is known as a "cross-database populate," because it enables you to +populate across MongoDB databases and even across MongoDB instances. + +If you don't have access to the model instance when defining your `eventSchema`, +you can also pass [the model instance as an option to `populate()`](/docs/api/model.html#model_Model.populate). + +```javascript +const events = await Event. + find(). + // The `model` option specifies the model to use for populating. + populate({ path: 'conversation', model: Conversation }); +``` + +

    Dynamic References via `refPath`

    + +Mongoose can also populate from multiple collections based on the value +of a property in the document. Let's say you're building a schema for +storing comments. A user may comment on either a blog post or a product. + +```javascript +const commentSchema = new Schema({ + body: { type: String, required: true }, + on: { + type: Schema.Types.ObjectId, + required: true, + // Instead of a hardcoded model name in `ref`, `refPath` means Mongoose + // will look at the `onModel` property to find the right model. + refPath: 'onModel' + }, + onModel: { + type: String, + required: true, + enum: ['BlogPost', 'Product'] + } +}); + +const Product = mongoose.model('Product', new Schema({ name: String })); +const BlogPost = mongoose.model('BlogPost', new Schema({ title: String })); +const Comment = mongoose.model('Comment', commentSchema); +``` + +The `refPath` option is a more sophisticated alternative to `ref`. If `ref` +is just a string, Mongoose will always query the same model to find the +populated subdocs. With `refPath`, you can configure what model Mongoose +uses for each document. + +```javascript +const book = await Product.create({ name: 'The Count of Monte Cristo' }); +const post = await BlogPost.create({ title: 'Top 10 French Novels' }); + +const commentOnBook = await Comment.create({ + body: 'Great read', + on: book._id, + onModel: 'Product' +}); + +const commentOnPost = await Comment.create({ + body: 'Very informative', + on: post._id, + onModel: 'BlogPost' +}); + +// The below `populate()` works even though one comment references the +// 'Product' collection and the other references the 'BlogPost' collection. +const comments = await Comment.find().populate('on').sort({ body: 1 }); +comments[0].on.name; // "The Count of Monte Cristo" +comments[1].on.title; // "Top 10 French Novels" +``` + +An alternative approach is to define separate `blogPost` and +`product` properties on `commentSchema`, and then `populate()` on both +properties. + +```javascript +const commentSchema = new Schema({ + body: { type: String, required: true }, + product: { + type: Schema.Types.ObjectId, + required: true, + ref: 'Product' + }, + blogPost: { + type: Schema.Types.ObjectId, + required: true, + ref: 'BlogPost' + } +}); + +// ... + +// The below `populate()` is equivalent to the `refPath` approach, you +// just need to make sure you `populate()` both `product` and `blogPost`. +const comments = await Comment.find(). + populate('product'). + populate('blogPost'). + sort({ body: 1 }); +comments[0].product.name; // "The Count of Monte Cristo" +comments[1].blogPost.title; // "Top 10 French Novels" +``` + +Defining separate `blogPost` and `product` properties works for this simple +example. But, if you decide to allow users to also comment on articles or +other comments, you'll need to add more properties to your schema. You'll +also need an extra `populate()` call for every property, unless you use +[mongoose-autopopulate](https://www.npmjs.com/package/mongoose-autopopulate). +Using `refPath` means you only need 2 schema paths and one `populate()` call +regardless of how many models your `commentSchema` can point to. + +

    Populate Virtuals

    + +So far you've only populated based on the `_id` field. However, that's +sometimes not the right choice. +In particular, [arrays that grow without bound are a MongoDB anti-pattern](https://docs.mongodb.com/manual/tutorial/model-referenced-one-to-many-relationships-between-documents/). +Using mongoose virtuals, you can define more sophisticated relationships +between documents. + +```javascript +const PersonSchema = new Schema({ + name: String, + band: String +}); + +const BandSchema = new Schema({ + name: String +}); +BandSchema.virtual('members', { + ref: 'Person', // The model to use + localField: 'name', // Find people where `localField` + foreignField: 'band', // is equal to `foreignField` + // If `justOne` is true, 'members' will be a single doc as opposed to + // an array. `justOne` is false by default. + justOne: false, + options: { sort: { name: -1 }, limit: 5 } // Query options, see http://bit.ly/mongoose-query-options +}); + +const Person = mongoose.model('Person', PersonSchema); +const Band = mongoose.model('Band', BandSchema); + +/** + * Suppose you have 2 bands: "Guns N' Roses" and "Motley Crue" + * And 4 people: "Axl Rose" and "Slash" with "Guns N' Roses", and + * "Vince Neil" and "Nikki Sixx" with "Motley Crue" + */ +Band.find({}).populate('members').exec(function(error, bands) { + /* `bands.members` is now an array of instances of `Person` */ +}); +``` + +You can also use [the populate `match` option](http://thecodebarbarian.com/mongoose-5-5-static-hooks-and-populate-match-functions.html#populate-match-function) +to add an additional filter to the `populate()` query. This is useful +if you need to split up `populate()` data: + +```javascript +const PersonSchema = new Schema({ + name: String, + band: String, + isActive: Boolean +}); + +const BandSchema = new Schema({ + name: String +}); +BandSchema.virtual('activeMembers', { + ref: 'Person', + localField: 'name', + foreignField: 'band', + justOne: false, + match: { isActive: true } +}); +BandSchema.virtual('formerMembers', { + ref: 'Person', + localField: 'name', + foreignField: 'band', + justOne: false, + match: { isActive: false } +}); +``` + +Keep in mind that virtuals are _not_ included in `toJSON()` and `toObject()` output by default. If you want populate +virtuals to show up when using functions that rely on `JSON.stringify()`, like Express' +[`res.json()` function](https://masteringjs.io/tutorials/express/json), +set the `virtuals: true` option on your schema's `toJSON` options. + +```javascript +const BandSchema = new Schema({ name: String }, { + toJSON: { virtuals: true }, // So `res.json()` and other `JSON.stringify()` functions include virtuals + toObject: { virtuals: true } // So `toObject()` output includes virtuals +}); +``` + +If you're using populate projections, make sure `foreignField` is included +in the projection. + +```javascript +Band. + find({}). + populate({ path: 'members', select: 'name' }). + exec(function(error, bands) { + // Won't work, foreign field `band` is not selected in the projection + }); + +Band. + find({}). + populate({ path: 'members', select: 'name band' }). + exec(function(error, bands) { + // Works, foreign field `band` is selected + }); +``` + +

    Populate Virtuals: The Count Option

    + +Populate virtuals also support counting the number of documents with +matching `foreignField` as opposed to the documents themselves. Set the +`count` option on your virtual: + +```javascript +const PersonSchema = new Schema({ + name: String, + band: String +}); + +const BandSchema = new Schema({ + name: String +}); +BandSchema.virtual('numMembers', { + ref: 'Person', // The model to use + localField: 'name', // Find people where `localField` + foreignField: 'band', // is equal to `foreignField` + count: true // And only get the number of docs +}); + +// Later +const doc = await Band.findOne({ name: 'Motley Crue' }). + populate('numMembers'); +doc.numMembers; // 2 +``` + +

    Populating Maps

    + +[Maps](/docs/schematypes.html#maps) are a type that represents an object with arbitrary +string keys. For example, in the below schema, `members` is a map from strings to ObjectIds. + +```javascript +const BandSchema = new Schema({ + name: String, + members: { + type: Map, + of: { + type: 'ObjectId', + ref: 'Person' + } + } +}); +const Band = mongoose.model('Band', bandSchema); +``` + +This map has a `ref`, which means you can use `populate()` to populate all the ObjectIds +in the map. Suppose you have the below `band` document: + +``` +const person1 = new Person({ name: 'Vince Neil' }); +const person2 = new Person({ name: 'Mick Mars' }); + +const band = new Band({ + name: 'Motley Crue', + members: { + 'singer': person1._id, + 'guitarist': person2._id + } +}); +``` + +You can `populate()` every element in the map by populating the special path `members.$*`. +`$*` is a special syntax that tells Mongoose to look at every key in the map. + +```javascript +const band = await Band.findOne({ name: 'Motley Crue' }).populate('members.$*'); + +band.members.get('singer'); // { _id: ..., name: 'Vince Neil' } +``` + +You can also populate paths in maps of subdocuments using `$*`. For example, suppose you +have the below `librarySchema`: + +```javascript +const librarySchema = new Schema({ + name: String, + books: { + type: Map, + of: new Schema({ + title: String, + author: { + type: 'ObjectId', + ref: 'Person' + } + }) + } +}); +const Library = mongoose.model('Library, librarySchema'); +``` + +You can `populate()` every book's author by populating `books.$*.author`: + +```javascript +const libraries = await Library.find().populate('books.$*.author'); +``` + +

    Populate in Middleware

    + +You can populate in either pre or post [hooks](http://mongoosejs.com/docs/middleware.html). If you want to +always populate a certain field, check out the [mongoose-autopopulate plugin](http://npmjs.com/package/mongoose-autopopulate). + +```javascript +// Always attach `populate()` to `find()` calls +MySchema.pre('find', function() { + this.populate('user'); +}); +``` + +```javascript +// Always `populate()` after `find()` calls. Useful if you want to selectively populate +// based on the docs found. +MySchema.post('find', async function(docs) { + for (let doc of docs) { + if (doc.isPublic) { + await doc.populate('user').execPopulate(); + } + } +}); +``` + +```javascript +// `populate()` after saving. Useful for sending populated data back to the client in an +// update API endpoint +MySchema.post('save', function(doc, next) { + doc.populate('user').execPopulate().then(function() { + next(); + }); +}); +``` + +### Next Up + +Now that we've covered `populate()`, let's take a look at [discriminators](/docs/discriminators.html). diff --git a/docs/populate.pug b/docs/populate.pug deleted file mode 100644 index 9ff769a0b54..00000000000 --- a/docs/populate.pug +++ /dev/null @@ -1,767 +0,0 @@ -extends layout - -append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown - ## Populate - - - - - - MongoDB has the join-like [$lookup](https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/) aggregation operator in versions >= 3.2. Mongoose has a more powerful alternative called `populate()`, which lets you reference documents in other collections. - - Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s). We may populate a single document, multiple documents, a plain object, multiple plain objects, or all objects returned from a query. Let's look at some examples. - - ```javascript - const mongoose = require('mongoose'); - const Schema = mongoose.Schema; - - const personSchema = Schema({ - _id: Schema.Types.ObjectId, - name: String, - age: Number, - stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }] - }); - - const storySchema = Schema({ - author: { type: Schema.Types.ObjectId, ref: 'Person' }, - title: String, - fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }] - }); - - const Story = mongoose.model('Story', storySchema); - const Person = mongoose.model('Person', personSchema); - ``` - - So far we've created two [Models](./models.html). Our `Person` model has - its `stories` field set to an array of `ObjectId`s. The `ref` option is - what tells Mongoose which model to use during population, in our case - the `Story` model. All `_id`s we store here must be document `_id`s from - the `Story` model. - - **Note**: `ObjectId`, `Number`, `String`, and `Buffer` are valid for use - as refs. However, you should use `ObjectId` unless you are an advanced - user and have a good reason for doing so. - - - -

    Saving refs

    - - Saving refs to other documents works the same way you normally save - properties, just assign the `_id` value: - - ```javascript - const author = new Person({ - _id: new mongoose.Types.ObjectId(), - name: 'Ian Fleming', - age: 50 - }); - - author.save(function (err) { - if (err) return handleError(err); - - const story1 = new Story({ - title: 'Casino Royale', - author: author._id // assign the _id from the person - }); - - story1.save(function (err) { - if (err) return handleError(err); - // that's it! - }); - }); - ``` - -

    Population

    - - So far we haven't done anything much different. We've merely created a - `Person` and a `Story`. Now let's take a look at populating our story's - `author` using the query builder: - - ```javascript - Story. - findOne({ title: 'Casino Royale' }). - populate('author'). - exec(function (err, story) { - if (err) return handleError(err); - console.log('The author is %s', story.author.name); - // prints "The author is Ian Fleming" - }); - ``` - - Populated paths are no longer set to their original `_id` , their value - is replaced with the mongoose document returned from the database by - performing a separate query before returning the results. - - Arrays of refs work the same way. Just call the - [populate](./api.html#query_Query-populate) method on the query and an - array of documents will be returned _in place_ of the original `_id`s. - -

    Setting Populated Fields

    - - You can manually populate a property by setting it to a document. The document - must be an instance of the model your `ref` property refers to. - - ```javascript - Story.findOne({ title: 'Casino Royale' }, function(error, story) { - if (error) { - return handleError(error); - } - story.author = author; - console.log(story.author.name); // prints "Ian Fleming" - }); - ``` - -

    Checking Whether a Field is Populated

    - - You can call the `populated()` function to check whether a field is populated. - If `populated()` returns a [truthy value](https://masteringjs.io/tutorials/fundamentals/truthy), - you can assume the field is populated. - - ```javascript - story.populated('author'); // truthy - - story.depopulate('author'); // Make `author` not populated anymore - story.populated('author'); // undefined - ``` - - A common reason for checking whether a path is populated is getting the `author` - id. However, for your convenience, Mongoose adds a [`_id` getter to ObjectId instances](/docs/api/mongoose.html#mongoose_Mongoose-set) - so you can use `story.author._id` regardless of whether `author` is populated. - - ```javascript - story.populated('author'); // truthy - story.author._id; // ObjectId - - story.depopulate('author'); // Make `author` not populated anymore - story.populated('author'); // undefined - - story.author instanceof ObjectId; // true - story.author._id; // ObjectId, because Mongoose adds a special getter - ``` - -

    What If There's No Foreign Document?

    - - Mongoose populate doesn't behave like conventional - [SQL joins](https://www.w3schools.com/sql/sql_join.asp). When there's no - document, `story.author` will be `null`. This is analogous to a - [left join](https://www.w3schools.com/sql/sql_join_left.asp) in SQL. - - ```javascript - await Person.deleteMany({ name: 'Ian Fleming' }); - - const story = await Story.findOne({ title: 'Casino Royale' }).populate('author'); - story.author; // `null` - ``` - - If you have an array of `authors` in your `storySchema`, `populate()` will - give you an empty array instead. - - ```javascript - const storySchema = Schema({ - authors: [{ type: Schema.Types.ObjectId, ref: 'Person' }], - title: String - }); - - // Later - - const story = await Story.findOne({ title: 'Casino Royale' }).populate('authors'); - story.authors; // `[]` - ``` - -

    Field Selection

    - - What if we only want a few specific fields returned for the populated - documents? This can be accomplished by passing the usual - [field name syntax](./api.html#query_Query-select) as the second argument - to the populate method: - - ```javascript - Story. - findOne({ title: /casino royale/i }). - populate('author', 'name'). // only return the Persons name - exec(function (err, story) { - if (err) return handleError(err); - - console.log('The author is %s', story.author.name); - // prints "The author is Ian Fleming" - - console.log('The authors age is %s', story.author.age); - // prints "The authors age is null' - }); - ``` - -

    Populating Multiple Paths

    - - What if we wanted to populate multiple paths at the same time? - - ```javascript - Story. - find(...). - populate('fans'). - populate('author'). - exec(); - ``` - - If you call `populate()` multiple times with the same path, only the last - one will take effect. - - ```javascript - // The 2nd `populate()` call below overwrites the first because they - // both populate 'fans'. - Story. - find(). - populate({ path: 'fans', select: 'name' }). - populate({ path: 'fans', select: 'email' }); - // The above is equivalent to: - Story.find().populate({ path: 'fans', select: 'email' }); - ``` - -

    Query conditions and other options

    - - What if we wanted to populate our fans array based on their age and - select just their names? - - ```javascript - Story. - find(). - populate({ - path: 'fans', - match: { age: { $gte: 21 } }, - // Explicitly exclude `_id`, see http://bit.ly/2aEfTdB - select: 'name -_id' - }). - exec(); - ``` - - The `match` option doesn't filter out `Story` documents. If there are no documents that satisfy `match`, - you'll get a `Story` document with an empty `fans` array. - - For example, suppose you `populate()` a story's `author` and the `author` doesn't satisfy `match`. Then - the story's `author` will be `null`. - - ```javascript - const story = await Story. - findOne({ title: 'Casino Royale' }). - populate({ path: 'author', name: { $ne: 'Ian Fleming' } }). - exec(); - story.author; // `null` - ``` - - In general, there is no way to make `populate()` filter stories based on properties of the story's `author`. - For example, the below query won't return any results, even though `author` is populated. - - ```javascript - const story = await Story. - findOne({ 'author.name': 'Ian Fleming' }). - populate('author'). - exec(); - story; // null - ``` - - If you want to filter stories by their author's name, you should use [denormalization](https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design-part-3). - -

    limit vs. perDocumentLimit

    - - Populate does support a `limit` option, however, it currently - does **not** limit on a per-document basis for backwards compatibility. For example, - suppose you have 2 stories: - - ```javascript - Story.create([ - { title: 'Casino Royale', fans: [1, 2, 3, 4, 5, 6, 7, 8] }, - { title: 'Live and Let Die', fans: [9, 10] } - ]); - ``` - - If you were to `populate()` using the `limit` option, you - would find that the 2nd story has 0 fans: - - ```javascript - const stories = Story.find().populate({ - path: 'fans', - options: { limit: 2 } - }); - - stories[0].name; // 'Casino Royale' - stories[0].fans.length; // 2 - - // 2nd story has 0 fans! - stories[1].name; // 'Live and Let Die' - stories[1].fans.length; // 0 - ``` - - That's because, in order to avoid executing a separate query - for each document, Mongoose instead queries for fans using - `numDocuments * limit` as the limit. If you need the correct - `limit`, you should use the `perDocumentLimit` option (new in Mongoose 5.9.0). - Just keep in mind that `populate()` will execute a separate query - for each story, which may cause `populate()` to be slower. - - ```javascript - const stories = await Story.find().populate({ - path: 'fans', - // Special option that tells Mongoose to execute a separate query - // for each `story` to make sure we get 2 fans for each story. - perDocumentLimit: 2 - }); - - stories[0].name; // 'Casino Royale' - stories[0].fans.length; // 2 - - stories[1].name; // 'Live and Let Die' - stories[1].fans.length; // 2 - ``` - -

    Refs to children

    - - We may find however, if we use the `author` object, we are unable to get a - list of the stories. This is because no `story` objects were ever 'pushed' - onto `author.stories`. - - There are two perspectives here. First, you may want the `author` know - which stories are theirs. Usually, your schema should resolve - one-to-many relationships by having a parent pointer in the 'many' side. - But, if you have a good reason to want an array of child pointers, you - can `push()` documents onto the array as shown below. - - ```javascript - author.stories.push(story1); - author.save(callback); - ``` - - This allows us to perform a `find` and `populate` combo: - - ```javascript - Person. - findOne({ name: 'Ian Fleming' }). - populate('stories'). // only works if we pushed refs to children - exec(function (err, person) { - if (err) return handleError(err); - console.log(person); - }); - ``` - - It is debatable that we really want two sets of pointers as they may get - out of sync. Instead we could skip populating and directly `find()` the - stories we are interested in. - - ```javascript - Story. - find({ author: author._id }). - exec(function (err, stories) { - if (err) return handleError(err); - console.log('The stories are an array: ', stories); - }); - ``` - - The documents returned from - [query population](./api.html#query_Query-populate) become fully - functional, `remove`able, `save`able documents unless the - [lean](./api.html#query_Query-lean) option is specified. Do not confuse - them with [sub docs](./subdocs.html). Take caution when calling its - remove method because you'll be removing it from the database, not just - the array. - -

    Populating an existing document

    - - If you have an existing mongoose document and want to populate some of its - paths, you can use the - [Document#populate()](./api.html#document_Document-populate) method. - Just make sure you call [`Document#execPopulate()`](/docs/api/document.html#document_Document-execPopulate) - to execute the `populate()`. - - ```javascript - const person = await Person.findOne({ name: 'Ian Fleming' }); - - person.populated('stories'); // null - - // Call the `populate()` method on a document to populate a path. - // Need to call `execPopulate()` to actually execute the `populate()`. - await person.populate('stories').execPopulate(); - - person.populated('stories'); // Array of ObjectIds - person.stories[0].name; // 'Casino Royale' - ``` - - The `Document#populate()` method supports chaining, so you can chain - multiple `populate()` calls together. - - ```javascript - await person.populate('stories').populate('fans').execPopulate(); - person.populated('fans'); // Array of ObjectIds - ``` - -

    Populating multiple existing documents

    - - If we have one or many mongoose documents or even plain objects - (_like [mapReduce](./api.html#model_Model.mapReduce) output_), we may - populate them using the [Model.populate()](./api.html#model_Model.populate) - method. This is what `Document#populate()` - and `Query#populate()` use to populate documents. - -

    Populating across multiple levels

    - - Say you have a user schema which keeps track of the user's friends. - - ```javascript - const userSchema = new Schema({ - name: String, - friends: [{ type: ObjectId, ref: 'User' }] - }); - ``` - - Populate lets you get a list of a user's friends, but what if you also - wanted a user's friends of friends? Specify the `populate` option to tell - mongoose to populate the `friends` array of all the user's friends: - - ```javascript - User. - findOne({ name: 'Val' }). - populate({ - path: 'friends', - // Get friends of friends - populate the 'friends' array for every friend - populate: { path: 'friends' } - }); - ``` - -

    Cross Database Populate

    - - Let's say you have a schema representing events, and a schema representing - conversations. Each event has a corresponding conversation thread. - - ```javascript - const db1 = mongoose.createConnection('mongodb://localhost:27000/db1'); - const db2 = mongoose.createConnection('mongodb://localhost:27001/db2'); - - const conversationSchema = new Schema({ numMessages: Number }); - const Conversation = db2.model('Conversation', conversationSchema); - - const eventSchema = new Schema({ - name: String, - conversation: { - type: ObjectId, - ref: Conversation // `ref` is a **Model class**, not a string - } - }); - const Event = db1.model('Event', eventSchema); - ``` - - In the above example, events and conversations are stored in separate MongoDB - databases. String `ref` will not work in this situation, because Mongoose - assumes a string `ref` refers to a model name on the same connection. In - the above example, the conversation model is registered on `db2`, not `db1`. - - ```javascript - // Works - const events = await Event. - find(). - populate('conversation'); - ``` - - This is known as a "cross-database populate," because it enables you to - populate across MongoDB databases and even across MongoDB instances. - - If you don't have access to the model instance when defining your `eventSchema`, - you can also pass [the model instance as an option to `populate()`](/docs/api/model.html#model_Model.populate). - - ```javascript - const events = await Event. - find(). - // The `model` option specifies the model to use for populating. - populate({ path: 'conversation', model: Conversation }); - ``` - -

    Dynamic References via `refPath`

    - - Mongoose can also populate from multiple collections based on the value - of a property in the document. Let's say you're building a schema for - storing comments. A user may comment on either a blog post or a product. - - ```javascript - const commentSchema = new Schema({ - body: { type: String, required: true }, - on: { - type: Schema.Types.ObjectId, - required: true, - // Instead of a hardcoded model name in `ref`, `refPath` means Mongoose - // will look at the `onModel` property to find the right model. - refPath: 'onModel' - }, - onModel: { - type: String, - required: true, - enum: ['BlogPost', 'Product'] - } - }); - - const Product = mongoose.model('Product', new Schema({ name: String })); - const BlogPost = mongoose.model('BlogPost', new Schema({ title: String })); - const Comment = mongoose.model('Comment', commentSchema); - ``` - - The `refPath` option is a more sophisticated alternative to `ref`. If `ref` - is just a string, Mongoose will always query the same model to find the - populated subdocs. With `refPath`, you can configure what model Mongoose - uses for each document. - - ```javascript - const book = await Product.create({ name: 'The Count of Monte Cristo' }); - const post = await BlogPost.create({ title: 'Top 10 French Novels' }); - - const commentOnBook = await Comment.create({ - body: 'Great read', - on: book._id, - onModel: 'Product' - }); - - const commentOnPost = await Comment.create({ - body: 'Very informative', - on: post._id, - onModel: 'BlogPost' - }); - - // The below `populate()` works even though one comment references the - // 'Product' collection and the other references the 'BlogPost' collection. - const comments = await Comment.find().populate('on').sort({ body: 1 }); - comments[0].on.name; // "The Count of Monte Cristo" - comments[1].on.title; // "Top 10 French Novels" - ``` - - An alternative approach is to define separate `blogPost` and - `product` properties on `commentSchema`, and then `populate()` on both - properties. - - ```javascript - const commentSchema = new Schema({ - body: { type: String, required: true }, - product: { - type: Schema.Types.ObjectId, - required: true, - ref: 'Product' - }, - blogPost: { - type: Schema.Types.ObjectId, - required: true, - ref: 'BlogPost' - } - }); - - // ... - - // The below `populate()` is equivalent to the `refPath` approach, you - // just need to make sure you `populate()` both `product` and `blogPost`. - const comments = await Comment.find(). - populate('product'). - populate('blogPost'). - sort({ body: 1 }); - comments[0].product.name; // "The Count of Monte Cristo" - comments[1].blogPost.title; // "Top 10 French Novels" - ``` - - Defining separate `blogPost` and `product` properties works for this simple - example. But, if you decide to allow users to also comment on articles or - other comments, you'll need to add more properties to your schema. You'll - also need an extra `populate()` call for every property, unless you use - [mongoose-autopopulate](https://www.npmjs.com/package/mongoose-autopopulate). - Using `refPath` means you only need 2 schema paths and one `populate()` call - regardless of how many models your `commentSchema` can point to. - -

    Populate Virtuals

    - - So far you've only populated based on the `_id` field. However, that's - sometimes not the right choice. - In particular, [arrays that grow without bound are a MongoDB anti-pattern](https://docs.mongodb.com/manual/tutorial/model-referenced-one-to-many-relationships-between-documents/). - Using mongoose virtuals, you can define more sophisticated relationships - between documents. - - ```javascript - const PersonSchema = new Schema({ - name: String, - band: String - }); - - const BandSchema = new Schema({ - name: String - }); - BandSchema.virtual('members', { - ref: 'Person', // The model to use - localField: 'name', // Find people where `localField` - foreignField: 'band', // is equal to `foreignField` - // If `justOne` is true, 'members' will be a single doc as opposed to - // an array. `justOne` is false by default. - justOne: false, - options: { sort: { name: -1 }, limit: 5 } // Query options, see http://bit.ly/mongoose-query-options - }); - - const Person = mongoose.model('Person', PersonSchema); - const Band = mongoose.model('Band', BandSchema); - - /** - * Suppose you have 2 bands: "Guns N' Roses" and "Motley Crue" - * And 4 people: "Axl Rose" and "Slash" with "Guns N' Roses", and - * "Vince Neil" and "Nikki Sixx" with "Motley Crue" - */ - Band.find({}).populate('members').exec(function(error, bands) { - /* `bands.members` is now an array of instances of `Person` */ - }); - ``` - - You can also use [the populate `match` option](http://thecodebarbarian.com/mongoose-5-5-static-hooks-and-populate-match-functions.html#populate-match-function) - to add an additional filter to the `populate()` query. This is useful - if you need to split up `populate()` data: - - ```javascript - const PersonSchema = new Schema({ - name: String, - band: String, - isActive: Boolean - }); - - const BandSchema = new Schema({ - name: String - }); - BandSchema.virtual('activeMembers', { - ref: 'Person', - localField: 'name', - foreignField: 'band', - justOne: false, - match: { isActive: true } - }); - BandSchema.virtual('formerMembers', { - ref: 'Person', - localField: 'name', - foreignField: 'band', - justOne: false, - match: { isActive: false } - }); - ``` - - Keep in mind that virtuals are _not_ included in `toJSON()` output by default. If you want populate virtuals to show up when using functions - that rely on `JSON.stringify()`, like Express' - [`res.json()` function](http://expressjs.com/en/4x/api.html#res.json), - set the `virtuals: true` option on your schema's `toJSON` options. - - ```javascript - // Set `virtuals: true` so `res.json()` works - const BandSchema = new Schema({ - name: String - }, { toJSON: { virtuals: true } }); - ``` - - If you're using populate projections, make sure `foreignField` is included - in the projection. - - ```javascript - Band. - find({}). - populate({ path: 'members', select: 'name' }). - exec(function(error, bands) { - // Won't work, foreign field `band` is not selected in the projection - }); - - Band. - find({}). - populate({ path: 'members', select: 'name band' }). - exec(function(error, bands) { - // Works, foreign field `band` is selected - }); - ``` - -

    Populate Virtuals: The Count Option

    - - Populate virtuals also support counting the number of documents with - matching `foreignField` as opposed to the documents themselves. Set the - `count` option on your virtual: - - ```javascript - const PersonSchema = new Schema({ - name: String, - band: String - }); - - const BandSchema = new Schema({ - name: String - }); - BandSchema.virtual('numMembers', { - ref: 'Person', // The model to use - localField: 'name', // Find people where `localField` - foreignField: 'band', // is equal to `foreignField` - count: true // And only get the number of docs - }); - - // Later - const doc = await Band.findOne({ name: 'Motley Crue' }). - populate('numMembers'); - doc.numMembers; // 2 - ``` - -

    Populate in Middleware

    - - You can populate in either pre or post [hooks](http://mongoosejs.com/docs/middleware.html). If you want to - always populate a certain field, check out the [mongoose-autopopulate plugin](http://npmjs.com/package/mongoose-autopopulate). - - ```javascript - // Always attach `populate()` to `find()` calls - MySchema.pre('find', function() { - this.populate('user'); - }); - ``` - - ```javascript - // Always `populate()` after `find()` calls. Useful if you want to selectively populate - // based on the docs found. - MySchema.post('find', async function(docs) { - for (let doc of docs) { - if (doc.isPublic) { - await doc.populate('user').execPopulate(); - } - } - }); - ``` - - ```javascript - // `populate()` after saving. Useful for sending populated data back to the client in an - // update API endpoint - MySchema.post('save', function(doc, next) { - doc.populate('user').execPopulate().then(function() { - next(); - }); - }); - ``` - - ### Next Up - - Now that we've covered `populate()`, let's take a look at [discriminators](/docs/discriminators.html). diff --git a/docs/prior.html b/docs/prior.html deleted file mode 100644 index cf7f3f49101..00000000000 --- a/docs/prior.html +++ /dev/null @@ -1,53 +0,0 @@ -Mongoose v5.6.0:
    \ No newline at end of file diff --git a/docs/production.html b/docs/production.html deleted file mode 100644 index 35b9eb10481..00000000000 --- a/docs/production.html +++ /dev/null @@ -1,9 +0,0 @@ -Mongoose Fork me on GitHub

    You are being redirected to http://mongoosejs.tumblr.com.
    Please click here if you are not redirected in 5 seconds.

    \ No newline at end of file diff --git a/docs/production.pug b/docs/production.pug deleted file mode 100644 index 2dd82917635..00000000000 --- a/docs/production.pug +++ /dev/null @@ -1,13 +0,0 @@ -extends redirect - -block meta - meta(http-equiv="refresh", content="5;url=http://mongoosejs.tumblr.com/") - -block content - p - | You are being redirected to http://mongoosejs.tumblr.com. - br - | Please - a(href="http://mongoosejs.tumblr.com/") click here - | if you are not redirected in 5 seconds. - diff --git a/docs/promises.html b/docs/promises.html deleted file mode 100644 index 02356e12498..00000000000 --- a/docs/promises.html +++ /dev/null @@ -1,120 +0,0 @@ -Mongoose v5.6.0: Promises

    Built-in Promises

    Mongoose async operations, like .save() and queries, return thenables. -This means that you can do things like MyModel.findOne({}).then() and -await MyModel.findOne({}).exec() if you're using -async/await.

    -

    You can find the return type of specific operations in the api docs

    - -
    var gnr = new Band({
    -  name: "Guns N' Roses",
    -  members: ['Axl', 'Slash']
    -});
    -
    -var promise = gnr.save();
    -assert.ok(promise instanceof Promise);
    -
    -promise.then(function (doc) {
    -  assert.equal(doc.name, "Guns N' Roses");
    -});
    -

    Queries are not promises

    Mongoose queries are not promises. They have a .then() -function for co and async/await as -a convenience. If you need -a fully-fledged promise, use the .exec() function.

    - -
    var query = Band.findOne({name: "Guns N' Roses"});
    -assert.ok(!(query instanceof Promise));
    -
    -// A query is not a fully-fledged promise, but it does have a `.then()`.
    -query.then(function (doc) {
    -  // use doc
    -});
    -
    -// `.exec()` gives you a fully-fledged promise
    -var promise = query.exec();
    -assert.ok(promise instanceof Promise);
    -
    -promise.then(function (doc) {
    -  // use doc
    -});
    -

    Plugging in your own Promises Library

    If you're an advanced user, you may want to plug in your own promise -library like bluebird. Just set -mongoose.Promise to your favorite -ES6-style promise constructor and mongoose will use it.

    - -
    var query = Band.findOne({name: "Guns N' Roses"});
    -
    -// Use bluebird
    -mongoose.Promise = require('bluebird');
    -assert.equal(query.exec().constructor, require('bluebird'));
    -
    -// Use q. Note that you **must** use `require('q').Promise`.
    -mongoose.Promise = require('q').Promise;
    -assert.ok(query.exec() instanceof require('q').makePromise);
    -
    -
    -
    - - Want to learn how to check whether your favorite npm modules work with - async/await without cobbling together contradictory answers from Google - and Stack Overflow? Chapter 4 of Mastering Async/Await explains the - basic principles for determining whether frameworks like React and - Mongoose support async/await. - Get your copy! - -

    - - - -
    -
    \ No newline at end of file diff --git a/docs/promises.md b/docs/promises.md new file mode 100644 index 00000000000..562626890a3 --- /dev/null +++ b/docs/promises.md @@ -0,0 +1,75 @@ +## Promises + +### Built-in Promises + +Mongoose async operations, like `.save()` and queries, return thenables. +This means that you can do things like `MyModel.findOne({}).then()` and +`await MyModel.findOne({}).exec()` if you're using +[async/await](http://thecodebarbarian.com/80-20-guide-to-async-await-in-node.js.html). + +You can find the return type of specific operations [in the api docs](https://mongoosejs.com/docs/api.html) +You can also read more about [promises in Mongoose](https://masteringjs.io/tutorials/mongoose/promise). + +```javascript +[require:Built-in Promises] +``` + +### Queries are not promises + +[Mongoose queries](http://mongoosejs.com/docs/queries.html) are **not** promises. They have a `.then()` +function for [co](https://www.npmjs.com/package/co) and async/await as +a convenience. If you need +a fully-fledged promise, use the `.exec()` function. + +```javascript +[require:Queries are not promises] +``` + +### Queries are thenable + +Although queries are not promises, queries are [thenables](https://promisesaplus.com/#terminology). +That means they have a `.then()` function, so you can use queries as promises with either +promise chaining or [async await](https://asyncawait.net) + +```javascript +[require:Queries are thenable] +``` + +### Should You Use `exec()` With `await`? + +There are two alternatives for using `await` with queries: + +- `await Band.findOne();` +- `await Band.findOne().exec();` + +As far as functionality is concerned, these two are equivalent. +However, we recommend using `.exec()` because that gives you +better stack traces. + +```javascript +[require:Should You Use `exec\(\)` With `await`] +``` + +### Plugging in your own Promises Library + +If you're an advanced user, you may want to plug in your own promise +library like [bluebird](https://www.npmjs.com/package/bluebird). Just set +`mongoose.Promise` to your favorite +ES6-style promise constructor and mongoose will use it. + +```javascript +[require:Plugging in your own Promises Library] +``` + + + Want to learn how to check whether your favorite npm modules work with + async/await without cobbling together contradictory answers from Google + and Stack Overflow? Chapter 4 of Mastering Async/Await explains the + basic principles for determining whether frameworks like React and + Mongoose support async/await. + Get your copy! + +

    + + + \ No newline at end of file diff --git a/docs/queries.html b/docs/queries.html deleted file mode 100644 index 9af1da99d72..00000000000 --- a/docs/queries.html +++ /dev/null @@ -1,195 +0,0 @@ -Mongoose v5.6.0: Queries

    Queries

    - - - - -

    Mongoose models provide several static helper functions -for CRUD operations. -Each of these functions returns a -mongoose Query object.

    - -

    A mongoose query can be executed in one of two ways. First, if you -pass in a callback function, Mongoose will execute the query asynchronously -and pass the results to the callback.

    -

    A query also has a .then() function, and thus can be used as a promise.

    - - -

    Executing

    -

    When executing a query with a callback function, you specify your query as a JSON document. The JSON document's syntax is the same as the MongoDB shell.

    -
    var Person = mongoose.model('Person', yourSchema);
    -
    -// find each person with a last name matching 'Ghost', selecting the `name` and `occupation` fields
    -Person.findOne({ 'name.last': 'Ghost' }, 'name occupation', function (err, person) {
    -  if (err) return handleError(err);
    -  // Prints "Space Ghost is a talk show host".
    -  console.log('%s %s is a %s.', person.name.first, person.name.last,
    -    person.occupation);
    -});
    -

    Mongoose executed the query and passed the results passed to callback. All callbacks in Mongoose use the pattern: -callback(error, result). If an error occurs executing the query, the error parameter will contain an error document, and result -will be null. If the query is successful, the error parameter will be null, and the result will be populated with the results of the query.

    -

    Anywhere a callback is passed to a query in Mongoose, the callback follows the pattern callback(error, results). What results is depends on the operation: For findOne() it is a potentially-null single document, find() a list of documents, count() the number of documents, update() the number of documents affected, etc. The API docs for Models provide more detail on what is passed to the callbacks.

    -

    Now let's look at what happens when no callback is passed:

    -
    // find each person with a last name matching 'Ghost'
    -var query = Person.findOne({ 'name.last': 'Ghost' });
    -
    -// selecting the `name` and `occupation` fields
    -query.select('name occupation');
    -
    -// execute the query at a later time
    -query.exec(function (err, person) {
    -  if (err) return handleError(err);
    -  // Prints "Space Ghost is a talk show host."
    -  console.log('%s %s is a %s.', person.name.first, person.name.last,
    -    person.occupation);
    -});
    -

    In the above code, the query variable is of type Query. -A Query enables you to build up a query using chaining syntax, rather than specifying a JSON object. -The below 2 examples are equivalent.

    -
    // With a JSON doc
    -Person.
    -  find({
    -    occupation: /host/,
    -    'name.last': 'Ghost',
    -    age: { $gt: 17, $lt: 66 },
    -    likes: { $in: ['vaporizing', 'talking'] }
    -  }).
    -  limit(10).
    -  sort({ occupation: -1 }).
    -  select({ name: 1, occupation: 1 }).
    -  exec(callback);
    -
    -// Using query builder
    -Person.
    -  find({ occupation: /host/ }).
    -  where('name.last').equals('Ghost').
    -  where('age').gt(17).lt(66).
    -  where('likes').in(['vaporizing', 'talking']).
    -  limit(10).
    -  sort('-occupation').
    -  select('name occupation').
    -  exec(callback);
    -

    A full list of Query helper functions can be found in the API docs.

    -

    - - Queries are Not Promises - -

    - -

    Mongoose queries are not promises. They have a .then() -function for co and -async/await -as a convenience. However, unlike promises, calling a query's .then() -can execute the query multiple times.

    -

    For example, the below code will execute 3 updateMany() calls, one -because of the callback, and two because .then() is called twice.

    -
    const q = MyModel.updateMany({}, { isDeleted: true }, function() {
    -  console.log('Update 1');
    -});
    -
    -q.then(() => console.log('Update 2'));
    -q.then(() => console.log('Update 3'));
    -

    Don't mix using callbacks and promises with queries, or you may end up -with duplicate operations.

    -

    References to other documents

    - -

    There are no joins in MongoDB but sometimes we still want references to -documents in other collections. This is where population -comes in. Read more about how to include documents from other collections in -your query results here.

    -

    Streaming

    - -

    You can stream query results from -MongoDB. You need to call the -Query#cursor() function to return an instance of -QueryCursor.

    -
    var cursor = Person.find({ occupation: /host/ }).cursor();
    -cursor.on('data', function(doc) {
    -  // Called once for every document
    -});
    -cursor.on('close', function() {
    -  // Called when done
    -});
    -

    Next Up

    - -

    Now that we've covered Queries, let's take a look at Validation.

    -
    \ No newline at end of file diff --git a/docs/queries.md b/docs/queries.md new file mode 100644 index 00000000000..501fe37068f --- /dev/null +++ b/docs/queries.md @@ -0,0 +1,245 @@ +## Queries + +Mongoose [models](./models.html) provide several static helper functions +for [CRUD operations](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete). +Each of these functions returns a +[mongoose `Query` object](http://mongoosejs.com/docs/api.html#Query). + +- [`Model.deleteMany()`](/docs/api.html#model_Model.deleteMany) +- [`Model.deleteOne()`](/docs/api.html#model_Model.deleteOne) +- [`Model.find()`](/docs/api.html#model_Model.find) +- [`Model.findById()`](/docs/api.html#model_Model.findById) +- [`Model.findByIdAndDelete()`](/docs/api.html#model_Model.findByIdAndDelete) +- [`Model.findByIdAndRemove()`](/docs/api.html#model_Model.findByIdAndRemove) +- [`Model.findByIdAndUpdate()`](/docs/api.html#model_Model.findByIdAndUpdate) +- [`Model.findOne()`](/docs/api.html#model_Model.findOne) +- [`Model.findOneAndDelete()`](/docs/api.html#model_Model.findOneAndDelete) +- [`Model.findOneAndRemove()`](/docs/api.html#model_Model.findOneAndRemove) +- [`Model.findOneAndReplace()`](/docs/api.html#model_Model.findOneAndReplace) +- [`Model.findOneAndUpdate()`](/docs/api.html#model_Model.findOneAndUpdate) +- [`Model.replaceOne()`](/docs/api.html#model_Model.replaceOne) +- [`Model.updateMany()`](/docs/api.html#model_Model.updateMany) +- [`Model.updateOne()`](/docs/api.html#model_Model.updateOne) + +A mongoose query can be executed in one of two ways. First, if you +pass in a `callback` function, Mongoose will execute the query asynchronously +and pass the results to the `callback`. + +A query also has a `.then()` function, and thus can be used as a promise. + + + +### Executing + +When executing a query with a `callback` function, you specify your query as a JSON document. The JSON document's syntax is the same as the [MongoDB shell](http://docs.mongodb.org/manual/tutorial/query-documents/). + +```javascript +const Person = mongoose.model('Person', yourSchema); + +// find each person with a last name matching 'Ghost', selecting the `name` and `occupation` fields +Person.findOne({ 'name.last': 'Ghost' }, 'name occupation', function (err, person) { + if (err) return handleError(err); + // Prints "Space Ghost is a talk show host". + console.log('%s %s is a %s.', person.name.first, person.name.last, + person.occupation); +}); +``` + +Mongoose executed the query and passed the results to `callback`. All callbacks in Mongoose use the pattern: +`callback(error, result)`. If an error occurs executing the query, the `error` parameter will contain an error document, and `result` +will be null. If the query is successful, the `error` parameter will be null, and the `result` will be populated with the results of the query. + +Anywhere a callback is passed to a query in Mongoose, the callback follows the pattern `callback(error, results)`. What `results` is depends on the operation: For `findOne()` it is a [potentially-null single document](./api.html#model_Model.findOne), `find()` a [list of documents](./api.html#model_Model.find), `count()` [the number of documents](./api.html#model_Model.count), `update()` the [number of documents affected](./api.html#model_Model.update), etc. The [API docs for Models](./api.html#model-js) provide more detail on what is passed to the callbacks. + +Now let's look at what happens when no `callback` is passed: + +```javascript +// find each person with a last name matching 'Ghost' +const query = Person.findOne({ 'name.last': 'Ghost' }); + +// selecting the `name` and `occupation` fields +query.select('name occupation'); + +// execute the query at a later time +query.exec(function (err, person) { + if (err) return handleError(err); + // Prints "Space Ghost is a talk show host." + console.log('%s %s is a %s.', person.name.first, person.name.last, + person.occupation); +}); +``` + +In the above code, the `query` variable is of type [Query](./api.html#query-js). +A `Query` enables you to build up a query using chaining syntax, rather than specifying a JSON object. +The below 2 examples are equivalent. + +```javascript +// With a JSON doc +Person. + find({ + occupation: /host/, + 'name.last': 'Ghost', + age: { $gt: 17, $lt: 66 }, + likes: { $in: ['vaporizing', 'talking'] } + }). + limit(10). + sort({ occupation: -1 }). + select({ name: 1, occupation: 1 }). + exec(callback); + +// Using query builder +Person. + find({ occupation: /host/ }). + where('name.last').equals('Ghost'). + where('age').gt(17).lt(66). + where('likes').in(['vaporizing', 'talking']). + limit(10). + sort('-occupation'). + select('name occupation'). + exec(callback); +``` + +A full list of [Query helper functions can be found in the API docs](./api.html#query-js). + +

    + + Queries are Not Promises + +

    + +Mongoose queries are **not** promises. They have a `.then()` +function for [co](https://www.npmjs.com/package/co) and +[async/await](http://thecodebarbarian.com/common-async-await-design-patterns-in-node.js.html) +as a convenience. However, unlike promises, calling a query's `.then()` +can execute the query multiple times. + +For example, the below code will execute 3 `updateMany()` calls, one +because of the callback, and two because `.then()` is called twice. + +```javascript +const q = MyModel.updateMany({}, { isDeleted: true }, function() { + console.log('Update 1'); +}); + +q.then(() => console.log('Update 2')); +q.then(() => console.log('Update 3')); +``` + +Don't mix using callbacks and promises with queries, or you may end up +with duplicate operations. That's because passing a callback to a query function +immediately executes the query, and calling [`then()`](https://masteringjs.io/tutorials/fundamentals/then) +executes the query again. + +Mixing promises and callbacks can lead to duplicate entries in arrays. +For example, the below code inserts 2 entries into the `tags` array, **not* just 1. + +```javascript +const BlogPost = mongoose.model('BlogPost', new Schema({ + title: String, + tags: [String] +})); + +// Because there's both `await` **and** a callback, this `updateOne()` executes twice +// and thus pushes the same string into `tags` twice. +const update = { $push: { tags: ['javascript'] } }; +await BlogPost.updateOne({ title: 'Introduction to Promises' }, update, (err, res) => { + console.log(res); +}); +``` + +

    References to other documents

    + +There are no joins in MongoDB but sometimes we still want references to +documents in other collections. This is where [population](./populate.html) +comes in. Read more about how to include documents from other collections in +your query results [here](./api.html#query_Query-populate). + +

    Streaming

    + +You can [stream](http://nodejs.org/api/stream.html) query results from +MongoDB. You need to call the +[Query#cursor()](./api.html#query_Query-cursor) function to return an instance of +[QueryCursor](./api.html#query_Query-cursor). + +```javascript +const cursor = Person.find({ occupation: /host/ }).cursor(); + +for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) { + console.log(doc); // Prints documents one at a time +} +``` + +Iterating through a Mongoose query using [async iterators](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js.html) +also creates a cursor. + +```javascript +for await (const doc of Person.find()) { + console.log(doc); // Prints documents one at a time +} +``` + +Cursors are subject to [cursor timeouts](https://stackoverflow.com/questions/21853178/when-a-mongodb-cursor-will-expire). +By default, MongoDB will close your cursor after 10 minutes and subsequent +`next()` calls will result in a `MongoError: cursor id 123 not found` error. +To override this, set the `noCursorTimeout` option on your cursor. + +```javascript +// MongoDB won't automatically close this cursor after 10 minutes. +const cursor = Person.find().cursor().addCursorFlag('noCursorTimeout', true); +``` + +However, cursors can still time out because of [session idle timeouts](https://docs.mongodb.com/manual/reference/method/cursor.noCursorTimeout/#session-idle-timeout-overrides-nocursortimeout). +So even a cursor with `noCursorTimeout` set will still time out after 30 minutes +of inactivity. You can read more about working around session idle timeouts in the [MongoDB documentation](https://docs.mongodb.com/manual/reference/method/cursor.noCursorTimeout/#session-idle-timeout-overrides-nocursortimeout). + +

    Versus Aggregation

    + +[Aggregation](https://mongoosejs.com/docs/api.html#aggregate_Aggregate) can +do many of the same things that queries can. For example, below is +how you can use `aggregate()` to find docs where `name.last = 'Ghost'`: + +```javascript +const docs = await Person.aggregate([{ $match: { 'name.last': 'Ghost' } }]); +``` + +However, just because you can use `aggregate()` doesn't mean you should. +In general, you should use queries where possible, and only use `aggregate()` +when you absolutely need to. + +Unlike query results, Mongoose does **not** [`hydrate()`](/docs/api/model.html#model_Model.hydrate) +aggregation results. Aggregation results are always POJOs, not Mongoose +documents. + +```javascript +const docs = await Person.aggregate([{ $match: { 'name.last': 'Ghost' } }]); + +docs[0] instanceof mongoose.Document; // false +``` + +Also, unlike query filters, Mongoose also doesn't +[cast](/docs/tutorials/query_casting.html) aggregation pipelines. That means +you're responsible for ensuring the values you pass in to an aggregation +pipeline have the correct type. + +```javascript +const doc = await Person.findOne(); + +const idString = doc._id.toString(); + +// Finds the `Person`, because Mongoose casts `idString` to an ObjectId +const queryRes = await Person.findOne({ _id: idString }); + +// Does **not** find the `Person`, because Mongoose doesn't cast aggregation +// pipelines. +const aggRes = await Person.aggregate([{ $match: { _id: idString } }]) +``` + +

    Next Up

    + +Now that we've covered `Queries`, let's take a look at [Validation](/docs/validation.html). diff --git a/docs/queries.pug b/docs/queries.pug deleted file mode 100644 index 82cf7600faf..00000000000 --- a/docs/queries.pug +++ /dev/null @@ -1,267 +0,0 @@ -extends layout - -append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown - ## Queries - - - - - - Mongoose [models](./models.html) provide several static helper functions - for [CRUD operations](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete). - Each of these functions returns a - [mongoose `Query` object](http://mongoosejs.com/docs/api.html#Query). - - - [`Model.deleteMany()`](/docs/api.html#model_Model.deleteMany) - - [`Model.deleteOne()`](/docs/api.html#model_Model.deleteOne) - - [`Model.find()`](/docs/api.html#model_Model.find) - - [`Model.findById()`](/docs/api.html#model_Model.findById) - - [`Model.findByIdAndDelete()`](/docs/api.html#model_Model.findByIdAndDelete) - - [`Model.findByIdAndRemove()`](/docs/api.html#model_Model.findByIdAndRemove) - - [`Model.findByIdAndUpdate()`](/docs/api.html#model_Model.findByIdAndUpdate) - - [`Model.findOne()`](/docs/api.html#model_Model.findOne) - - [`Model.findOneAndDelete()`](/docs/api.html#model_Model.findOneAndDelete) - - [`Model.findOneAndRemove()`](/docs/api.html#model_Model.findOneAndRemove) - - [`Model.findOneAndReplace()`](/docs/api.html#model_Model.findOneAndReplace) - - [`Model.findOneAndUpdate()`](/docs/api.html#model_Model.findOneAndUpdate) - - [`Model.replaceOne()`](/docs/api.html#model_Model.replaceOne) - - [`Model.updateMany()`](/docs/api.html#model_Model.updateMany) - - [`Model.updateOne()`](/docs/api.html#model_Model.updateOne) - - A mongoose query can be executed in one of two ways. First, if you - pass in a `callback` function, Mongoose will execute the query asynchronously - and pass the results to the `callback`. - - A query also has a `.then()` function, and thus can be used as a promise. - - - - ### Executing - - When executing a query with a `callback` function, you specify your query as a JSON document. The JSON document's syntax is the same as the [MongoDB shell](http://docs.mongodb.org/manual/tutorial/query-documents/). - - ```javascript - const Person = mongoose.model('Person', yourSchema); - - // find each person with a last name matching 'Ghost', selecting the `name` and `occupation` fields - Person.findOne({ 'name.last': 'Ghost' }, 'name occupation', function (err, person) { - if (err) return handleError(err); - // Prints "Space Ghost is a talk show host". - console.log('%s %s is a %s.', person.name.first, person.name.last, - person.occupation); - }); - ``` - - Mongoose executed the query and passed the results to `callback`. All callbacks in Mongoose use the pattern: - `callback(error, result)`. If an error occurs executing the query, the `error` parameter will contain an error document, and `result` - will be null. If the query is successful, the `error` parameter will be null, and the `result` will be populated with the results of the query. - - Anywhere a callback is passed to a query in Mongoose, the callback follows the pattern `callback(error, results)`. What `results` is depends on the operation: For `findOne()` it is a [potentially-null single document](./api.html#model_Model.findOne), `find()` a [list of documents](./api.html#model_Model.find), `count()` [the number of documents](./api.html#model_Model.count), `update()` the [number of documents affected](./api.html#model_Model.update), etc. The [API docs for Models](./api.html#model-js) provide more detail on what is passed to the callbacks. - - Now let's look at what happens when no `callback` is passed: - - ```javascript - // find each person with a last name matching 'Ghost' - const query = Person.findOne({ 'name.last': 'Ghost' }); - - // selecting the `name` and `occupation` fields - query.select('name occupation'); - - // execute the query at a later time - query.exec(function (err, person) { - if (err) return handleError(err); - // Prints "Space Ghost is a talk show host." - console.log('%s %s is a %s.', person.name.first, person.name.last, - person.occupation); - }); - ``` - - In the above code, the `query` variable is of type [Query](./api.html#query-js). - A `Query` enables you to build up a query using chaining syntax, rather than specifying a JSON object. - The below 2 examples are equivalent. - - ```javascript - // With a JSON doc - Person. - find({ - occupation: /host/, - 'name.last': 'Ghost', - age: { $gt: 17, $lt: 66 }, - likes: { $in: ['vaporizing', 'talking'] } - }). - limit(10). - sort({ occupation: -1 }). - select({ name: 1, occupation: 1 }). - exec(callback); - - // Using query builder - Person. - find({ occupation: /host/ }). - where('name.last').equals('Ghost'). - where('age').gt(17).lt(66). - where('likes').in(['vaporizing', 'talking']). - limit(10). - sort('-occupation'). - select('name occupation'). - exec(callback); - ``` - - A full list of [Query helper functions can be found in the API docs](./api.html#query-js). - -

    - - Queries are Not Promises - -

    - - Mongoose queries are **not** promises. They have a `.then()` - function for [co](https://www.npmjs.com/package/co) and - [async/await](http://thecodebarbarian.com/common-async-await-design-patterns-in-node.js.html) - as a convenience. However, unlike promises, calling a query's `.then()` - can execute the query multiple times. - - For example, the below code will execute 3 `updateMany()` calls, one - because of the callback, and two because `.then()` is called twice. - - ```javascript - const q = MyModel.updateMany({}, { isDeleted: true }, function() { - console.log('Update 1'); - }); - - q.then(() => console.log('Update 2')); - q.then(() => console.log('Update 3')); - ``` - - Don't mix using callbacks and promises with queries, or you may end up - with duplicate operations. That's because passing a callback to a query function - immediately executes the query, and calling [`then()`](https://masteringjs.io/tutorials/fundamentals/then) - executes the query again. - - Mixing promises and callbacks can lead to duplicate entries in arrays. - For example, the below code inserts 2 entries into the `tags` array, **not* just 1. - - ```javascript - const BlogPost = mongoose.model('BlogPost', new Schema({ - title: String, - tags: [String] - })); - - // Because there's both `await` **and** a callback, this `updateOne()` executes twice - // and thus pushes the same string into `tags` twice. - const update = { $push: { tags: ['javascript'] } }; - await BlogPost.updateOne({ title: 'Introduction to Promises' }, update, (err, res) => { - console.log(res); - }); - ``` - -

    References to other documents

    - - There are no joins in MongoDB but sometimes we still want references to - documents in other collections. This is where [population](./populate.html) - comes in. Read more about how to include documents from other collections in - your query results [here](./api.html#query_Query-populate). - -

    Streaming

    - - You can [stream](http://nodejs.org/api/stream.html) query results from - MongoDB. You need to call the - [Query#cursor()](./api.html#query_Query-cursor) function to return an instance of - [QueryCursor](./api.html#query_Query-cursor). - - ```javascript - const cursor = Person.find({ occupation: /host/ }).cursor(); - - for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) { - console.log(doc); // Prints documents one at a time - } - ``` - - Iterating through a Mongoose query using [async iterators](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js.html) - also creates a cursor. - - ```javascript - for await (const doc of Person.find()) { - console.log(doc); // Prints documents one at a time - } - ``` - - Cursors are subject to [cursor timeouts](https://stackoverflow.com/questions/21853178/when-a-mongodb-cursor-will-expire). - By default, MongoDB will close your cursor after 10 minutes and subsequent - `next()` calls will result in a `MongoError: cursor id 123 not found` error. - To override this, set the `noCursorTimeout` option on your cursor. - - ```javascript - // MongoDB won't automatically close this cursor after 10 minutes. - const cursor = Person.find().cursor().addCursorFlag('noCursorTimeout', true); - ``` - - However, cursors can still time out because of [session idle timeouts](https://docs.mongodb.com/manual/reference/method/cursor.noCursorTimeout/#session-idle-timeout-overrides-nocursortimeout). - So even a cursor with `noCursorTimeout` set will still time out after 30 minutes - of inactivity. You can read more about working around session idle timeouts in the [MongoDB documentation](https://docs.mongodb.com/manual/reference/method/cursor.noCursorTimeout/#session-idle-timeout-overrides-nocursortimeout). - -

    Versus Aggregation

    - - [Aggregation](https://mongoosejs.com/docs/api.html#aggregate_Aggregate) can - do many of the same things that queries can. For example, below is - how you can use `aggregate()` to find docs where `name.last = 'Ghost'`: - - ```javascript - const docs = await Person.aggregate([{ $match: { 'name.last': 'Ghost' } }]); - ``` - - However, just because you can use `aggregate()` doesn't mean you should. - In general, you should use queries where possible, and only use `aggregate()` - when you absolutely need to. - - Unlike query results, Mongoose does **not** [`hydrate()`](/docs/api/model.html#model_Model.hydrate) - aggregation results. Aggregation results are always POJOs, not Mongoose - documents. - - ```javascript - const docs = await Person.aggregate([{ $match: { 'name.last': 'Ghost' } }]); - - docs[0] instanceof mongoose.Document; // false - ``` - - Also, unlike query filters, Mongoose also doesn't - [cast](/docs/tutorials/query_casting.html) aggregation pipelines. That means - you're responsible for ensuring the values you pass in to an aggregation - pipeline have the correct type. - - ```javascript - const doc = await Person.findOne(); - - const idString = doc._id.toString(); - - // Finds the `Person`, because Mongoose casts `idString` to an ObjectId - const queryRes = await Person.findOne({ _id: idString }); - - // Does **not** find the `Person`, because Mongoose doesn't cast aggregation - // pipelines. - const aggRes = await Person.aggregate([{ $match: { _id: idString } }]) - ``` - -

    Next Up

    - - Now that we've covered `Queries`, let's take a look at [Validation](/docs/validation.html). diff --git a/docs/schematypes.html b/docs/schematypes.html deleted file mode 100644 index 5d1c01233a0..00000000000 --- a/docs/schematypes.html +++ /dev/null @@ -1,539 +0,0 @@ -Mongoose v5.6.0: SchemaTypes

    SchemaTypes

    - - - - - -

    SchemaTypes handle definition of path -defaults, -validation, -getters, -setters, -field selection defaults for -queries, -and other general characteristics for Mongoose document properties.

    - - -

    What is a SchemaType?

    - -

    You can think of a Mongoose schema as the configuration object for a -Mongoose model. A SchemaType is then a configuration object for an individual -property. A SchemaType says what type a given -path should have, whether it has any getters/setters, and what values are -valid for that path.

    -
    const schema = new Schema({ name: String });
    -schema.path('name') instanceof mongoose.SchemaType; // true
    -schema.path('name') instanceof mongoose.Schema.Types.String; // true
    -schema.path('name').instance; // 'String'
    -

    A SchemaType is different from a type. In other words, mongoose.ObjectId !== mongoose.Types.ObjectId. -A SchemaType is just a configuration object for Mongoose. An instance of -the mongoose.ObjectId SchemaType doesn't actually create MongoDB ObjectIds, -it is just a configuration for a path in a schema.

    -

    The following are all the valid SchemaTypes in Mongoose. Mongoose plugins -can also add custom SchemaTypes like int32. -Check out Mongoose's plugins search to find plugins.

    - -

    Example

    - -
    var schema = new Schema({
    -  name:    String,
    -  binary:  Buffer,
    -  living:  Boolean,
    -  updated: { type: Date, default: Date.now },
    -  age:     { type: Number, min: 18, max: 65 },
    -  mixed:   Schema.Types.Mixed,
    -  _someId: Schema.Types.ObjectId,
    -  decimal: Schema.Types.Decimal128,
    -  array: [],
    -  ofString: [String],
    -  ofNumber: [Number],
    -  ofDates: [Date],
    -  ofBuffer: [Buffer],
    -  ofBoolean: [Boolean],
    -  ofMixed: [Schema.Types.Mixed],
    -  ofObjectId: [Schema.Types.ObjectId],
    -  ofArrays: [[]],
    -  ofArrayOfNumbers: [[Number]],
    -  nested: {
    -    stuff: { type: String, lowercase: true, trim: true }
    -  },
    -  map: Map,
    -  mapOfString: {
    -    type: Map,
    -    of: String
    -  }
    -})
    -
    -// example use
    -
    -var Thing = mongoose.model('Thing', schema);
    -
    -var m = new Thing;
    -m.name = 'Statue of Liberty';
    -m.age = 125;
    -m.updated = new Date;
    -m.binary = Buffer.alloc(0);
    -m.living = false;
    -m.mixed = { any: { thing: 'i want' } };
    -m.markModified('mixed');
    -m._someId = new mongoose.Types.ObjectId;
    -m.array.push(1);
    -m.ofString.push("strings!");
    -m.ofNumber.unshift(1,2,3,4);
    -m.ofDates.addToSet(new Date);
    -m.ofBuffer.pop();
    -m.ofMixed = [1, [], 'three', { four: 5 }];
    -m.nested.stuff = 'good';
    -m.map = new Map([['key', 'value']]);
    -m.save(callback);
    -

    SchemaType Options

    - -

    You can declare a schema type using the type directly, or an object with -a type property.

    -
    var schema1 = new Schema({
    -  test: String // `test` is a path of type String
    -});
    -
    -var schema2 = new Schema({
    -  // The `test` object contains the "SchemaType options"
    -  test: { type: String } // `test` is a path of type string
    -});
    -

    In addition to the type property, you can specify additional properties -for a path. For example, if you want to lowercase a string before saving:

    -
    var schema2 = new Schema({
    -  test: {
    -    type: String,
    -    lowercase: true // Always convert `test` to lowercase
    -  }
    -});
    -

    You can add any property you want to your SchemaType options. Many plugins -rely on custom SchemaType options. For example, the mongoose-autopopulate -plugin automatically populates paths if you set autopopulate: true in your -SchemaType options. Mongoose comes with support for several built-in -SchemaType options, like lowercase in the above example.

    -

    The lowercase option only works for strings. There are certain options -which apply for all schema types, and some that apply for specific schema -types.

    -
    All Schema Types
    - -
      -
    • required: boolean or function, if true adds a required validator for this property
    • -
    • default: Any or function, sets a default value for the path. If the value is a function, the return value of the function is used as the default.
    • -
    • select: boolean, specifies default projections for queries
    • -
    • validate: function, adds a validator function for this property
    • -
    • get: function, defines a custom getter for this property using Object.defineProperty().
    • -
    • set: function, defines a custom setter for this property using Object.defineProperty().
    • -
    • alias: string, mongoose >= 4.10.0 only. Defines a virtual with the given name that gets/sets this path.
    • -
    -
    var numberSchema = new Schema({
    -  integerOnly: {
    -    type: Number,
    -    get: v => Math.round(v),
    -    set: v => Math.round(v),
    -    alias: 'i'
    -  }
    -});
    -
    -var Number = mongoose.model('Number', numberSchema);
    -
    -var doc = new Number();
    -doc.integerOnly = 2.001;
    -doc.integerOnly; // 2
    -doc.i; // 2
    -doc.i = 3.001;
    -doc.integerOnly; // 3
    -doc.i; // 3
    -
    Indexes
    - -

    You can also define MongoDB indexes -using schema type options.

    -
      -
    • index: boolean, whether to define an index on this property.
    • -
    • unique: boolean, whether to define a unique index on this property.
    • -
    • sparse: boolean, whether to define a sparse index on this property.
    • -
    -
    var schema2 = new Schema({
    -  test: {
    -    type: String,
    -    index: true,
    -    unique: true // Unique index. If you specify `unique: true`
    -    // specifying `index: true` is optional if you do `unique: true`
    -  }
    -});
    -
    String
    - -
      -
    • lowercase: boolean, whether to always call .toLowerCase() on the value
    • -
    • uppercase: boolean, whether to always call .toUpperCase() on the value
    • -
    • trim: boolean, whether to always call .trim() on the value
    • -
    • match: RegExp, creates a validator that checks if the value matches the given regular expression
    • -
    • enum: Array, creates a validator that checks if the value is in the given array.
    • -
    • minlength: Number, creates a validator that checks if the value length is not less than the given number
    • -
    • maxlength: Number, creates a validator that checks if the value length is not greater than the given number
    • -
    -
    Number
    - -
      -
    • min: Number, creates a validator that checks if the value is greater than or equal to the given minimum.
    • -
    • max: Number, creates a validator that checks if the value is less than or equal to the given maximum.
    • -
    -
    Date
    - -
      -
    • min: Date
    • -
    • max: Date
    • -
    -

    Usage notes

    - -

    String

    - -

    To declare a path as a string, you may use either the String global -constructor or the string 'String'.

    -
    const schema1 = new Schema({ name: String }); // name will be cast to string
    -const schema2 = new Schema({ name: 'String' }); // Equivalent
    -
    -const Person = mongoose.model('Person', schema2);
    -

    If you pass an element that has a toString() function, Mongoose will call it, -unless the toString() function is strictly equal to Object.prototype.toString().

    -
    new Person({ name: 42 }).name; // "42" as a string
    -new Person({ name: { toString: () => 42 } }).name; // "42" as a string
    -
    -// "undefined", will get a cast error if you `save()` this document 
    -new Person({ name: { foo: 42 } }).name; 
    -

    Number

    - -

    To declare a path as a number, you may use either the Number global -constructor or the string 'Number'.

    -
    const schema1 = new Schema({ age: Number }); // age will be cast to a Number
    -const schema2 = new Schema({ age: 'Number' }); // Equivalent
    -
    -const Car = mongoose.model('Car', schema2);
    -

    There are several types of values that will be successfully cast to a Number.

    -
    new Car({ age: '15' }).age; // 15 as a Number
    -new Car({ age: true }).age; // 1 as a Number
    -new Car({ age: false }).age; // 0 as a Number
    -new Car({ age: { valueOf: () => 83 } }).age; // 83 as a Number
    -

    If you pass an object with a valueOf() function that returns a Number, Mongoose will -call it and assign the returned value to the path.

    -

    The values null and undefined are not cast.

    -

    NaN, strings that cast to NaN, arrays, and objects that don't have a valueOf() function -will all result in a CastError.

    -

    Dates

    - -

    Built-in Date methods are not hooked into the mongoose change tracking logic which in English means that if you use a Date in your document and modify it with a method like setMonth(), mongoose will be unaware of this change and doc.save() will not persist this modification. If you must modify Date types using built-in methods, tell mongoose about the change with doc.markModified('pathToYourDate') before saving.

    -
    var Assignment = mongoose.model('Assignment', { dueDate: Date });
    -Assignment.findOne(function (err, doc) {
    -  doc.dueDate.setMonth(3);
    -  doc.save(callback); // THIS DOES NOT SAVE YOUR CHANGE
    -
    -  doc.markModified('dueDate');
    -  doc.save(callback); // works
    -})
    -

    Buffer

    - -

    To declare a path as a Buffer, you may use either the Buffer global -constructor or the string 'Buffer'.

    -
    const schema1 = new Schema({ binData: Buffer }); // binData will be cast to a Buffer
    -const schema2 = new Schema({ binData: 'Buffer' }); // Equivalent
    -
    -const Data = mongoose.model('Data', schema2);
    -

    Mongoose will successfully cast the below values to buffers.

    -
    const file1 = new Data({ binData: 'test'}); // {"type":"Buffer","data":[116,101,115,116]}
    -const file2 = new Data({ binData: 72987 }); // {"type":"Buffer","data":[27]}
    -const file4 = new Data({ binData: { type: 'Buffer', data: [1, 2, 3]}}); // {"type":"Buffer","data":[1,2,3]}

    Mixed

    - -

    An "anything goes" SchemaType. Mongoose will not do any casting on mixed paths. -You can define a mixed path using Schema.Types.Mixed or by passing an empty -object literal. The following are equivalent.

    -
    const Any = new Schema({ any: {} });
    -const Any = new Schema({ any: Object });
    -const Any = new Schema({ any: Schema.Types.Mixed });
    -const Any = new Schema({ any: mongoose.Mixed });
    -// Note that if you're using `type`, putting _any_ POJO as the `type` will
    -// make the path mixed.
    -const Any = new Schema({
    -  any: {
    -    type: { foo: String }
    -  }
    -});
    -

    Since it is a schema-less type, you can change the value to anything else you -like, but Mongoose loses the ability to auto detect and save those changes. -To tell Mongoose that the value of a Mixed type has changed, you need to -call doc.markModified(path), passing the path to the Mixed type you just changed.

    -
    person.anything = { x: [3, 4, { y: "changed" }] };
    -person.markModified('anything');
    -person.save(); // Mongoose will save changes to `anything`.
    -

    ObjectIds

    - -

    To specify a type of ObjectId, use Schema.Types.ObjectId in your declaration.

    -
    var mongoose = require('mongoose');
    -var ObjectId = mongoose.Schema.Types.ObjectId;
    -var Car = new Schema({ driver: ObjectId });
    -// or just Schema.ObjectId for backwards compatibility with v2
    -

    Boolean

    - -

    Booleans in Mongoose are plain JavaScript booleans. -By default, Mongoose casts the below values to true:

    -
      -
    • true
    • -
    • 'true'
    • -
    • 1
    • -
    • '1'
    • -
    • 'yes'
    • -
    -

    Mongoose casts the below values to false:

    -
      -
    • false
    • -
    • 'false'
    • -
    • 0
    • -
    • '0'
    • -
    • 'no'
    • -
    -

    Any other value causes a CastError. -You can modify what values Mongoose converts to true or false using the -convertToTrue and convertToFalse properties, which are JavaScript sets.

    -
    const M = mongoose.model('Test', new Schema({ b: Boolean }));
    -console.log(new M({ b: 'nay' }).b); // undefined
    -
    -// Set { false, 'false', 0, '0', 'no' }
    -console.log(mongoose.Schema.Types.Boolean.convertToFalse);
    -
    -mongoose.Schema.Types.Boolean.convertToFalse.add('nay');
    -console.log(new M({ b: 'nay' }).b); // false
    -

    Arrays

    - -

    Mongoose supports arrays of SchemaTypes -and arrays of subdocuments. Arrays of SchemaTypes are -also called primitive arrays, and arrays of subdocuments are also called -document arrays.

    -
    var ToySchema = new Schema({ name: String });
    -var ToyBoxSchema = new Schema({
    -  toys: [ToySchema],
    -  buffers: [Buffer],
    -  strings: [String],
    -  numbers: [Number]
    -  // ... etc
    -});
    -

    Arrays are special because they implicitly have a default value of [] (empty array).

    -
    var ToyBox = mongoose.model('ToyBox', ToyBoxSchema);
    -console.log((new ToyBox()).toys); // []
    -

    To overwrite this default, you need to set the default value to undefined

    -
    var ToyBoxSchema = new Schema({
    -  toys: {
    -    type: [ToySchema],
    -    default: undefined
    -  }
    -});
    -

    Note: specifying an empty array is equivalent to Mixed. The following all create arrays of -Mixed:

    -
    var Empty1 = new Schema({ any: [] });
    -var Empty2 = new Schema({ any: Array });
    -var Empty3 = new Schema({ any: [Schema.Types.Mixed] });
    -var Empty4 = new Schema({ any: [{}] });
    -

    Maps

    - -

    New in Mongoose 5.1.0

    -

    A MongooseMap is a subclass of the built-in Map class. -In these docs, we'll use the terms 'map' and MongooseMap interchangeably. -In Mongoose, maps are how you create a nested document with arbitrary keys.

    -

    Note: In Mongoose Maps, keys must be strings in order to store the document in MongoDB.

    -
    const userSchema = new Schema({
    -  // `socialMediaHandles` is a map whose values are strings. A map's
    -  // keys are always strings. You specify the type of values using `of`.
    -  socialMediaHandles: {
    -    type: Map,
    -    of: String
    -  }
    -});
    -
    -const User = mongoose.model('User', userSchema);
    -// Map { 'github' => 'vkarpov15', 'twitter' => '@code_barbarian' }
    -console.log(new User({
    -  socialMediaHandles: {
    -    github: 'vkarpov15',
    -    twitter: '@code_barbarian'
    -  }
    -}).socialMediaHandles);
    -

    The above example doesn't explicitly declare github or twitter as paths, -but, since socialMediaHandles is a map, you can store arbitrary key/value -pairs. However, since socialMediaHandles is a map, you must use -.get() to get the value of a key and .set() to set the value of a key.

    -
    const user = new User({
    -  socialMediaHandles: {}
    -});
    -
    -// Good
    -user.socialMediaHandles.set('github', 'vkarpov15');
    -// Works too
    -user.set('socialMediaHandles.twitter', '@code_barbarian');
    -// Bad, the `myspace` property will **not** get saved
    -user.socialMediaHandles.myspace = 'fail';
    -
    -// 'vkarpov15'
    -console.log(user.socialMediaHandles.get('github'));
    -// '@code_barbarian'
    -console.log(user.get('socialMediaHandles.twitter'));
    -// undefined
    -user.socialMediaHandles.github;
    -
    -// Will only save the 'github' and 'twitter' properties
    -user.save();
    -

    Map types are stored as BSON objects in MongoDB. -Keys in a BSON object are ordered, so this means the insertion order -property of maps is maintained.

    -

    Getters

    - -

    Getters are like virtuals for paths defined in your schema. For example, -let's say you wanted to store user profile pictures as relative paths and -then add the hostname in your application. Below is how you would structure -your userSchema:

    -
    const root = 'https://s3.amazonaws.com/mybucket';
    -
    -const userSchema = new Schema({
    -  name: String,
    -  picture: {
    -    type: String,
    -    get: v => `${root}${v}`
    -  }
    -});
    -
    -const User = mongoose.model('User', userSchema);
    -
    -const doc = new User({ name: 'Val', picture: '/123.png' });
    -doc.picture; // 'https://s3.amazonaws.com/mybucket/123.png'
    -doc.toObject({ getters: false }).picture; // '123.png'
    -

    Generally, you only use getters on primitive paths as opposed to arrays -or subdocuments. Because getters override what accessing a Mongoose path returns, -declaring a getter on an object may remove Mongoose change tracking for -that path.

    -
    const schema = new Schema({
    -  arr: [{ url: String }]
    -});
    -
    -const root = 'https://s3.amazonaws.com/mybucket';
    -
    -// Bad, don't do this!
    -schema.path('arr').get(v => {
    -  return v.map(el => Object.assign(el, { url: root + el.url }));
    -});
    -
    -// Later
    -doc.arr.push({ key: String });
    -doc.arr[0]; // 'undefined' because every `doc.arr` creates a new array!
    -

    Instead of declaring a getter on the array as shown above, you should -declare a getter on the url string as shown below. If you need to declare -a getter on a nested document or array, be very careful!

    -
    const schema = new Schema({
    -  arr: [{ url: String }]
    -});
    -
    -const root = 'https://s3.amazonaws.com/mybucket';
    -
    -// Good, do this instead of declaring a getter on `arr`
    -schema.path('arr.0.url').get(v => `${root}${v}`);
    -

    Creating Custom Types

    - -

    Mongoose can also be extended with custom SchemaTypes. Search the -plugins -site for compatible types like -mongoose-long, -mongoose-int32, -and -other -types.

    -

    Read more about creating custom SchemaTypes here.

    -

    The `schema.path()` Function

    - -

    The schema.path() function returns the instantiated schema type for a -given path.

    -
    var sampleSchema = new Schema({ name: { type: String, required: true } });
    -console.log(sampleSchema.path('name'));
    -// Output looks like:
    -/**
    - * SchemaString {
    - *   enumValues: [],
    - *   regExp: null,
    - *   path: 'name',
    - *   instance: 'String',
    - *   validators: ...
    - */
    -

    You can use this function to inspect the schema type for a given path, -including what validators it has and what the type is.

    -

    Next Up

    - -

    Now that we've covered SchemaTypes, let's take a look at Connections.

    -
    \ No newline at end of file diff --git a/docs/schematypes.md b/docs/schematypes.md new file mode 100644 index 00000000000..bcc2cc5a9ce --- /dev/null +++ b/docs/schematypes.md @@ -0,0 +1,739 @@ +

    SchemaTypes

    + +SchemaTypes handle definition of path +[defaults](./api.html#schematype_SchemaType-default), +[validation](./api.html#schematype_SchemaType-validate), +[getters](#getters), +[setters](./api.html#schematype_SchemaType-set), +[field selection defaults](./api.html#schematype_SchemaType-select) for +[queries](./api.html#query-js), +and other general characteristics for Mongoose document properties. + + + +* [What is a SchemaType?](#what-is-a-schematype) +* [The `type` Key](#type-key) +* [SchemaType Options](#schematype-options) +* [Usage Notes](#usage-notes) +* [Getters](#getters) +* [Custom Types](#customtypes) +* [The `schema.path()` Function](#path) + +

    What is a SchemaType?

    + +You can think of a Mongoose schema as the configuration object for a +Mongoose model. A SchemaType is then a configuration object for an individual +property. A SchemaType says what type a given +path should have, whether it has any getters/setters, and what values are +valid for that path. + +```javascript +const schema = new Schema({ name: String }); +schema.path('name') instanceof mongoose.SchemaType; // true +schema.path('name') instanceof mongoose.Schema.Types.String; // true +schema.path('name').instance; // 'String' +``` + +A SchemaType is different from a type. In other words, `mongoose.ObjectId !== mongoose.Types.ObjectId`. +A SchemaType is just a configuration object for Mongoose. An instance of +the `mongoose.ObjectId` SchemaType doesn't actually create MongoDB ObjectIds, +it is just a configuration for a path in a schema. + +The following are all the valid SchemaTypes in Mongoose. Mongoose plugins +can also add custom SchemaTypes like [int32](http://plugins.mongoosejs.io/plugins/int32). +Check out [Mongoose's plugins search](http://plugins.mongoosejs.io) to find plugins. + +- [String](#strings) +- [Number](#numbers) +- [Date](#dates) +- [Buffer](#buffers) +- [Boolean](#booleans) +- [Mixed](#mixed) +- [ObjectId](#objectids) +- [Array](#arrays) +- [Decimal128](./api.html#mongoose_Mongoose-Decimal128) +- [Map](#maps) +- [Schema](#schemas) + +

    Example

    + +```javascript +const schema = new Schema({ + name: String, + binary: Buffer, + living: Boolean, + updated: { type: Date, default: Date.now }, + age: { type: Number, min: 18, max: 65 }, + mixed: Schema.Types.Mixed, + _someId: Schema.Types.ObjectId, + decimal: Schema.Types.Decimal128, + array: [], + ofString: [String], + ofNumber: [Number], + ofDates: [Date], + ofBuffer: [Buffer], + ofBoolean: [Boolean], + ofMixed: [Schema.Types.Mixed], + ofObjectId: [Schema.Types.ObjectId], + ofArrays: [[]], + ofArrayOfNumbers: [[Number]], + nested: { + stuff: { type: String, lowercase: true, trim: true } + }, + map: Map, + mapOfString: { + type: Map, + of: String + } +}) + +// example use + +const Thing = mongoose.model('Thing', schema); + +const m = new Thing; +m.name = 'Statue of Liberty'; +m.age = 125; +m.updated = new Date; +m.binary = Buffer.alloc(0); +m.living = false; +m.mixed = { any: { thing: 'i want' } }; +m.markModified('mixed'); +m._someId = new mongoose.Types.ObjectId; +m.array.push(1); +m.ofString.push("strings!"); +m.ofNumber.unshift(1,2,3,4); +m.ofDates.addToSet(new Date); +m.ofBuffer.pop(); +m.ofMixed = [1, [], 'three', { four: 5 }]; +m.nested.stuff = 'good'; +m.map = new Map([['key', 'value']]); +m.save(callback); +``` + +

    The `type` Key

    + +`type` is a special property in Mongoose schemas. When Mongoose finds +a nested property named `type` in your schema, Mongoose assumes that +it needs to define a SchemaType with the given type. + +```javascript +// 3 string SchemaTypes: 'name', 'nested.firstName', 'nested.lastName' +const schema = new Schema({ + name: { type: String }, + nested: { + firstName: { type: String }, + lastName: { type: String } + } +}); +``` + +As a consequence, [you need a little extra work to define a property named `type` in your schema](/docs/faq.html#type-key). +For example, suppose you're building a stock portfolio app, and you +want to store the asset's `type` (stock, bond, ETF, etc.). Naively, +you might define your schema as shown below: + +```javascript +const holdingSchema = new Schema({ + // You might expect `asset` to be an object that has 2 properties, + // but unfortunately `type` is special in Mongoose so mongoose + // interprets this schema to mean that `asset` is a string + asset: { + type: String, + ticker: String + } +}); +``` + +However, when Mongoose sees `type: String`, it assumes that you mean +`asset` should be a string, not an object with a property `type`. +The correct way to define an object with a property `type` is shown +below. + +```javascript +const holdingSchema = new Schema({ + asset: { + // Workaround to make sure Mongoose knows `asset` is an object + // and `asset.type` is a string, rather than thinking `asset` + // is a string. + type: { type: String }, + ticker: String + } +}); +``` + +

    SchemaType Options

    + +You can declare a schema type using the type directly, or an object with +a `type` property. + +```javascript +const schema1 = new Schema({ + test: String // `test` is a path of type String +}); + +const schema2 = new Schema({ + // The `test` object contains the "SchemaType options" + test: { type: String } // `test` is a path of type string +}); +``` + +In addition to the type property, you can specify additional properties +for a path. For example, if you want to lowercase a string before saving: + +```javascript +const schema2 = new Schema({ + test: { + type: String, + lowercase: true // Always convert `test` to lowercase + } +}); +``` + +You can add any property you want to your SchemaType options. Many plugins +rely on custom SchemaType options. For example, the [mongoose-autopopulate](http://plugins.mongoosejs.io/plugins/autopopulate) +plugin automatically populates paths if you set `autopopulate: true` in your +SchemaType options. Mongoose comes with support for several built-in +SchemaType options, like `lowercase` in the above example. + +The `lowercase` option only works for strings. There are certain options +which apply for all schema types, and some that apply for specific schema +types. + +
    All Schema Types
    + +* `required`: boolean or function, if true adds a [required validator](./validation.html#built-in-validators) for this property +* `default`: Any or function, sets a default value for the path. If the value is a function, the return value of the function is used as the default. +* `select`: boolean, specifies default [projections](https://docs.mongodb.com/manual/tutorial/project-fields-from-query-results/) for queries +* `validate`: function, adds a [validator function](./validation.html#built-in-validators) for this property +* `get`: function, defines a custom getter for this property using [`Object.defineProperty()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty). +* `set`: function, defines a custom setter for this property using [`Object.defineProperty()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty). +* `alias`: string, mongoose >= 4.10.0 only. Defines a [virtual](./guide.html#virtuals) with the given name that gets/sets this path. +* `immutable`: boolean, defines path as immutable. Mongoose prevents you from changing immutable paths unless the parent document has `isNew: true`. +* `transform`: function, Mongoose calls this function when you call [`Document#toJSON()`](/docs/api/document.html#document_Document-toJSON) function, including when you [`JSON.stringify()`](https://thecodebarbarian.com/the-80-20-guide-to-json-stringify-in-javascript) a document. + +```javascript +const numberSchema = new Schema({ + integerOnly: { + type: Number, + get: v => Math.round(v), + set: v => Math.round(v), + alias: 'i' + } +}); + +const Number = mongoose.model('Number', numberSchema); + +const doc = new Number(); +doc.integerOnly = 2.001; +doc.integerOnly; // 2 +doc.i; // 2 +doc.i = 3.001; +doc.integerOnly; // 3 +doc.i; // 3 +``` + +
    Indexes
    + +You can also define [MongoDB indexes](https://docs.mongodb.com/manual/indexes/) +using schema type options. + +* `index`: boolean, whether to define an [index](https://docs.mongodb.com/manual/indexes/) on this property. +* `unique`: boolean, whether to define a [unique index](https://docs.mongodb.com/manual/core/index-unique/) on this property. +* `sparse`: boolean, whether to define a [sparse index](https://docs.mongodb.com/manual/core/index-sparse/) on this property. + +```javascript +const schema2 = new Schema({ + test: { + type: String, + index: true, + unique: true // Unique index. If you specify `unique: true` + // specifying `index: true` is optional if you do `unique: true` + } +}); +``` + +
    String
    + +* `lowercase`: boolean, whether to always call `.toLowerCase()` on the value +* `uppercase`: boolean, whether to always call `.toUpperCase()` on the value +* `trim`: boolean, whether to always call `.trim()` on the value +* `match`: RegExp, creates a [validator](./validation.html) that checks if the value matches the given regular expression +* `enum`: Array, creates a [validator](./validation.html) that checks if the value is in the given array. +* `minLength`: Number, creates a [validator](./validation.html) that checks if the value length is not less than the given number +* `maxLength`: Number, creates a [validator](./validation.html) that checks if the value length is not greater than the given number +* `populate`: Object, sets default [populate options](/docs/populate.html#query-conditions) + +
    Number
    + +* `min`: Number, creates a [validator](./validation.html) that checks if the value is greater than or equal to the given minimum. +* `max`: Number, creates a [validator](./validation.html) that checks if the value is less than or equal to the given maximum. +* `enum`: Array, creates a [validator](./validation.html) that checks if the value is strictly equal to one of the values in the given array. +* `populate`: Object, sets default [populate options](/docs/populate.html#query-conditions) + +
    Date
    + +* `min`: Date +* `max`: Date + +
    ObjectId
    + +* `populate`: Object, sets default [populate options](/docs/populate.html#query-conditions) + +

    Usage Notes

    + +

    String

    + +To declare a path as a string, you may use either the `String` global +constructor or the string `'String'`. + +```javascript +const schema1 = new Schema({ name: String }); // name will be cast to string +const schema2 = new Schema({ name: 'String' }); // Equivalent + +const Person = mongoose.model('Person', schema2); +``` + +If you pass an element that has a `toString()` function, Mongoose will call it, +unless the element is an array or the `toString()` function is strictly equal to +`Object.prototype.toString()`. + +```javascript +new Person({ name: 42 }).name; // "42" as a string +new Person({ name: { toString: () => 42 } }).name; // "42" as a string + +// "undefined", will get a cast error if you `save()` this document +new Person({ name: { foo: 42 } }).name; +``` + +

    Number

    + +To declare a path as a number, you may use either the `Number` global +constructor or the string `'Number'`. + +```javascript +const schema1 = new Schema({ age: Number }); // age will be cast to a Number +const schema2 = new Schema({ age: 'Number' }); // Equivalent + +const Car = mongoose.model('Car', schema2); +``` + +There are several types of values that will be successfully cast to a Number. + +```javascript +new Car({ age: '15' }).age; // 15 as a Number +new Car({ age: true }).age; // 1 as a Number +new Car({ age: false }).age; // 0 as a Number +new Car({ age: { valueOf: () => 83 } }).age; // 83 as a Number +``` + +If you pass an object with a `valueOf()` function that returns a Number, Mongoose will +call it and assign the returned value to the path. + +The values `null` and `undefined` are not cast. + +NaN, strings that cast to NaN, arrays, and objects that don't have a `valueOf()` function +will all result in a [CastError](/docs/validation.html#cast-errors) once validated, meaning that it will not throw on initialization, only when validated. + +

    Dates

    + +[Built-in `Date` methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) are [__not__ hooked into](https://github.com/Automattic/mongoose/issues/1598) the mongoose change tracking logic which in English means that if you use a `Date` in your document and modify it with a method like `setMonth()`, mongoose will be unaware of this change and `doc.save()` will not persist this modification. If you must modify `Date` types using built-in methods, tell mongoose about the change with `doc.markModified('pathToYourDate')` before saving. + +```javascript +const Assignment = mongoose.model('Assignment', { dueDate: Date }); +Assignment.findOne(function (err, doc) { + doc.dueDate.setMonth(3); + doc.save(callback); // THIS DOES NOT SAVE YOUR CHANGE + + doc.markModified('dueDate'); + doc.save(callback); // works +}) +``` + +

    Buffer

    + +To declare a path as a Buffer, you may use either the `Buffer` global +constructor or the string `'Buffer'`. + +```javascript +const schema1 = new Schema({ binData: Buffer }); // binData will be cast to a Buffer +const schema2 = new Schema({ binData: 'Buffer' }); // Equivalent + +const Data = mongoose.model('Data', schema2); +``` + +Mongoose will successfully cast the below values to buffers. + +``` +const file1 = new Data({ binData: 'test'}); // {"type":"Buffer","data":[116,101,115,116]} +const file2 = new Data({ binData: 72987 }); // {"type":"Buffer","data":[27]} +const file4 = new Data({ binData: { type: 'Buffer', data: [1, 2, 3]}}); // {"type":"Buffer","data":[1,2,3]} +``` + +

    Mixed

    + +An "anything goes" SchemaType. Mongoose will not do any casting on mixed paths. +You can define a mixed path using `Schema.Types.Mixed` or by passing an empty +object literal. The following are equivalent. + +```javascript +const Any = new Schema({ any: {} }); +const Any = new Schema({ any: Object }); +const Any = new Schema({ any: Schema.Types.Mixed }); +const Any = new Schema({ any: mongoose.Mixed }); +// Note that by default, if you're using `type`, putting _any_ POJO as the `type` will +// make the path mixed. +const Any = new Schema({ + any: { + type: { foo: String } + } // "any" will be Mixed - everything inside is ignored. +}); +// However, as of Mongoose 5.8.0, this behavior can be overridden with typePojoToMixed. +// In that case, it will create a single nested subdocument type instead. +const Any = new Schema({ + any: { + type: { foo: String } + } // "any" will be a single nested subdocument. +}, {typePojoToMixed: false}); +``` + +Since Mixed is a schema-less type, you can change the value to anything else you +like, but Mongoose loses the ability to auto detect and save those changes. +To tell Mongoose that the value of a Mixed type has changed, you need to +call `doc.markModified(path)`, passing the path to the Mixed type you just changed. + +To avoid these side-effects, a [Subdocument](./subdocs.html) path may be used +instead. + +```javascript +person.anything = { x: [3, 4, { y: "changed" }] }; +person.markModified('anything'); +person.save(); // Mongoose will save changes to `anything`. +``` + +

    ObjectIds

    + +An [ObjectId](https://docs.mongodb.com/manual/reference/method/ObjectId/) +is a special type typically used for unique identifiers. Here's how +you declare a schema with a path `driver` that is an ObjectId: + +```javascript +const mongoose = require('mongoose'); +const carSchema = new mongoose.Schema({ driver: mongoose.ObjectId }); +``` + +`ObjectId` is a class, and ObjectIds are objects. However, they are +often represented as strings. When you convert an ObjectId to a string +using `toString()`, you get a 24-character hexadecimal string: + +```javascript +const Car = mongoose.model('Car', carSchema); + +const car = new Car(); +car.driver = new mongoose.Types.ObjectId(); + +typeof car.driver; // 'object' +car.driver instanceof mongoose.Types.ObjectId; // true + +car.driver.toString(); // Something like "5e1a0651741b255ddda996c4" +``` + +

    Boolean

    + +Booleans in Mongoose are [plain JavaScript booleans](https://www.w3schools.com/js/js_booleans.asp). +By default, Mongoose casts the below values to `true`: + +* `true` +* `'true'` +* `1` +* `'1'` +* `'yes'` + +Mongoose casts the below values to `false`: + +* `false` +* `'false'` +* `0` +* `'0'` +* `'no'` + +Any other value causes a [CastError](/docs/validation.html#cast-errors). +You can modify what values Mongoose converts to true or false using the +`convertToTrue` and `convertToFalse` properties, which are [JavaScript sets](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set). + +```javascript +const M = mongoose.model('Test', new Schema({ b: Boolean })); +console.log(new M({ b: 'nay' }).b); // undefined + +// Set { false, 'false', 0, '0', 'no' } +console.log(mongoose.Schema.Types.Boolean.convertToFalse); + +mongoose.Schema.Types.Boolean.convertToFalse.add('nay'); +console.log(new M({ b: 'nay' }).b); // false +``` + +

    Arrays

    + +Mongoose supports arrays of [SchemaTypes](./api.html#schema_Schema.Types) +and arrays of [subdocuments](./subdocs.html). Arrays of SchemaTypes are +also called _primitive arrays_, and arrays of subdocuments are also called +_document arrays_. + +```javascript +const ToySchema = new Schema({ name: String }); +const ToyBoxSchema = new Schema({ + toys: [ToySchema], + buffers: [Buffer], + strings: [String], + numbers: [Number] + // ... etc +}); +``` + +Arrays are special because they implicitly have a default value of `[]` (empty array). + +```javascript +const ToyBox = mongoose.model('ToyBox', ToyBoxSchema); +console.log((new ToyBox()).toys); // [] +``` + +To overwrite this default, you need to set the default value to `undefined` + +```javascript +const ToyBoxSchema = new Schema({ + toys: { + type: [ToySchema], + default: undefined + } +}); +``` + +Note: specifying an empty array is equivalent to `Mixed`. The following all create arrays of +`Mixed`: + +```javascript +const Empty1 = new Schema({ any: [] }); +const Empty2 = new Schema({ any: Array }); +const Empty3 = new Schema({ any: [Schema.Types.Mixed] }); +const Empty4 = new Schema({ any: [{}] }); +``` + +

    Maps

    + +_New in Mongoose 5.1.0_ + +A `MongooseMap` is a subclass of [JavaScript's `Map` class](http://thecodebarbarian.com/the-80-20-guide-to-maps-in-javascript.html). +In these docs, we'll use the terms 'map' and `MongooseMap` interchangeably. +In Mongoose, maps are how you create a nested document with arbitrary keys. + +**Note**: In Mongoose Maps, keys must be strings in order to store the document in MongoDB. + +```javascript +const userSchema = new Schema({ + // `socialMediaHandles` is a map whose values are strings. A map's + // keys are always strings. You specify the type of values using `of`. + socialMediaHandles: { + type: Map, + of: String + } +}); + +const User = mongoose.model('User', userSchema); +// Map { 'github' => 'vkarpov15', 'twitter' => '@code_barbarian' } +console.log(new User({ + socialMediaHandles: { + github: 'vkarpov15', + twitter: '@code_barbarian' + } +}).socialMediaHandles); +``` + +The above example doesn't explicitly declare `github` or `twitter` as paths, +but, since `socialMediaHandles` is a map, you can store arbitrary key/value +pairs. However, since `socialMediaHandles` is a map, you **must** use +`.get()` to get the value of a key and `.set()` to set the value of a key. + +```javascript +const user = new User({ + socialMediaHandles: {} +}); + +// Good +user.socialMediaHandles.set('github', 'vkarpov15'); +// Works too +user.set('socialMediaHandles.twitter', '@code_barbarian'); +// Bad, the `myspace` property will **not** get saved +user.socialMediaHandles.myspace = 'fail'; + +// 'vkarpov15' +console.log(user.socialMediaHandles.get('github')); +// '@code_barbarian' +console.log(user.get('socialMediaHandles.twitter')); +// undefined +user.socialMediaHandles.github; + +// Will only save the 'github' and 'twitter' properties +user.save(); +``` + +Map types are stored as [BSON objects in MongoDB](https://en.wikipedia.org/wiki/BSON#Data_types_and_syntax). +Keys in a BSON object are ordered, so this means the [insertion order](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Description) +property of maps is maintained. + +Mongoose supports a special `$*` syntax to [populate](/docs/populate.html) all elements in a map. +For example, suppose your `socialMediaHandles` map contains a `ref`: + +```javascript +const userSchema = new Schema({ + socialMediaHandles: { + type: Map, + of: new Schema({ + handle: String, + oauth: { + type: ObjectId, + ref: 'OAuth' + } + }) + } +}); +const User = mongoose.model('User', userSchema); +``` + +To populate every `socialMediaHandles` entry's `oauth` property, you should populate +on `socialMediaHandles.$*.oauth`: + +```javascript +const user = await User.findOne().populate('socialMediaHandles.$*.oauth'); +``` + +

    Getters

    + +Getters are like virtuals for paths defined in your schema. For example, +let's say you wanted to store user profile pictures as relative paths and +then add the hostname in your application. Below is how you would structure +your `userSchema`: + +```javascript +const root = 'https://s3.amazonaws.com/mybucket'; + +const userSchema = new Schema({ + name: String, + picture: { + type: String, + get: v => `${root}${v}` + } +}); + +const User = mongoose.model('User', userSchema); + +const doc = new User({ name: 'Val', picture: '/123.png' }); +doc.picture; // 'https://s3.amazonaws.com/mybucket/123.png' +doc.toObject({ getters: false }).picture; // '/123.png' +``` + +Generally, you only use getters on primitive paths as opposed to arrays +or subdocuments. Because getters override what accessing a Mongoose path returns, +declaring a getter on an object may remove Mongoose change tracking for +that path. + +```javascript +const schema = new Schema({ + arr: [{ url: String }] +}); + +const root = 'https://s3.amazonaws.com/mybucket'; + +// Bad, don't do this! +schema.path('arr').get(v => { + return v.map(el => Object.assign(el, { url: root + el.url })); +}); + +// Later +doc.arr.push({ key: String }); +doc.arr[0]; // 'undefined' because every `doc.arr` creates a new array! +``` + +Instead of declaring a getter on the array as shown above, you should +declare a getter on the `url` string as shown below. If you need to declare +a getter on a nested document or array, be very careful! + +```javascript +const schema = new Schema({ + arr: [{ url: String }] +}); + +const root = 'https://s3.amazonaws.com/mybucket'; + +// Good, do this instead of declaring a getter on `arr` +schema.path('arr.0.url').get(v => `${root}${v}`); +``` + +

    Schemas

    + +To declare a path as another [schema](./guide.html#definition), +set `type` to the sub-schema's instance. + +To set a default value based on the sub-schema's shape, simply set a default value, +and the value will be cast based on the sub-schema's definition before being set +during document creation. + +```javascript +const subSchema = new mongoose.Schema({ + // some schema definition here +}); + +const schema = new mongoose.Schema({ + data: { + type: subSchema + default: {} + } +}); +``` + +

    Creating Custom Types

    + +Mongoose can also be extended with [custom SchemaTypes](customschematypes.html). Search the +[plugins](http://plugins.mongoosejs.io) +site for compatible types like +[mongoose-long](https://github.com/aheckmann/mongoose-long), +[mongoose-int32](https://github.com/vkarpov15/mongoose-int32), +and +[other](https://github.com/aheckmann/mongoose-function) +[types](https://github.com/OpenifyIt/mongoose-types). + +Read more about creating [custom SchemaTypes here](customschematypes.html). + +

    The `schema.path()` Function

    + +The `schema.path()` function returns the instantiated schema type for a +given path. + +```javascript +const sampleSchema = new Schema({ name: { type: String, required: true } }); +console.log(sampleSchema.path('name')); +// Output looks like: +/** + * SchemaString { + * enumValues: [], + * regExp: null, + * path: 'name', + * instance: 'String', + * validators: ... + */ + ``` + +You can use this function to inspect the schema type for a given path, +including what validators it has and what the type is. + +

    Further Reading

    + + diff --git a/docs/schematypes.pug b/docs/schematypes.pug deleted file mode 100644 index 7d08ee24737..00000000000 --- a/docs/schematypes.pug +++ /dev/null @@ -1,735 +0,0 @@ -extends layout - -block append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown -

    SchemaTypes

    - - - - - - SchemaTypes handle definition of path - [defaults](./api.html#schematype_SchemaType-default), - [validation](./api.html#schematype_SchemaType-validate), - [getters](#getters), - [setters](./api.html#schematype_SchemaType-set), - [field selection defaults](./api.html#schematype_SchemaType-select) for - [queries](./api.html#query-js), - and other general characteristics for Mongoose document properties. - - - - * [What is a SchemaType?](#what-is-a-schematype) - * [The `type` Key](#type-key) - * [SchemaType Options](#schematype-options) - * [Usage Notes](#usage-notes) - * [Getters](#getters) - * [Custom Types](#customtypes) - * [The `schema.path()` Function](#path) - -

    What is a SchemaType?

    - - You can think of a Mongoose schema as the configuration object for a - Mongoose model. A SchemaType is then a configuration object for an individual - property. A SchemaType says what type a given - path should have, whether it has any getters/setters, and what values are - valid for that path. - - ```javascript - const schema = new Schema({ name: String }); - schema.path('name') instanceof mongoose.SchemaType; // true - schema.path('name') instanceof mongoose.Schema.Types.String; // true - schema.path('name').instance; // 'String' - ``` - - A SchemaType is different from a type. In other words, `mongoose.ObjectId !== mongoose.Types.ObjectId`. - A SchemaType is just a configuration object for Mongoose. An instance of - the `mongoose.ObjectId` SchemaType doesn't actually create MongoDB ObjectIds, - it is just a configuration for a path in a schema. - - The following are all the valid SchemaTypes in Mongoose. Mongoose plugins - can also add custom SchemaTypes like [int32](http://plugins.mongoosejs.io/plugins/int32). - Check out [Mongoose's plugins search](http://plugins.mongoosejs.io) to find plugins. - - - [String](#strings) - - [Number](#numbers) - - [Date](#dates) - - [Buffer](#buffers) - - [Boolean](#booleans) - - [Mixed](#mixed) - - [ObjectId](#objectids) - - [Array](#arrays) - - [Decimal128](./api.html#mongoose_Mongoose-Decimal128) - - [Map](#maps) - - [Schema](#schemas) - -

    Example

    - - ```javascript - const schema = new Schema({ - name: String, - binary: Buffer, - living: Boolean, - updated: { type: Date, default: Date.now }, - age: { type: Number, min: 18, max: 65 }, - mixed: Schema.Types.Mixed, - _someId: Schema.Types.ObjectId, - decimal: Schema.Types.Decimal128, - array: [], - ofString: [String], - ofNumber: [Number], - ofDates: [Date], - ofBuffer: [Buffer], - ofBoolean: [Boolean], - ofMixed: [Schema.Types.Mixed], - ofObjectId: [Schema.Types.ObjectId], - ofArrays: [[]], - ofArrayOfNumbers: [[Number]], - nested: { - stuff: { type: String, lowercase: true, trim: true } - }, - map: Map, - mapOfString: { - type: Map, - of: String - } - }) - - // example use - - const Thing = mongoose.model('Thing', schema); - - const m = new Thing; - m.name = 'Statue of Liberty'; - m.age = 125; - m.updated = new Date; - m.binary = Buffer.alloc(0); - m.living = false; - m.mixed = { any: { thing: 'i want' } }; - m.markModified('mixed'); - m._someId = new mongoose.Types.ObjectId; - m.array.push(1); - m.ofString.push("strings!"); - m.ofNumber.unshift(1,2,3,4); - m.ofDates.addToSet(new Date); - m.ofBuffer.pop(); - m.ofMixed = [1, [], 'three', { four: 5 }]; - m.nested.stuff = 'good'; - m.map = new Map([['key', 'value']]); - m.save(callback); - ``` - -

    The `type` Key

    - - `type` is a special property in Mongoose schemas. When Mongoose finds - a nested property named `type` in your schema, Mongoose assumes that - it needs to define a SchemaType with the given type. - - ```javascript - // 3 string SchemaTypes: 'name', 'nested.firstName', 'nested.lastName' - const schema = new Schema({ - name: { type: String }, - nested: { - firstName: { type: String }, - lastName: { type: String } - } - }); - ``` - - As a consequence, [you need a little extra work to define a property named `type` in your schema](/docs/faq.html#type-key). - For example, suppose you're building a stock portfolio app, and you - want to store the asset's `type` (stock, bond, ETF, etc.). Naively, - you might define your schema as shown below: - - ```javascript - const holdingSchema = new Schema({ - // You might expect `asset` to be an object that has 2 properties, - // but unfortunately `type` is special in Mongoose so mongoose - // interprets this schema to mean that `asset` is a string - asset: { - type: String, - ticker: String - } - }); - ``` - - However, when Mongoose sees `type: String`, it assumes that you mean - `asset` should be a string, not an object with a property `type`. - The correct way to define an object with a property `type` is shown - below. - - ```javascript - const holdingSchema = new Schema({ - asset: { - // Workaround to make sure Mongoose knows `asset` is an object - // and `asset.type` is a string, rather than thinking `asset` - // is a string. - type: { type: String }, - ticker: String - } - }); - ``` - -

    SchemaType Options

    - - You can declare a schema type using the type directly, or an object with - a `type` property. - - ```javascript - const schema1 = new Schema({ - test: String // `test` is a path of type String - }); - - const schema2 = new Schema({ - // The `test` object contains the "SchemaType options" - test: { type: String } // `test` is a path of type string - }); - ``` - - In addition to the type property, you can specify additional properties - for a path. For example, if you want to lowercase a string before saving: - - ```javascript - const schema2 = new Schema({ - test: { - type: String, - lowercase: true // Always convert `test` to lowercase - } - }); - ``` - - You can add any property you want to your SchemaType options. Many plugins - rely on custom SchemaType options. For example, the [mongoose-autopopulate](http://plugins.mongoosejs.io/plugins/autopopulate) - plugin automatically populates paths if you set `autopopulate: true` in your - SchemaType options. Mongoose comes with support for several built-in - SchemaType options, like `lowercase` in the above example. - - The `lowercase` option only works for strings. There are certain options - which apply for all schema types, and some that apply for specific schema - types. - -
    All Schema Types
    - - * `required`: boolean or function, if true adds a [required validator](./validation.html#built-in-validators) for this property - * `default`: Any or function, sets a default value for the path. If the value is a function, the return value of the function is used as the default. - * `select`: boolean, specifies default [projections](https://docs.mongodb.com/manual/tutorial/project-fields-from-query-results/) for queries - * `validate`: function, adds a [validator function](./validation.html#built-in-validators) for this property - * `get`: function, defines a custom getter for this property using [`Object.defineProperty()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty). - * `set`: function, defines a custom setter for this property using [`Object.defineProperty()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty). - * `alias`: string, mongoose >= 4.10.0 only. Defines a [virtual](./guide.html#virtuals) with the given name that gets/sets this path. - * `immutable`: boolean, defines path as immutable. Mongoose prevents you from changing immutable paths unless the parent document has `isNew: true`. - * `transform`: function, Mongoose calls this function when you call [`Document#toJSON()`](/docs/api/document.html#document_Document-toJSON) function, including when you [`JSON.stringify()`](https://thecodebarbarian.com/the-80-20-guide-to-json-stringify-in-javascript) a document. - - ```javascript - const numberSchema = new Schema({ - integerOnly: { - type: Number, - get: v => Math.round(v), - set: v => Math.round(v), - alias: 'i' - } - }); - - const Number = mongoose.model('Number', numberSchema); - - const doc = new Number(); - doc.integerOnly = 2.001; - doc.integerOnly; // 2 - doc.i; // 2 - doc.i = 3.001; - doc.integerOnly; // 3 - doc.i; // 3 - ``` - -
    Indexes
    - - You can also define [MongoDB indexes](https://docs.mongodb.com/manual/indexes/) - using schema type options. - - * `index`: boolean, whether to define an [index](https://docs.mongodb.com/manual/indexes/) on this property. - * `unique`: boolean, whether to define a [unique index](https://docs.mongodb.com/manual/core/index-unique/) on this property. - * `sparse`: boolean, whether to define a [sparse index](https://docs.mongodb.com/manual/core/index-sparse/) on this property. - - ```javascript - const schema2 = new Schema({ - test: { - type: String, - index: true, - unique: true // Unique index. If you specify `unique: true` - // specifying `index: true` is optional if you do `unique: true` - } - }); - ``` - -
    String
    - - * `lowercase`: boolean, whether to always call `.toLowerCase()` on the value - * `uppercase`: boolean, whether to always call `.toUpperCase()` on the value - * `trim`: boolean, whether to always call `.trim()` on the value - * `match`: RegExp, creates a [validator](./validation.html) that checks if the value matches the given regular expression - * `enum`: Array, creates a [validator](./validation.html) that checks if the value is in the given array. - * `minLength`: Number, creates a [validator](./validation.html) that checks if the value length is not less than the given number - * `maxLength`: Number, creates a [validator](./validation.html) that checks if the value length is not greater than the given number - * `populate`: Object, sets default [populate options](/docs/populate.html#query-conditions) - -
    Number
    - - * `min`: Number, creates a [validator](./validation.html) that checks if the value is greater than or equal to the given minimum. - * `max`: Number, creates a [validator](./validation.html) that checks if the value is less than or equal to the given maximum. - * `enum`: Array, creates a [validator](./validation.html) that checks if the value is strictly equal to one of the values in the given array. - * `populate`: Object, sets default [populate options](/docs/populate.html#query-conditions) - -
    Date
    - - * `min`: Date - * `max`: Date - -
    ObjectId
    - - * `populate`: Object, sets default [populate options](/docs/populate.html#query-conditions) - -

    Usage Notes

    - -

    String

    - - To declare a path as a string, you may use either the `String` global - constructor or the string `'String'`. - - ```javascript - const schema1 = new Schema({ name: String }); // name will be cast to string - const schema2 = new Schema({ name: 'String' }); // Equivalent - - const Person = mongoose.model('Person', schema2); - ``` - - If you pass an element that has a `toString()` function, Mongoose will call it, - unless the element is an array or the `toString()` function is strictly equal to - `Object.prototype.toString()`. - - ```javascript - new Person({ name: 42 }).name; // "42" as a string - new Person({ name: { toString: () => 42 } }).name; // "42" as a string - - // "undefined", will get a cast error if you `save()` this document - new Person({ name: { foo: 42 } }).name; - ``` - -

    Number

    - - To declare a path as a number, you may use either the `Number` global - constructor or the string `'Number'`. - - ```javascript - const schema1 = new Schema({ age: Number }); // age will be cast to a Number - const schema2 = new Schema({ age: 'Number' }); // Equivalent - - const Car = mongoose.model('Car', schema2); - ``` - - There are several types of values that will be successfully cast to a Number. - - ```javascript - new Car({ age: '15' }).age; // 15 as a Number - new Car({ age: true }).age; // 1 as a Number - new Car({ age: false }).age; // 0 as a Number - new Car({ age: { valueOf: () => 83 } }).age; // 83 as a Number - ``` - - If you pass an object with a `valueOf()` function that returns a Number, Mongoose will - call it and assign the returned value to the path. - - The values `null` and `undefined` are not cast. - - NaN, strings that cast to NaN, arrays, and objects that don't have a `valueOf()` function - will all result in a [CastError](/docs/validation.html#cast-errors) once validated, meaning that it will not throw on initialization, only when validated. - -

    Dates

    - - [Built-in `Date` methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) are [__not__ hooked into](https://github.com/Automattic/mongoose/issues/1598) the mongoose change tracking logic which in English means that if you use a `Date` in your document and modify it with a method like `setMonth()`, mongoose will be unaware of this change and `doc.save()` will not persist this modification. If you must modify `Date` types using built-in methods, tell mongoose about the change with `doc.markModified('pathToYourDate')` before saving. - - ```javascript - const Assignment = mongoose.model('Assignment', { dueDate: Date }); - Assignment.findOne(function (err, doc) { - doc.dueDate.setMonth(3); - doc.save(callback); // THIS DOES NOT SAVE YOUR CHANGE - - doc.markModified('dueDate'); - doc.save(callback); // works - }) - ``` - -

    Buffer

    - - To declare a path as a Buffer, you may use either the `Buffer` global - constructor or the string `'Buffer'`. - - ```javascript - const schema1 = new Schema({ binData: Buffer }); // binData will be cast to a Buffer - const schema2 = new Schema({ binData: 'Buffer' }); // Equivalent - - const Data = mongoose.model('Data', schema2); - ``` - - Mongoose will successfully cast the below values to buffers. - - ``` - const file1 = new Data({ binData: 'test'}); // {"type":"Buffer","data":[116,101,115,116]} - const file2 = new Data({ binData: 72987 }); // {"type":"Buffer","data":[27]} - const file4 = new Data({ binData: { type: 'Buffer', data: [1, 2, 3]}}); // {"type":"Buffer","data":[1,2,3]} - ``` - -

    Mixed

    - - An "anything goes" SchemaType. Mongoose will not do any casting on mixed paths. - You can define a mixed path using `Schema.Types.Mixed` or by passing an empty - object literal. The following are equivalent. - - ```javascript - const Any = new Schema({ any: {} }); - const Any = new Schema({ any: Object }); - const Any = new Schema({ any: Schema.Types.Mixed }); - const Any = new Schema({ any: mongoose.Mixed }); - // Note that by default, if you're using `type`, putting _any_ POJO as the `type` will - // make the path mixed. - const Any = new Schema({ - any: { - type: { foo: String } - } // "any" will be Mixed - everything inside is ignored. - }); - // However, as of Mongoose 5.8.0, this behavior can be overridden with typePojoToMixed. - // In that case, it will create a single nested subdocument type instead. - const Any = new Schema({ - any: { - type: { foo: String } - } // "any" will be a single nested subdocument. - }, {typePojoToMixed: false}); - ``` - - Since Mixed is a schema-less type, you can change the value to anything else you - like, but Mongoose loses the ability to auto detect and save those changes. - To tell Mongoose that the value of a Mixed type has changed, you need to - call `doc.markModified(path)`, passing the path to the Mixed type you just changed. - - To avoid these side-effects, a [Subdocument](./subdocs.html) path may be used - instead. - - ```javascript - person.anything = { x: [3, 4, { y: "changed" }] }; - person.markModified('anything'); - person.save(); // Mongoose will save changes to `anything`. - ``` - -

    ObjectIds

    - - An [ObjectId](https://docs.mongodb.com/manual/reference/method/ObjectId/) - is a special type typically used for unique identifiers. Here's how - you declare a schema with a path `driver` that is an ObjectId: - - ```javascript - const mongoose = require('mongoose'); - const carSchema = new mongoose.Schema({ driver: mongoose.ObjectId }); - ``` - - `ObjectId` is a class, and ObjectIds are objects. However, they are - often represented as strings. When you convert an ObjectId to a string - using `toString()`, you get a 24-character hexadecimal string: - - ```javascript - const Car = mongoose.model('Car', carSchema); - - const car = new Car(); - car.driver = new mongoose.Types.ObjectId(); - - typeof car.driver; // 'object' - car.driver instanceof mongoose.Types.ObjectId; // true - - car.driver.toString(); // Something like "5e1a0651741b255ddda996c4" - ``` - -

    Boolean

    - - Booleans in Mongoose are [plain JavaScript booleans](https://www.w3schools.com/js/js_booleans.asp). - By default, Mongoose casts the below values to `true`: - - * `true` - * `'true'` - * `1` - * `'1'` - * `'yes'` - - Mongoose casts the below values to `false`: - - * `false` - * `'false'` - * `0` - * `'0'` - * `'no'` - - Any other value causes a [CastError](/docs/validation.html#cast-errors). - You can modify what values Mongoose converts to true or false using the - `convertToTrue` and `convertToFalse` properties, which are [JavaScript sets](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set). - - ```javascript - const M = mongoose.model('Test', new Schema({ b: Boolean })); - console.log(new M({ b: 'nay' }).b); // undefined - - // Set { false, 'false', 0, '0', 'no' } - console.log(mongoose.Schema.Types.Boolean.convertToFalse); - - mongoose.Schema.Types.Boolean.convertToFalse.add('nay'); - console.log(new M({ b: 'nay' }).b); // false - ``` - -

    Arrays

    - - Mongoose supports arrays of [SchemaTypes](./api.html#schema_Schema.Types) - and arrays of [subdocuments](./subdocs.html). Arrays of SchemaTypes are - also called _primitive arrays_, and arrays of subdocuments are also called - _document arrays_. - - ```javascript - const ToySchema = new Schema({ name: String }); - const ToyBoxSchema = new Schema({ - toys: [ToySchema], - buffers: [Buffer], - strings: [String], - numbers: [Number] - // ... etc - }); - ``` - - Arrays are special because they implicitly have a default value of `[]` (empty array). - - ```javascript - const ToyBox = mongoose.model('ToyBox', ToyBoxSchema); - console.log((new ToyBox()).toys); // [] - ``` - - To overwrite this default, you need to set the default value to `undefined` - - ```javascript - const ToyBoxSchema = new Schema({ - toys: { - type: [ToySchema], - default: undefined - } - }); - ``` - - Note: specifying an empty array is equivalent to `Mixed`. The following all create arrays of - `Mixed`: - - ```javascript - const Empty1 = new Schema({ any: [] }); - const Empty2 = new Schema({ any: Array }); - const Empty3 = new Schema({ any: [Schema.Types.Mixed] }); - const Empty4 = new Schema({ any: [{}] }); - ``` - -

    Maps

    - - _New in Mongoose 5.1.0_ - - A `MongooseMap` is a subclass of [JavaScript's `Map` class](http://thecodebarbarian.com/the-80-20-guide-to-maps-in-javascript.html). - In these docs, we'll use the terms 'map' and `MongooseMap` interchangeably. - In Mongoose, maps are how you create a nested document with arbitrary keys. - - **Note**: In Mongoose Maps, keys must be strings in order to store the document in MongoDB. - - ```javascript - const userSchema = new Schema({ - // `socialMediaHandles` is a map whose values are strings. A map's - // keys are always strings. You specify the type of values using `of`. - socialMediaHandles: { - type: Map, - of: String - } - }); - - const User = mongoose.model('User', userSchema); - // Map { 'github' => 'vkarpov15', 'twitter' => '@code_barbarian' } - console.log(new User({ - socialMediaHandles: { - github: 'vkarpov15', - twitter: '@code_barbarian' - } - }).socialMediaHandles); - ``` - - The above example doesn't explicitly declare `github` or `twitter` as paths, - but, since `socialMediaHandles` is a map, you can store arbitrary key/value - pairs. However, since `socialMediaHandles` is a map, you **must** use - `.get()` to get the value of a key and `.set()` to set the value of a key. - - ```javascript - const user = new User({ - socialMediaHandles: {} - }); - - // Good - user.socialMediaHandles.set('github', 'vkarpov15'); - // Works too - user.set('socialMediaHandles.twitter', '@code_barbarian'); - // Bad, the `myspace` property will **not** get saved - user.socialMediaHandles.myspace = 'fail'; - - // 'vkarpov15' - console.log(user.socialMediaHandles.get('github')); - // '@code_barbarian' - console.log(user.get('socialMediaHandles.twitter')); - // undefined - user.socialMediaHandles.github; - - // Will only save the 'github' and 'twitter' properties - user.save(); - ``` - - Map types are stored as [BSON objects in MongoDB](https://en.wikipedia.org/wiki/BSON#Data_types_and_syntax). - Keys in a BSON object are ordered, so this means the [insertion order](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Description) - property of maps is maintained. - -

    Getters

    - - Getters are like virtuals for paths defined in your schema. For example, - let's say you wanted to store user profile pictures as relative paths and - then add the hostname in your application. Below is how you would structure - your `userSchema`: - - ```javascript - const root = 'https://s3.amazonaws.com/mybucket'; - - const userSchema = new Schema({ - name: String, - picture: { - type: String, - get: v => `${root}${v}` - } - }); - - const User = mongoose.model('User', userSchema); - - const doc = new User({ name: 'Val', picture: '/123.png' }); - doc.picture; // 'https://s3.amazonaws.com/mybucket/123.png' - doc.toObject({ getters: false }).picture; // '/123.png' - ``` - - Generally, you only use getters on primitive paths as opposed to arrays - or subdocuments. Because getters override what accessing a Mongoose path returns, - declaring a getter on an object may remove Mongoose change tracking for - that path. - - ```javascript - const schema = new Schema({ - arr: [{ url: String }] - }); - - const root = 'https://s3.amazonaws.com/mybucket'; - - // Bad, don't do this! - schema.path('arr').get(v => { - return v.map(el => Object.assign(el, { url: root + el.url })); - }); - - // Later - doc.arr.push({ key: String }); - doc.arr[0]; // 'undefined' because every `doc.arr` creates a new array! - ``` - - Instead of declaring a getter on the array as shown above, you should - declare a getter on the `url` string as shown below. If you need to declare - a getter on a nested document or array, be very careful! - - ```javascript - const schema = new Schema({ - arr: [{ url: String }] - }); - - const root = 'https://s3.amazonaws.com/mybucket'; - - // Good, do this instead of declaring a getter on `arr` - schema.path('arr.0.url').get(v => `${root}${v}`); - ``` - -

    Schemas

    - - To declare a path as another [schema](./guide.html#definition), - set `type` to the sub-schema's instance. - - To set a default value based on the sub-schema's shape, simply set a default value, - and the value will be cast based on the sub-schema's definition before being set - during document creation. - - ```javascript - const subSchema = new mongoose.Schema({ - // some schema definition here - }); - - const schema = new mongoose.Schema({ - data: { - type: subSchema - default: {} - } - }); - ``` - -

    Creating Custom Types

    - - Mongoose can also be extended with [custom SchemaTypes](customschematypes.html). Search the - [plugins](http://plugins.mongoosejs.io) - site for compatible types like - [mongoose-long](https://github.com/aheckmann/mongoose-long), - [mongoose-int32](https://github.com/vkarpov15/mongoose-int32), - and - [other](https://github.com/aheckmann/mongoose-function) - [types](https://github.com/OpenifyIt/mongoose-types). - - Read more about creating [custom SchemaTypes here](customschematypes.html). - -

    The `schema.path()` Function

    - - The `schema.path()` function returns the instantiated schema type for a - given path. - - ```javascript - const sampleSchema = new Schema({ name: { type: String, required: true } }); - console.log(sampleSchema.path('name')); - // Output looks like: - /** - * SchemaString { - * enumValues: [], - * regExp: null, - * path: 'name', - * instance: 'String', - * validators: ... - */ - ``` - - You can use this function to inspect the schema type for a given path, - including what validators it has and what the type is. - -

    Further Reading

    - - diff --git a/docs/search.html b/docs/search.html deleted file mode 100644 index b9de2769480..00000000000 --- a/docs/search.html +++ /dev/null @@ -1,100 +0,0 @@ -Mongoose v5.6.0: Search

    Search

    \ No newline at end of file diff --git a/docs/search.js b/docs/search.js index c85a0feaf55..d093bc812e2 100644 --- a/docs/search.js +++ b/docs/search.js @@ -47,10 +47,40 @@ for (const filename of files) { contents.push(content); } } + } else if (file.markdown) { + let text = fs.readFileSync(filename, 'utf8'); + text = markdown(text); + + const content = new Content({ + title: file.title, + body: text, + url: filename.replace('.md', '.html').replace(/^docs/, '') + }); + + content.validateSync(); + + const $ = cheerio.load(text); + + contents.push(content); + + // Break up individual h3's into separate content for more fine grained search + $('h3').each((index, el) => { + el = $(el); + const title = el.text(); + const html = el.nextUntil('h3').html(); + const content = new Content({ + title: `${file.title}: ${title}`, + body: html, + url: `${filename.replace('.md', '.html').replace(/^docs/, '')}#${el.prop('id')}` + }); + + content.validateSync(); + contents.push(content); + }); } else if (file.guide) { let text = fs.readFileSync(filename, 'utf8'); text = text.substr(text.indexOf('block content') + 'block content\n'.length); - text = pug.render(`div\n${text}`, { filters: { markdown } }); + text = pug.render(`div\n${text}`, { filters: { markdown }, filename }); const content = new Content({ title: file.title, diff --git a/docs/source/acquit.js b/docs/source/acquit.js deleted file mode 100644 index 126fdb71336..00000000000 --- a/docs/source/acquit.js +++ /dev/null @@ -1,99 +0,0 @@ -'use strict'; -var fs = require('fs'); -var acquit = require('acquit'); -var hl = require('highlight.js'); -var marked = require('marked'); - -require('acquit-ignore')(); - -var files = [ - { - input: 'test/docs/defaults.test.js', - output: 'defaults.html', - title: 'Defaults' - }, - { - input: 'test/docs/discriminators.test.js', - output: 'discriminators.html', - title: 'Discriminators' - }, - { - input: 'test/es-next/promises.test.es6.js', - output: 'promises.html', - title: 'Promises', - suffix: ` -
    -
    - - Want to learn how to check whether your favorite npm modules work with - async/await without cobbling together contradictory answers from Google - and Stack Overflow? Chapter 4 of Mastering Async/Await explains the - basic principles for determining whether frameworks like React and - Mongoose support async/await. - Get your copy! - -

    - - - -
    - ` - }, - { - input: 'test/docs/schematypes.test.js', - output: 'customschematypes.html', - title: 'Custom Schema Types' - }, - { - input: 'test/docs/validation.test.js', - output: 'validation.html', - title: 'Validation' - }, - { - input: 'test/docs/schemas.test.js', - output: 'advanced_schemas.html', - title: 'Advanced Schemas' - } -]; - -files.forEach(function(file) { - var blocks = acquit.parse(fs.readFileSync(file.input).toString()); - - for (var i = 0; i < blocks.length; ++i) { - var block = blocks[i]; - block.identifier = toHtmlIdentifier(acquit.trimEachLine(block.contents)); - block.contents = marked(acquit.trimEachLine(block.contents)); - if (block.comments && block.comments.length) { - var last = block.comments.length - 1; - block.comments[last] = - marked(acquit.trimEachLine(block.comments[last])); - } - if (block.code) { - b.code = hl.highlight('javascript', b.code).value; - } - - for (var j = 0; j < block.blocks.length; ++j) { - var b = block.blocks[j]; - b.identifier = toHtmlIdentifier(acquit.trimEachLine(b.contents)); - b.contents = marked(acquit.trimEachLine(b.contents), []); - if (b.comments && b.comments.length) { - var last = b.comments.length - 1; - b.comments[last] = marked(acquit.trimEachLine(b.comments[last])); - } - } - } - - exports[file.output] = { - input: file.input, - title: file.title, - acquitBlocks: blocks, - suffix: file.suffix, - destination: file.output, - guide: true - } -}); - -function toHtmlIdentifier(str) { - return str.toLowerCase().replace(/ /g, '-').replace(/\(/g, ''). - replace(/\)/, '').replace(/`/g, '').replace(/\./g, '-').replace(/'/g, ''); -} diff --git a/docs/source/api.js b/docs/source/api.js index 0f78017e634..2026683d0bb 100644 --- a/docs/source/api.js +++ b/docs/source/api.js @@ -24,6 +24,7 @@ const files = [ 'lib/virtualtype.js', 'lib/error/index.js', 'lib/types/core_array.js', + 'lib/schema/array.js', 'lib/schema/documentarray.js', 'lib/schema/SingleNestedPath.js', 'lib/options/SchemaTypeOptions.js', @@ -60,10 +61,14 @@ function parse() { replace('.js', ''). replace('/index', ''); const lastSlash = name.lastIndexOf('/'); + const fullName = name; name = name.substr(lastSlash === -1 ? 0 : lastSlash + 1); if (name === 'core_array') { name = 'array'; } + if (fullName === 'schema/array') { + name = 'SchemaArray'; + } if (name === 'documentarray') { name = 'DocumentArrayPath'; } @@ -101,13 +106,13 @@ function parse() { ctx.type = 'property'; ctx.static = true; ctx.name = tag.string; - ctx.string = `${ctx.constructor}.${ctx.name}`; + ctx.string = `${data.name}.${ctx.name}`; break; case 'function': ctx.type = 'function'; ctx.static = true; ctx.name = tag.string; - ctx.string = `${ctx.constructor}.${ctx.name}()`; + ctx.string = `${data.name}.${ctx.name}()`; break; case 'return': tag.description = tag.description ? @@ -164,7 +169,7 @@ function parse() { ctx.description = prop.description.full. replace(/
    /ig, ' '). - replace(/>/i, '>'); + replace(/>/ig, '>'); ctx.description = highlight(ctx.description); data.props.push(ctx); diff --git a/docs/source/index.js b/docs/source/index.js index 9a78297a33e..df4be188c73 100644 --- a/docs/source/index.js +++ b/docs/source/index.js @@ -2,35 +2,44 @@ 'use strict'; exports['index.pug'] = require('./home'); exports['docs/api.pug'] = require('./api'); -exports['docs/browser.pug'] = { guide: true, title: 'Browser Library', acquit: true }; -exports['docs/index.pug'] = { title: 'Getting Started' }; -exports['docs/production.pug'] = require('./production'); exports['docs/prior.pug'] = require('./prior'); -exports['docs/guides.pug'] = { guide: true, schema: true, title: 'Schemas' }; -exports['docs/guide.pug'] = { guide: true, schema: true, title: 'Schemas', acquit: true }; -exports['docs/schematypes.pug'] = { guide: true, schema: true, title: 'SchemaTypes' }; -exports['docs/middleware.pug'] = { guide: true, title: 'Middleware', acquit: true }; -exports['docs/plugins.pug'] = { guide: true, title: 'Plugins' }; -exports['docs/subdocs.pug'] = { guide: true, docs: true, title: 'SubDocuments' }; -exports['docs/documents.pug'] = { guide: true, docs: true, title: 'Documents' }; -exports['docs/models.pug'] = { guide: true, title: 'Models' }; -exports['docs/queries.pug'] = { guide: true, title: 'Queries' }; -exports['docs/populate.pug'] = { guide: true, title: 'Query Population' }; -exports['docs/migration.pug'] = { guide: true, title: 'Migration Guide' }; -exports['docs/migrating_to_5.pug'] = { guide: true, title: 'Migrating to Mongoose 5' }; -exports['docs/contributing.pug'] = { guide: true, title: 'Contributing' }; -exports['docs/connections.pug'] = { guide: true, title: 'Connecting to MongoDB' }; -exports['docs/lambda.pug'] = { guide: true, title: 'Using Mongoose With AWS Lambda' }; -exports['docs/geojson.pug'] = { guide: true, title: 'Using GeoJSON', acquit: true }; -exports['docs/transactions.pug'] = { guide: true, title: 'Transactions', acquit: true }; -exports['docs/deprecations.pug'] = { guide: true, title: 'Deprecation Warnings' }; -exports['docs/further_reading.pug'] = { title: 'Further Reading' }; -exports['docs/jest.pug'] = { title: 'Testing Mongoose with Jest' }; -exports['docs/faq.pug'] = { guide: true, title: 'FAQ' }; -exports['docs/compatibility.pug'] = { + +exports['docs/advanced_schemas.md'] = { title: 'Advanced Schemas', acquit: true, markdown: true }; +exports['docs/validation.md'] = { title: 'Validation', acquit: true, markdown: true }; +exports['docs/customschematypes.md'] = { title: 'Custom Schema Types', acquit: true, markdown: true }; +exports['docs/promises.md'] = { title: 'Promises', acquit: true, markdown: true }; +exports['docs/discriminators.md'] = { title: 'Discriminators', acquit: true, markdown: true }; +exports['docs/defaults.md'] = { title: 'Defaults', acquit: true, markdown: true }; +exports['docs/index.md'] = { title: 'Getting Started', markdown: true }; +exports['docs/browser.md'] = { guide: true, title: 'Browser Library', acquit: true, markdown: true }; +exports['docs/guides.md'] = { guide: true, schema: true, title: 'Schemas', markdown: true }; +exports['docs/guide.md'] = { guide: true, schema: true, title: 'Schemas', acquit: true, markdown: true }; +exports['docs/schematypes.md'] = { guide: true, schema: true, title: 'SchemaTypes', markdown: true }; +exports['docs/middleware.md'] = { guide: true, title: 'Middleware', acquit: true, markdown: true }; +exports['docs/plugins.md'] = { guide: true, title: 'Plugins', markdown: true }; +exports['docs/subdocs.md'] = { guide: true, docs: true, title: 'SubDocuments', markdown: true }; +exports['docs/documents.md'] = { guide: true, docs: true, title: 'Documents', markdown: true }; +exports['docs/models.md'] = { guide: true, title: 'Models', markdown: true }; +exports['docs/queries.md'] = { guide: true, title: 'Queries', markdown: true }; +exports['docs/populate.md'] = { guide: true, title: 'Query Population', markdown: true }; +exports['docs/migration.md'] = { guide: true, title: 'Migration Guide', markdown: true }; +exports['docs/migrating_to_5.md'] = { guide: true, title: 'Migrating to Mongoose 5', markdown: true }; +exports['docs/contributing.md'] = { guide: true, title: 'Contributing', markdown: true }; +exports['docs/connections.md'] = { guide: true, title: 'Connecting to MongoDB', markdown: true }; +exports['docs/lambda.md'] = { guide: true, title: 'Using Mongoose With AWS Lambda', markdown: true }; +exports['docs/geojson.md'] = { guide: true, title: 'Using GeoJSON', acquit: true, markdown: true }; +exports['docs/transactions.md'] = { guide: true, title: 'Transactions', acquit: true, markdown: true }; +exports['docs/deprecations.md'] = { guide: true, title: 'Deprecation Warnings', markdown: true }; +exports['docs/further_reading.md'] = { title: 'Further Reading', markdown: true }; +exports['docs/jest.md'] = { title: 'Testing Mongoose with Jest', markdown: true }; +exports['docs/faq.md'] = { guide: true, title: 'FAQ', markdown: true }; +exports['docs/typescript.md'] = { guide: true, title: 'Using TypeScript with Mongoose', markdown: true }; +exports['docs/compatibility.md'] = { title: 'MongoDB Version Compatibility', - guide: true + guide: true, + markdown: true }; exports['docs/search.pug'] = { title: 'Search' }; -exports['docs/enterprise.pug'] = { title: 'Mongoose for Enterprise' }; -exports['docs/built-with-mongoose.pug'] = { title: 'Built with Mongoose' }; \ No newline at end of file +exports['docs/enterprise.md'] = { title: 'Mongoose for Enterprise', markdown: true }; +exports['docs/built-with-mongoose.md'] = { title: 'Built with Mongoose', markdown: true }; +exports['docs/async-await.md'] = { title: 'Using Async/Await with Mongoose', markdown: true }; \ No newline at end of file diff --git a/docs/source/production.js b/docs/source/production.js deleted file mode 100644 index 9a9cb59beb3..00000000000 --- a/docs/source/production.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; -module.exports = { - images: [] - , title: 'Production' -} diff --git a/docs/subdocs.html b/docs/subdocs.html deleted file mode 100644 index 1f790de50dc..00000000000 --- a/docs/subdocs.html +++ /dev/null @@ -1,227 +0,0 @@ -Mongoose v5.6.0: SubDocuments

    Subdocuments

    - - - - -

    Subdocuments are documents embedded in other documents. In Mongoose, this -means you can nest schemas in other schemas. Mongoose has two -distinct notions of subdocuments: arrays of subdocuments and single nested -subdocuments.

    -
    var childSchema = new Schema({ name: 'string' });
    -
    -var parentSchema = new Schema({
    -  // Array of subdocuments
    -  children: [childSchema],
    -  // Single nested subdocuments. Caveat: single nested subdocs only work
    -  // in mongoose >= 4.2.0
    -  child: childSchema
    -});
    - - -

    What is a Subdocument?

    -

    Subdocuments are similar to normal documents. Nested schemas can have -middleware, custom validation logic, -virtuals, and any other feature top-level schemas can use. The major -difference is that subdocuments are -not saved individually, they are saved whenever their top-level parent -document is saved.

    -
    var Parent = mongoose.model('Parent', parentSchema);
    -var parent = new Parent({ children: [{ name: 'Matt' }, { name: 'Sarah' }] })
    -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);
    -

    Subdocuments have save and validate middleware -just like top-level documents. Calling save() on the parent document triggers -the save() middleware for all its subdocuments, and the same for validate() -middleware.

    -
    childSchema.pre('save', function (next) {
    -  if ('invalid' == this.name) {
    -    return next(new Error('#sadpanda'));
    -  }
    -  next();
    -});
    -
    -var parent = new Parent({ children: [{ name: 'invalid' }] });
    -parent.save(function (err) {
    -  console.log(err.message) // #sadpanda
    -});
    -

    Subdocuments' pre('save') and pre('validate') middleware execute -before the top-level document's pre('save') but after the -top-level document's pre('validate') middleware. This is because validating -before save() is actually a piece of built-in middleware.

    -
    // Below code will print out 1-4 in order
    -var childSchema = new mongoose.Schema({ name: 'string' });
    -
    -childSchema.pre('validate', function(next) {
    -  console.log('2');
    -  next();
    -});
    -
    -childSchema.pre('save', function(next) {
    -  console.log('3');
    -  next();
    -});
    -
    -var parentSchema = new mongoose.Schema({
    -  child: childSchema,
    -    });
    -
    -parentSchema.pre('validate', function(next) {
    -  console.log('1');
    -  next();
    -});
    -
    -parentSchema.pre('save', function(next) {
    -  console.log('4');
    -  next();
    -});
    -

    Finding a Subdocument

    Each subdocument has an _id by default. Mongoose document arrays have a -special id method -for searching a document array to find a document with a given _id.

    -
    var doc = parent.children.id(_id);
    -

    Adding Subdocs to Arrays

    MongooseArray methods such as -push, -unshift, -addToSet, -and others cast arguments to their proper types transparently:

    -
    var Parent = mongoose.model('Parent');
    -var parent = new Parent;
    -
    -// create a comment
    -parent.children.push({ name: 'Liesl' });
    -var 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!');
    -});
    -

    Subdocs may also be created without adding them to the array by using the -create -method of MongooseArrays.

    -
    var newdoc = parent.children.create({ name: 'Aaron' });
    -

    Removing Subdocs

    Each subdocument has it's own -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.

    -
    // Equivalent to `parent.children.pull(_id)`
    -parent.children.id(_id).remove();
    -// Equivalent to `parent.child = null`
    -parent.child.remove();
    -parent.save(function (err) {
    -  if (err) return handleError(err);
    -  console.log('the subdocs were removed');
    -});
    -

    Parents of Subdocs

    Sometimes, you need to get the parent of a subdoc. You can access the -parent using the parent() function.

    -
    const schema = new Schema({
    -  docArr: [{ name: String }],
    -  singleNested: new Schema({ name: String })
    -});
    -const Model = mongoose.model('Test', schema);
    -
    -const doc = new Model({
    -  docArr: [{ name: 'foo' }],
    -  singleNested: { name: 'bar' }
    -});
    -
    -doc.singleNested.parent() === doc; // true
    -doc.docArr[0].parent() === doc; // true
    -

    If you have a deeply nested subdoc, you can access the top-level document -using the ownerDocument() function.

    -
    const schema = new Schema({
    -  level1: new Schema({
    -    level2: new Schema({
    -      test: String
    -    })
    -  })
    -});
    -const Model = mongoose.model('Test', schema);
    -
    -const doc = new Model({ level1: { level2: 'test' } });
    -
    -doc.level1.level2.parent() === doc; // false
    -doc.level1.level2.parent() === doc.level1; // true
    -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:

    -
    var parentSchema = new Schema({
    -  children: [{ name: 'string' }]
    -});
    -// Equivalent
    -var parentSchema = new Schema({
    -  children: [new Schema({ name: 'string' })]
    -});
    -

    Next Up

    Now that we've covered Subdocuments, let's take a look at -querying.

    -
    \ No newline at end of file diff --git a/docs/subdocs.md b/docs/subdocs.md new file mode 100644 index 00000000000..1b3d778c968 --- /dev/null +++ b/docs/subdocs.md @@ -0,0 +1,392 @@ +## Subdocuments + +Subdocuments are documents embedded in other documents. In Mongoose, this +means you can nest schemas in other schemas. Mongoose has two +distinct notions of subdocuments: [arrays of subdocuments](https://masteringjs.io/tutorials/mongoose/array#document-arrays) and single nested +subdocuments. + +```javascript +const childSchema = new Schema({ name: 'string' }); + +const parentSchema = new Schema({ + // Array of subdocuments + children: [childSchema], + // Single nested subdocuments. Caveat: single nested subdocs only work + // in mongoose >= 4.2.0 + child: childSchema +}); +``` + +Aside from code reuse, one important reason to use subdocuments is to create +a path where there would otherwise not be one to allow for validation over +a group of fields (e.g. dateRange.fromDate <= dateRange.toDate). + + + +### What is a Subdocument? + +Subdocuments are similar to normal documents. Nested schemas can have +[middleware](./middleware.html), [custom validation logic](./validation.html), +virtuals, and any other feature top-level schemas can use. The major +difference is that subdocuments are +not saved individually, they are saved whenever their top-level parent +document is saved. + +```javascript +const Parent = mongoose.model('Parent', parentSchema); +const parent = new Parent({ children: [{ name: 'Matt' }, { name: 'Sarah' }] }) +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); +``` + +Subdocuments have `save` and `validate` [middleware](./middleware.html) +just like top-level documents. Calling `save()` on the parent document triggers +the `save()` middleware for all its subdocuments, and the same for `validate()` +middleware. + +```javascript +childSchema.pre('save', function (next) { + if ('invalid' == this.name) { + return next(new Error('#sadpanda')); + } + next(); +}); + +const parent = new Parent({ children: [{ name: 'invalid' }] }); +parent.save(function (err) { + console.log(err.message) // #sadpanda +}); +``` + +Subdocuments' `pre('save')` and `pre('validate')` middleware execute +**before** the top-level document's `pre('save')` but **after** the +top-level document's `pre('validate')` middleware. This is because validating +before `save()` is actually a piece of built-in middleware. + +```javascript +// Below code will print out 1-4 in order +const childSchema = new mongoose.Schema({ name: 'string' }); + +childSchema.pre('validate', function(next) { + console.log('2'); + next(); +}); + +childSchema.pre('save', function(next) { + console.log('3'); + next(); +}); + +const parentSchema = new mongoose.Schema({ + child: childSchema +}); + +parentSchema.pre('validate', function(next) { + console.log('1'); + next(); +}); + +parentSchema.pre('save', function(next) { + console.log('4'); + next(); +}); +``` + +### Subdocuments versus Nested Paths + +In Mongoose, nested paths are subtly different from subdocuments. +For example, below are two schemas: one with `child` as a subdocument, +and one with `child` as a nested path. + +```javascript +// Subdocument +const subdocumentSchema = new mongoose.Schema({ + child: new mongoose.Schema({ name: String, age: Number }) +}); +const Subdoc = mongoose.model('Subdoc', subdocumentSchema); + +// Nested path +const nestedSchema = new mongoose.Schema({ + child: { name: String, age: Number } +}); +const Nested = mongoose.model('Nested', nestedSchema); +``` + +These two schemas look similar, and the documents in MongoDB will +have the same structure with both schemas. But there are a few +Mongoose-specific differences: + +First, instances of `Nested` never have `child === undefined`. +You can always set subproperties of `child`, even if you don't set +the `child` property. But instances of `Subdoc` can have `child === undefined`. + +```javascript +const doc1 = new Subdoc({}); +doc1.child === undefined; // true +doc1.child.name = 'test'; // Throws TypeError: cannot read property... + +const doc2 = new Nested({}); +doc2.child === undefined; // false +console.log(doc2.child); // Prints 'MongooseDocument { undefined }' +doc2.child.name = 'test'; // Works +``` + +Secondly, in Mongoose 5, [`Document#set()`](/docs/api/document.html#document_Document-set) +merges when you call it on a nested path, but overwrites when you call +it on a subdocument. + +```javascript +const doc1 = new Subdoc({ child: { name: 'Luke', age: 19 } }); +doc1.set({ child: { age: 21 } }); +doc1.child; // { age: 21 } + +const doc2 = new Nested({ child: { name: 'Luke', age: 19 } }); +doc2.set({ child: { age: 21 } }); +doc2.child; // { name: Luke, age: 21 } +``` + +### Subdocument Defaults + +Subdocument paths are undefined by default, and Mongoose does +not apply subdocument defaults unless you set the subdocument +path to a non-nullish value. + +```javascript +const subdocumentSchema = new mongoose.Schema({ + child: new mongoose.Schema({ + name: String, + age: { + type: Number, + default: 0 + } + }) +}); +const Subdoc = mongoose.model('Subdoc', subdocumentSchema); + +// Note that the `age` default has no effect, because `child` +// is `undefined`. +const doc = new Subdoc(); +doc.child; // undefined +``` + +However, if you set `doc.child` to any object, Mongoose will apply +the `age` default if necessary. + +```javascript +doc.child = {}; +// Mongoose applies the `age` default: +doc.child.age; // 0 +``` + +Mongoose applies defaults recursively, which means there's a nice +workaround if you want to make sure Mongoose applies subdocument +defaults: make the subdocument path default to an empty object. + +```javascript +const childSchema = new mongoose.Schema({ + name: String, + age: { + type: Number, + default: 0 + } +}); +const subdocumentSchema = new mongoose.Schema({ + child: { + type: childSchema, + default: () => ({}) + } +}); +const Subdoc = mongoose.model('Subdoc', subdocumentSchema); + +// Note that Mongoose sets `age` to its default value 0, because +// `child` defaults to an empty object and Mongoose applies +// defaults to that empty object. +const doc = new Subdoc(); +doc.child; // { age: 0 } +``` + +### Finding a Subdocument + +Each subdocument has an `_id` by default. Mongoose document arrays have a +special [id](./api.html#types_documentarray_MongooseDocumentArray-id) method +for searching a document array to find a document with a given `_id`. +```javascript +const doc = parent.children.id(_id); +``` + +### Adding Subdocs to Arrays + +MongooseArray methods such as +[push](./api.html#mongoosearray_MongooseArray-push), +[unshift](./api.html#mongoosearray_MongooseArray-unshift), +[addToSet](./api.html#mongoosearray_MongooseArray-addToSet), +and others cast arguments to their proper types transparently: +```javascript +const Parent = mongoose.model('Parent'); +const parent = new Parent; + +// create a comment +parent.children.push({ name: 'Liesl' }); +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!'); +}); +``` + +Subdocs may also be created without adding them to the array by using the +[create](./api.html#types_documentarray_MongooseDocumentArray.create) +method of MongooseArrays. + +```javascript +const newdoc = parent.children.create({ name: 'Aaron' }); +``` + +### Removing Subdocs + +Each subdocument has it's own +[remove](./api.html#types_embedded_EmbeddedDocument-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`. + +```javascript +// Equivalent to `parent.children.pull(_id)` +parent.children.id(_id).remove(); +// Equivalent to `parent.child = null` +parent.child.remove(); +parent.save(function (err) { + if (err) return handleError(err); + console.log('the subdocs were removed'); +}); +``` + +

    Parents of Subdocs

    + +Sometimes, you need to get the parent of a subdoc. You can access the +parent using the `parent()` function. + +```javascript +const schema = new Schema({ + docArr: [{ name: String }], + singleNested: new Schema({ name: String }) +}); +const Model = mongoose.model('Test', schema); + +const doc = new Model({ + docArr: [{ name: 'foo' }], + singleNested: { name: 'bar' } +}); + +doc.singleNested.parent() === doc; // true +doc.docArr[0].parent() === doc; // true +``` + +If you have a deeply nested subdoc, you can access the top-level document +using the `ownerDocument()` function. + +```javascript +const schema = new Schema({ + level1: new Schema({ + level2: new Schema({ + test: String + }) + }) +}); +const Model = mongoose.model('Test', schema); + +const doc = new Model({ level1: { level2: 'test' } }); + +doc.level1.level2.parent() === doc; // false +doc.level1.level2.parent() === doc.level1; // true +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: + +```javascript +const parentSchema = new Schema({ + children: [{ name: 'string' }] +}); +// Equivalent +const parentSchema = new Schema({ + children: [new Schema({ name: 'string' })] +}); +``` + +

    Alternate declaration syntax for single nested subdocuments

    + +Unlike document arrays, Mongoose 5 does not convert an objects in schemas +into nested schemas. In the below example, `nested` is a _nested path_ +rather than a subdocument. + +```javascript +const schema = new Schema({ + nested: { + prop: String + } +}); +``` + +This leads to some surprising behavior when you attempt to define a +nested path with validators or getters/setters. + +```javascript +const schema = new Schema({ + nested: { + // Do not do this! This makes `nested` a mixed path in Mongoose 5 + type: { prop: String }, + required: true + } +}); + +const schema = new Schema({ + nested: { + // This works correctly + type: new Schema({ prop: String }), + required: true + } +}); +``` + +Surprisingly, declaring `nested` with an object `type` makes `nested` +into a path of type [Mixed](/docs/schematypes.html#mixed). To instead +make Mongoose automatically convert `type: { prop: String }` into +`type: new Schema({ prop: String })`, set the `typePojoToMixed` option +to `false`. + +```javascript +const schema = new Schema({ + nested: { + // Because of `typePojoToMixed`, Mongoose knows to + // wrap `{ prop: String }` in a `new Schema()`. + type: { prop: String }, + required: true + } +}, { typePojoToMixed: false }); +``` + +### Next Up + +Now that we've covered Subdocuments, let's take a look at +[querying](./queries.html). diff --git a/docs/subdocs.pug b/docs/subdocs.pug deleted file mode 100644 index 25eac08bac4..00000000000 --- a/docs/subdocs.pug +++ /dev/null @@ -1,408 +0,0 @@ -extends layout - -append style - link(rel="stylesheet", href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown - ## Subdocuments - - - - - - Subdocuments are documents embedded in other documents. In Mongoose, this - means you can nest schemas in other schemas. Mongoose has two - distinct notions of subdocuments: [arrays of subdocuments](https://masteringjs.io/tutorials/mongoose/array#document-arrays) and single nested - subdocuments. - ```javascript - const childSchema = new Schema({ name: 'string' }); - - const parentSchema = new Schema({ - // Array of subdocuments - children: [childSchema], - // Single nested subdocuments. Caveat: single nested subdocs only work - // in mongoose >= 4.2.0 - child: childSchema - }); - ``` - Aside from code reuse, one important reason to use subdocuments is to create - a path where there would otherwise not be one to allow for validation over - a group of fields (e.g. dateRange.fromDate <= dateRange.toDate). - - :markdown - - - ### What is a Subdocument? - - Subdocuments are similar to normal documents. Nested schemas can have - [middleware](./middleware.html), [custom validation logic](./validation.html), - virtuals, and any other feature top-level schemas can use. The major - difference is that subdocuments are - not saved individually, they are saved whenever their top-level parent - document is saved. - ```javascript - const Parent = mongoose.model('Parent', parentSchema); - const parent = new Parent({ children: [{ name: 'Matt' }, { name: 'Sarah' }] }) - 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); - ``` - :markdown - Subdocuments have `save` and `validate` [middleware](./middleware.html) - just like top-level documents. Calling `save()` on the parent document triggers - the `save()` middleware for all its subdocuments, and the same for `validate()` - middleware. - - ```javascript - childSchema.pre('save', function (next) { - if ('invalid' == this.name) { - return next(new Error('#sadpanda')); - } - next(); - }); - - const parent = new Parent({ children: [{ name: 'invalid' }] }); - parent.save(function (err) { - console.log(err.message) // #sadpanda - }); - ``` - :markdown - Subdocuments' `pre('save')` and `pre('validate')` middleware execute - **before** the top-level document's `pre('save')` but **after** the - top-level document's `pre('validate')` middleware. This is because validating - before `save()` is actually a piece of built-in middleware. - - ```javascript - // Below code will print out 1-4 in order - const childSchema = new mongoose.Schema({ name: 'string' }); - - childSchema.pre('validate', function(next) { - console.log('2'); - next(); - }); - - childSchema.pre('save', function(next) { - console.log('3'); - next(); - }); - - const parentSchema = new mongoose.Schema({ - child: childSchema - }); - - parentSchema.pre('validate', function(next) { - console.log('1'); - next(); - }); - - parentSchema.pre('save', function(next) { - console.log('4'); - next(); - }); - ``` - - ### Subdocuments versus Nested Paths - - In Mongoose, nested paths are subtly different from subdocuments. - For example, below are two schemas: one with `child` as a subdocument, - and one with `child` as a nested path. - - ```javascript - // Subdocument - const subdocumentSchema = new mongoose.Schema({ - child: new mongoose.Schema({ name: String, age: Number }) - }); - const Subdoc = mongoose.model('Subdoc', subdocumentSchema); - - // Nested path - const nestedSchema = new mongoose.Schema({ - child: { name: String, age: Number } - }); - const Nested = mongoose.model('Nested', nestedSchema); - ``` - - These two schemas look similar, and the documents in MongoDB will - have the same structure with both schemas. But there are a few - Mongoose-specific differences: - - First, instances of `Nested` never have `child === undefined`. - You can always set subproperties of `child`, even if you don't set - the `child` property. But instances of `Subdoc` can have `child === undefined`. - - ```javascript - const doc1 = new Subdoc({}); - doc1.child === undefined; // true - doc1.child.name = 'test'; // Throws TypeError: cannot read property... - - const doc2 = new Nested({}); - doc2.child === undefined; // false - console.log(doc2.child); // Prints 'MongooseDocument { undefined }' - doc2.child.name = 'test'; // Works - ``` - - Secondly, in Mongoose 5, [`Document#set()`](/docs/api/document.html#document_Document-set) - merges when you call it on a nested path, but overwrites when you call - it on a subdocument. - - ```javascript - const doc1 = new Subdoc({ child: { name: 'Luke', age: 19 } }); - doc1.set({ child: { age: 21 } }); - doc1.child; // { age: 21 } - - const doc2 = new Nested({ child: { name: 'Luke', age: 19 } }); - doc2.set({ child: { age: 21 } }); - doc2.child; // { name: Luke, age: 21 } - ``` - - ### Subdocument Defaults - - Subdocument paths are undefined by default, and Mongoose does - not apply subdocument defaults unless you set the subdocument - path to a non-nullish value. - - ```javascript - const subdocumentSchema = new mongoose.Schema({ - child: new mongoose.Schema({ - name: String, - age: { - type: Number, - default: 0 - } - }) - }); - const Subdoc = mongoose.model('Subdoc', subdocumentSchema); - - // Note that the `age` default has no effect, because `child` - // is `undefined`. - const doc = new Subdoc(); - doc.child; // undefined - ``` - - However, if you set `doc.child` to any object, Mongoose will apply - the `age` default if necessary. - - ```javascript - doc.child = {}; - // Mongoose applies the `age` default: - doc.child.age; // 0 - ``` - - Mongoose applies defaults recursively, which means there's a nice - workaround if you want to make sure Mongoose applies subdocument - defaults: make the subdocument path default to an empty object. - - ```javascript - const childSchema = new mongoose.Schema({ - name: String, - age: { - type: Number, - default: 0 - } - }); - const subdocumentSchema = new mongoose.Schema({ - child: { - type: childSchema, - default: () => ({}) - } - }); - const Subdoc = mongoose.model('Subdoc', subdocumentSchema); - - // Note that Mongoose sets `age` to its default value 0, because - // `child` defaults to an empty object and Mongoose applies - // defaults to that empty object. - const doc = new Subdoc(); - doc.child; // { age: 0 } - ``` - - h3#finding-a-subdocument Finding a Subdocument - :markdown - Each subdocument has an `_id` by default. Mongoose document arrays have a - special [id](./api.html#types_documentarray_MongooseDocumentArray-id) method - for searching a document array to find a document with a given `_id`. - ```javascript - const doc = parent.children.id(_id); - ``` - h3#adding-subdocs-to-arrays Adding Subdocs to Arrays - :markdown - MongooseArray methods such as - [push](./api.html#mongoosearray_MongooseArray-push), - [unshift](./api.html#mongoosearray_MongooseArray-unshift), - [addToSet](./api.html#mongoosearray_MongooseArray-addToSet), - and others cast arguments to their proper types transparently: - ```javascript - const Parent = mongoose.model('Parent'); - const parent = new Parent; - - // create a comment - parent.children.push({ name: 'Liesl' }); - 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!'); - }); - ``` - :markdown - Subdocs may also be created without adding them to the array by using the - [create](./api.html#types_documentarray_MongooseDocumentArray.create) - method of MongooseArrays. - ```javascript - const newdoc = parent.children.create({ name: 'Aaron' }); - ``` - h3#removing-subdocs Removing Subdocs - :markdown - Each subdocument has it's own - [remove](./api.html#types_embedded_EmbeddedDocument-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`. - ```javascript - // Equivalent to `parent.children.pull(_id)` - parent.children.id(_id).remove(); - // Equivalent to `parent.child = null` - parent.child.remove(); - parent.save(function (err) { - if (err) return handleError(err); - console.log('the subdocs were removed'); - }); - ``` - - h3#subdoc-parents Parents of Subdocs - :markdown - Sometimes, you need to get the parent of a subdoc. You can access the - parent using the `parent()` function. - - ```javascript - const schema = new Schema({ - docArr: [{ name: String }], - singleNested: new Schema({ name: String }) - }); - const Model = mongoose.model('Test', schema); - - const doc = new Model({ - docArr: [{ name: 'foo' }], - singleNested: { name: 'bar' } - }); - - doc.singleNested.parent() === doc; // true - doc.docArr[0].parent() === doc; // true - ``` - - If you have a deeply nested subdoc, you can access the top-level document - using the `ownerDocument()` function. - - ```javascript - const schema = new Schema({ - level1: new Schema({ - level2: new Schema({ - test: String - }) - }) - }); - const Model = mongoose.model('Test', schema); - - const doc = new Model({ level1: { level2: 'test' } }); - - doc.level1.level2.parent() === doc; // false - doc.level1.level2.parent() === doc.level1; // true - doc.level1.level2.ownerDocument() === doc; // true - ``` - - h4#altsyntaxarrays Alternate declaration syntax for arrays - :markdown - 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({ - children: [{ name: 'string' }] - }); - // Equivalent - const parentSchema = new Schema({ - children: [new Schema({ name: 'string' })] - }); - ``` - - h4#altsyntaxsingle Alternate declaration syntax for single nested subdocuments - :markdown - Unlike document arrays, Mongoose 5 does not convert an objects in schemas - into nested schemas. In the below example, `nested` is a _nested path_ - rather than a subdocument. - - ```javascript - const schema = new Schema({ - nested: { - prop: String - } - }); - ``` - - This leads to some surprising behavior when you attempt to define a - nested path with validators or getters/setters. - - ```javascript - const schema = new Schema({ - nested: { - // Do not do this! This makes `nested` a mixed path in Mongoose 5 - type: { prop: String }, - required: true - } - }); - - const schema = new Schema({ - nested: { - // This works correctly - type: new Schema({ prop: String }), - required: true - } - }); - ``` - - Surprisingly, declaring `nested` with an object `type` makes `nested` - into a path of type [Mixed](/docs/schematypes.html#mixed). To instead - make Mongoose automatically convert `type: { prop: String }` into - `type: new Schema({ prop: String })`, set the `typePojoToMixed` option - to `false`. - - ```javascript - const schema = new Schema({ - nested: { - // Because of `typePojoToMixed`, Mongoose knows to - // wrap `{ prop: String }` in a `new Schema()`. - type: { prop: String }, - required: true - } - }, { typePojoToMixed: false }); - ``` - - h3#next Next Up - :markdown - Now that we've covered Subdocuments, let's take a look at - [querying](./queries.html). diff --git a/docs/transactions.html b/docs/transactions.html deleted file mode 100644 index 32dc8227626..00000000000 --- a/docs/transactions.html +++ /dev/null @@ -1,217 +0,0 @@ -Mongoose v5.6.0: Transactions

    Transactions in Mongoose

    - - - - -

    Transactions are new in MongoDB -4.0 and Mongoose 5.2.0. Transactions let you execute multiple operations -in isolation and potentially undo all the operations if one of them fails. -This guide will get you started using transactions with Mongoose.

    -

    Your First Transaction

    -

    MongoDB currently only supports transactions on replica sets, -not standalone servers. To run a -local replica set for development -on macOS, Linux or Windows, use npm to install -run-rs globally and run -run-rs --version 4.0.0. Run-rs will download MongoDB 4.0.0 for you.

    -

    To use transactions with Mongoose, you should use Mongoose >= 5.2.0. To -check your current version of Mongoose, run npm list | grep "mongoose" -or check the mongoose.version property.

    -

    Transactions are built on MongoDB sessions. -To start a transaction, you first need to call startSession() -and then call the session's startTransaction() function. To execute an -operation in a transaction, you need to pass the session as an option.

    -
    const Customer = db.model('Customer', new Schema({ name: String }));
    -
    -let session = null;
    -return Customer.createCollection().
    -  then(() => db.startSession()).
    -  then(_session => {
    -    session = _session;
    -    // Start a transaction
    -    session.startTransaction();
    -    // This `create()` is part of the transaction because of the `session`
    -    // option.
    -    return Customer.create([{ name: 'Test' }], { session: session });
    -  }).
    -  // Transactions execute in isolation, so unless you pass a `session`
    -  // to `findOne()` you won't see the document until the transaction
    -  // is committed.
    -  then(() => Customer.findOne({ name: 'Test' })).
    -  then(doc => assert.ok(!doc)).
    -  // This `findOne()` will return the doc, because passing the `session`
    -  // means this `findOne()` will run as part of the transaction.
    -  then(() => Customer.findOne({ name: 'Test' }).session(session)).
    -  then(doc => assert.ok(doc)).
    -  // Once the transaction is committed, the write operation becomes
    -  // visible outside of the transaction.
    -  then(() => session.commitTransaction()).
    -  then(() => Customer.findOne({ name: 'Test' })).
    -  then(doc => assert.ok(doc));
    -

    Aborting a Transaction

    -

    The most important feature of transactions is the ability to roll back all -operations in the transaction using the abortTransaction() function.

    -

    Think about modeling a bank account in Mongoose. -To transfer money from account A to account B, you would decrement -A's balance and increment B's balance. However, if A only has a balance -of $5 and you try to transfer $10, you want to abort the transaction and undo -incrementing B's balance.

    -
    let session = null;
    -return Customer.createCollection().
    -  then(() => Customer.startSession()).
    -  then(_session => {
    -    session = _session;
    -    session.startTransaction();
    -    return Customer.create([{ name: 'Test' }], { session: session });
    -  }).
    -  then(() => Customer.create([{ name: 'Test2' }], { session: session })).
    -  then(() => session.abortTransaction()).
    -  then(() => Customer.countDocuments()).
    -  then(count => assert.strictEqual(count, 0));
    -

    The withTransaction() Helper

    -

    The previous examples explicitly create a transaction and commits it. In -practice, you'll want to use the session.withTransaction() helper -instead. The session.withTransaction() helper handles creating a -transaction, committing the transaction if it succeeds, aborting the transaction -if your operation throws. It also handles transient transaction errors.

    -
    return Customer.createCollection().
    -  then(() => Customer.startSession()).
    -  // The `withTransaction()` function's first parameter is a function
    -  // that returns a promise.
    -  then(session => session.withTransaction(() => {
    -    return Customer.create([{ name: 'Test' }], { session: session });
    -  })).
    -  then(() => Customer.countDocuments()).
    -  then(count => assert.strictEqual(count, 1));
    -

    With Mongoose Documents and save()

    -

    If you get a Mongoose document from findOne() -or find() using a session, the document will -keep a reference to the session and use that session for save().

    -

    To get/set the session associated with a given document, use doc.$session().

    -
    const User = db.model('User', new Schema({ name: String }));
    -
    -let session = null;
    -return User.createCollection().
    -  then(() => db.startSession()).
    -  then(_session => {
    -    session = _session;
    -    return User.create({ name: 'foo' });
    -  }).
    -  then(() => {
    -    session.startTransaction();
    -    return User.findOne({ name: 'foo' }).session(session);
    -  }).
    -  then(user => {
    -    // Getter/setter for the session associated with this document.
    -    assert.ok(user.$session());
    -    user.name = 'bar';
    -    // By default, `save()` uses the associated session
    -    return user.save();
    -  }).
    -  then(() => User.findOne({ name: 'bar' })).
    -  // Won't find the doc because `save()` is part of an uncommitted transaction
    -  then(doc => assert.ok(!doc)).
    -  then(() => {
    -    session.commitTransaction();
    -    return User.findOne({ name: 'bar' });
    -  }).
    -  then(doc => assert.ok(doc));
    -

    With the Aggregation Framework

    -

    The Model.aggregate() function also supports transactions. Mongoose -aggregations have a session() helper -that sets the session option. -Below is an example of executing an aggregation within a transaction.

    -
    const Event = db.model('Event', new Schema({ createdAt: Date }), 'Event');
    -
    -let session = null;
    -return Event.createCollection().
    -  then(() => db.startSession()).
    -  then(_session => {
    -    session = _session;
    -    session.startTransaction();
    -    return Event.insertMany([
    -      { createdAt: new Date('2018-06-01') },
    -      { createdAt: new Date('2018-06-02') },
    -      { createdAt: new Date('2017-06-01') },
    -      { createdAt: new Date('2017-05-31') }
    -    ], { session: session });
    -  }).
    -  then(() => Event.aggregate([
    -    {
    -      $group: {
    -        _id: {
    -          month: { $month: '$createdAt' },
    -          year: { $year: '$createdAt' }
    -        },
    -        count: { $sum: 1 }
    -      }
    -    },
    -    { $sort: { count: -1, '_id.year': -1, '_id.month': -1 } }
    -  ]).session(session)).
    -  then(res => {
    -    assert.deepEqual(res, [
    -      { _id: { month: 6, year: 2018 }, count: 2 },
    -      { _id: { month: 6, year: 2017 }, count: 1 },
    -      { _id: { month: 5, year: 2017 }, count: 1 }
    -    ]);
    -    session.commitTransaction();
    -  });
    -
    \ No newline at end of file diff --git a/docs/transactions.md b/docs/transactions.md new file mode 100644 index 00000000000..9fe2d5129db --- /dev/null +++ b/docs/transactions.md @@ -0,0 +1,86 @@ +# Transactions in Mongoose + +[Transactions](https://www.mongodb.com/transactions) are new in MongoDB +4.0 and Mongoose 5.2.0. Transactions let you execute multiple operations +in isolation and potentially undo all the operations if one of them fails. +This guide will get you started using transactions with Mongoose. + +

    Getting Started with Transactions

    + +If you haven't already, import mongoose: +```javascript +import mongoose from 'mongoose'; +``` + +To create a transaction, you first need to create a session using or [`Mongoose#startSession`](/docs/api/mongoose.html#mongoose_Mongoose-startSession) +or [`Connection#startSession()`](/docs/api/connection.html#connection_Connection-startSession). + +```javascript +// Using Mongoose's default connection +const session = await mongoose.startSession(); + +// Using custom connection +const db = await mongoose.createConnection(mongodbUri, { useUnifiedTopology: true, useNewUrlParser: true }); +const session = await db.startSession(); +``` + +In practice, you should use either the [`session.withTransaction()` helper](https://mongodb.github.io/node-mongodb-native/3.2/api/ClientSession.html#withTransaction) +or Mongoose's `Connection#transaction()` function to run a transaction. The `session.withTransaction()` helper handles: + +- Creating a transaction +- Committing the transaction if it succeeds +- Aborting the transaction if your operation throws +- Retrying in the event of a [transient transaction error](https://stackoverflow.com/questions/52153538/what-is-a-transienttransactionerror-in-mongoose-or-mongodb). + +```javascript +[require:transactions.*withTransaction] +``` + +For more information on the `ClientSession#withTransaction()` function, please see +[the MongoDB Node.js driver docs](https://mongodb.github.io/node-mongodb-native/3.2/api/ClientSession.html#withTransaction). + +Mongoose's `Connection#transaction()` function is a wrapper around `withTransaction()` that +integrates Mongoose change tracking with transactions. For example, the `Connection#transaction()` +function handles resetting a document if you `save()` that document in a transaction that later fails. + +```javascript +[require:transactions.*can save document after aborted transaction] +``` + +

    With Mongoose Documents and save()

    + +If you get a [Mongoose document](/docs/documents.html) from [`findOne()`](/docs/api.html#findone_findOne) +or [`find()`](/docs/api.html#find_find) using a session, the document will +keep a reference to the session and use that session for [`save()`](/docs/api.html#document_Document-save). + +To get/set the session associated with a given document, use [`doc.$session()`](/docs/api.html#document_Document-$session). + +```javascript +[require:transactions.*save] +``` + +

    With the Aggregation Framework

    + +The `Model.aggregate()` function also supports transactions. Mongoose +aggregations have a [`session()` helper](/docs/api.html#aggregate_Aggregate-session) +that sets the [`session` option](/docs/api.html#aggregate_Aggregate-option). +Below is an example of executing an aggregation within a transaction. + +```javascript +[require:transactions.*aggregate] +``` + +

    Advanced Usage

    + +Advanced users who want more fine-grained control over when they commit or abort transactions +can use `session.startTransaction()` to start a transaction: + +```javascript +[require:transactions.*basic example] +``` + +You can also use `session.abortTransaction()` to abort a transaction: + +```javascript +[require:transactions.*abort] +``` diff --git a/docs/transactions.pug b/docs/transactions.pug deleted file mode 100644 index 0aab8125ec8..00000000000 --- a/docs/transactions.pug +++ /dev/null @@ -1,111 +0,0 @@ -extends layout - -block append style - link(rel="stylesheet" href="/docs/css/inlinecpc.css") - script(type="text/javascript" src="/docs/js/native.js") - -block content - - - - - :markdown - # Transactions in Mongoose - - - - - - [Transactions](https://www.mongodb.com/transactions) are new in MongoDB - 4.0 and Mongoose 5.2.0. Transactions let you execute multiple operations - in isolation and potentially undo all the operations if one of them fails. - This guide will get you started using transactions with Mongoose. - - ## Your First Transaction - - [MongoDB currently only supports transactions on replica sets](https://docs.mongodb.com/manual/replication/#transactions), - not standalone servers. To run a - [local replica set for development](http://thecodebarbarian.com/introducing-run-rs-zero-config-mongodb-runner.html) - on macOS, Linux or Windows, use npm to install - [run-rs](https://www.npmjs.com/package/run-rs) globally and run - `run-rs --version 4.0.0`. Run-rs will download MongoDB 4.0.0 for you. - - To use transactions with Mongoose, you should use Mongoose `>= 5.2.0`. To - check your current version of Mongoose, run `npm list | grep "mongoose"` - or check the [`mongoose.version` property](http://mongoosejs.com/docs/api.html#mongoose_Mongoose-version). - - Transactions are built on [MongoDB sessions](https://docs.mongodb.com/manual/reference/server-sessions/). - To start a transaction, you first need to call [`startSession()`](/docs/api.html#startsession_startSession) - and then call the session's `startTransaction()` function. To execute an - operation in a transaction, you need to pass the `session` as an option. - - ```javascript - [require:transactions.*basic example] - ``` - - In the above example, `session` is an instance of the - [MongoDB Node.js driver's `ClientSession` class](https://mongodb.github.io/node-mongodb-native/3.2/api/ClientSession.html). - Please refer to the [MongoDB driver docs](https://mongodb.github.io/node-mongodb-native/3.2/api/ClientSession.html) - for more information on what methods `session` has. - - ## Aborting a Transaction - - The most important feature of transactions is the ability to roll back _all_ - operations in the transaction using the [`abortTransaction()` function](https://docs.mongodb.com/manual/reference/method/Session.abortTransaction/). - - Think about [modeling a bank account in Mongoose](https://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html#transactions-with-mongoose). - To transfer money from account `A` to account `B`, you would decrement - `A`'s balance and increment `B`'s balance. However, if `A` only has a balance - of $5 and you try to transfer $10, you want to abort the transaction and undo - incrementing `B`'s balance. - - ```javascript - [require:transactions.*abort] - ``` - - ## The `withTransaction()` Helper - - The previous examples explicitly create a transaction and commits it. In - practice, you'll want to use the [`session.withTransaction()` helper](https://mongodb.github.io/node-mongodb-native/3.2/api/ClientSession.html#withTransaction) - instead. The `session.withTransaction()` helper handles: - - - Creating a transaction - - Committing the transaction if it succeeds - - Aborting the transaction if your operation throws - - Retrying in the event of a [transient transaction error](https://stackoverflow.com/questions/52153538/what-is-a-transienttransactionerror-in-mongoose-or-mongodb). - - ```javascript - [require:transactions.*withTransaction] - ``` - - For more information on the `ClientSession#withTransaction()` function, please see - [the MongoDB Node.js driver docs](https://mongodb.github.io/node-mongodb-native/3.2/api/ClientSession.html#withTransaction). - - ## With Mongoose Documents and `save()` - - If you get a [Mongoose document](/docs/documents.html) from [`findOne()`](/docs/api.html#findone_findOne) - or [`find()`](/docs/api.html#find_find) using a session, the document will - keep a reference to the session and use that session for [`save()`](/docs/api.html#document_Document-save). - - To get/set the session associated with a given document, use [`doc.$session()`](/docs/api.html#document_Document-$session). - - ```javascript - [require:transactions.*save] - ``` - - ## With the Aggregation Framework - - The `Model.aggregate()` function also supports transactions. Mongoose - aggregations have a [`session()` helper](/docs/api.html#aggregate_Aggregate-session) - that sets the [`session` option](/docs/api.html#aggregate_Aggregate-option). - Below is an example of executing an aggregation within a transaction. - - ```javascript - [require:transactions.*aggregate] - ``` diff --git a/docs/tutorials/custom-casting.html b/docs/tutorials/custom-casting.html deleted file mode 100644 index 627dfd87cf3..00000000000 --- a/docs/tutorials/custom-casting.html +++ /dev/null @@ -1,102 +0,0 @@ -Mongoose v5.6.4-pre: Mongoose Tutorials: Custom Casting

    Custom Casting

    - - - - -

    Mongoose 5.4.0 introduced several ways to configure SchemaTypes globally. One of these new features is the SchemaType.cast() function, which enables you to override Mongoose's built-in casting.

    -

    For example, by default Mongoose will throw an error if you attempt to cast -a string that contains a Japanese numeral to a number.

    -
    const schema = new mongoose.Schema({
    -  age: Number
    -});
    -const Model = mongoose.model('Test', schema);
    -
    -const doc = new Model({ age: '二' });
    -const err = doc.validateSync();
    -// "Cast to Number failed for value "二" at path "age""
    -err.message;
    -

    You can overwrite the default casting function for numbers to allow converting -the string that contains the Japanese numeral "2" to a number as shown below.

    -
    // Calling `cast()` on a class that inherits from `SchemaType` returns the
    -// current casting function.
    -const originalCast = mongoose.Number.cast();
    -
    -// Calling `cast()` with a function sets the current function used to
    -// cast a given schema type, in this cast Numbers.
    -mongoose.Number.cast(v => {
    -  if (v === '二') {
    -    return 2;
    -  }
    -  return originalCast(v);
    -});
    -
    -const schema = new mongoose.Schema({
    -  age: Number
    -});
    -
    -const Model = mongoose.model('Test', schema);
    -
    -const doc = new Model({ age: '二' });
    -const err = doc.validateSync();
    -err; // null
    -doc.age; // 2
    -
    \ No newline at end of file diff --git a/docs/tutorials/dates.html b/docs/tutorials/dates.html deleted file mode 100644 index fe8cb0f9a77..00000000000 --- a/docs/tutorials/dates.html +++ /dev/null @@ -1,159 +0,0 @@ -Mongoose v5.6.4-pre: Mongoose Tutorials: Working With Dates

    Working With Dates

    - - - - -

    Here's how you declare a path of type Date with a Mongoose schema:

    -
    const mongoose = require('mongoose');
    -
    -const userSchema = new mongoose.Schema({
    -  name: String,
    -  // `lastActiveAt` is a date
    -  lastActiveAt: Date
    -});
    -const User = mongoose.model('User', userSchema);
    -

    When you create a user document, Mongoose will cast -the value to a native JavaScript date -using the Date() constructor.

    -
    const user = new User({
    -  name: 'Jean-Luc Picard',
    -  lastActiveAt: '2002-12-09'
    -});
    -user.lastActiveAt instanceof Date; // true
    -

    An invalid date will lead to a CastError when you validate the document.

    -
    const user = new User({
    -  name: 'Jean-Luc Picard',
    -  lastActiveAt: 'not a date'
    -});
    -user.lastActiveAt instanceof Date; // false
    -user.validateSync().errors['lastActiveAt']; // CastError
    -

    Validators

    -

    Dates have two built-in validators: min and max. These validators will -report a ValidatorError if the given date is strictly less than min or -strictly greater than max.

    -
    const episodeSchema = new mongoose.Schema({
    -  title: String,
    -  airedAt: {
    -    type: Date,
    -    // The dates of the first and last episodes of
    -    // Star Trek: The Next Generation
    -    min: '1987-09-28',
    -    max: '1994-05-23'
    -  }
    -});
    -const Episode = mongoose.model('Episode', episodeSchema);
    -
    -const ok = new Episode({
    -  title: 'Encounter at Farpoint',
    -  airedAt: '1987-09-28'
    -});
    -ok.validateSync(); // No error
    -
    -const bad = new Episode({
    -  title: 'What You Leave Behind',
    -  airedAt: '1999-06-02'
    -});
    -bad.airedAt; // "1999-06-02T00:00:00.000Z"
    -
    -// Path `airedAt` (Tue Jun 01 1999 20:00:00 GMT-0400 (EDT)) is after
    -// maximum allowed value (Sun May 22 1994 20:00:00 GMT-0400 (EDT)).
    -bad.validateSync();
    -

    Querying

    -

    MongoDB supports querying by date ranges and sorting by dates. Here's some -examples of querying by dates, date ranges, and sorting by date:

    -
    // Find episodes that aired on this exact date
    -return Episode.find({ airedAt: new Date('1987-10-26') }).
    -  then(episodes => {
    -    episodes[0].title; // "Where No One Has Gone Before"
    -    // Find episodes within a range of dates, sorted by date ascending
    -    return Episode.
    -      find({ airedAt: { $gte: '1987-10-19', $lte: '1987-10-26' } }).
    -      sort({ airedAt: 1 });
    -  }).
    -  then(episodes => {
    -    episodes[0].title; // "The Last Outpost"
    -    episodes[1].title; // "Where No One Has Gone Before"
    -  });
    -

    Casting Edge Cases

    -

    Date casting has a couple small cases where it differs from JavaScript's -native date parsing. First, Mongoose looks for a valueOf() function on the given object, -and calls valueOf() before casting the date. This means Mongoose can cast -moment objects to dates automatically.

    -
    const moment = require('moment');
    -const user = new User({
    -  name: 'Jean-Luc Picard',
    -  lastActiveAt: moment.utc('2002-12-09')
    -});
    -user.lastActiveAt; // "2002-12-09T00:00:00.000Z"
    -

    By default, if you pass a numeric -string to the Date constructor, JavaScript will attempt to convert it to a -year.

    -
    new Date(1552261496289); // "2019-03-10T23:44:56.289Z"
    -new Date('1552261496289'); // "Invalid Date"
    -new Date('2010'); // 2010-01-01T00:00:00.000Z
    -

    Mongoose converts numeric strings that contain numbers outside the range of representable dates in JavaScript and converts them to numbers before passing them to the date constructor.

    -
    require: Date Tutorial.*Example 1.4.3]
    -

    Timezones

    -

    MongoDB stores dates as 64-bit integers, which -means that Mongoose does not store timezone information by default. When -you call Date#toString(), the JavaScript runtime will use your OS' timezone.

    -
    \ No newline at end of file diff --git a/docs/tutorials/findoneandupdate.html b/docs/tutorials/findoneandupdate.html deleted file mode 100644 index 12804c5d9d0..00000000000 --- a/docs/tutorials/findoneandupdate.html +++ /dev/null @@ -1,136 +0,0 @@ -Mongoose v5.6.4-pre: Mongoose Tutorials: How to Use `findOneAndUpdate()` in Mongoose

    How to Use findOneAndUpdate() in Mongoose

    - - - - -

    The findOneAndUpdate() function in Mongoose has a wide variety of use cases. You should use save() to update documents where possible, but there are some cases where you need to use findOneAndUpdate(). In this tutorial, you'll see how to use findOneAndUpdate(), and learn when you need to use it.

    - -

    Getting Started

    - -

    As the name implies, findOneAndUpdate() finds the first document that matches a given filter, applies an update, and returns the document. By default, findOneAndUpdate() returns the document as it was before update was applied.

    -
    const Character = mongoose.model('Character', new mongoose.Schema({
    -  name: String,
    -  age: Number
    -}));
    -
    -await Character.create({ name: 'Jean-Luc Picard' });
    -
    -const filter = { name: 'Jean-Luc Picard' };
    -const update = { age: 59 };
    -
    -// `doc` is the document _before_ `update` was applied
    -let doc = await Character.findOneAndUpdate(filter, update);
    -doc.name; // 'Jean-Luc Picard'
    -doc.age; // undefined
    -
    -doc = await Character.findOne(filter);
    -doc.age; // 59
    -

    You should set the new option to true to return the document after update was applied.

    -
    const filter = { name: 'Jean-Luc Picard' };
    -const update = { age: 59 };
    -
    -// `doc` is the document _after_ `update` was applied because of
    -// `new: true`
    -let doc = await Character.findOneAndUpdate(filter, update, {
    -  new: true
    -});
    -doc.name; // 'Jean-Luc Picard'
    -doc.age; // 59
    -

    Mongoose's findOneAndUpdate() is slightly different from the MongoDB Node.js driver's findOneAndUpdate() because it returns the document itself, not a result object.

    -

    Atomic Updates

    - -

    With the exception of an unindexed upsert, findOneAndUpdate() is atomic. That means you can assume the document doesn't change between when MongoDB finds the document and when it updates the document, unless you're doing an upsert.

    -

    For example, if you're using save() to update a document, the document can change in MongoDB in between when you load the document using findOne() and when you save the document using save() as show below. For many use cases, the save() race condition is a non-issue. But you can work around it with findOneAndUpdate() (or transactions) if you need to.

    -
    const filter = { name: 'Jean-Luc Picard' };
    -const update = { age: 59 };
    -
    -let doc = await Character.findOne({ name: 'Jean-Luc Picard' });
    -
    -// Document changed in MongoDB, but not in Mongoose
    -await Character.updateOne(filter, { name: 'Will Riker' });
    -
    -// This will update `doc` age to `59`, even though the doc changed.
    -doc.age = 59;
    -await doc.save();
    -
    -doc = await Character.findOne();
    -doc.name; // Will Riker
    -doc.age; // 59
    -

    Upsert

    - -

    Using the upsert option, you can use findOneAndUpdate() as a find-and-upsert operation. An upsert behaves like a normal findOneAndUpdate() if it finds a document that matches filter. But, if no document matches filter, MongoDB will insert one by combining filter and update as shown below.

    -
    const filter = { name: 'Will Riker' };
    -const update = { age: 29 };
    -
    -await Character.countDocuments(filter); // 0
    -
    -let doc = await Character.findOneAndUpdate(filter, update, {
    -  new: true,
    -  upsert: true // Make this update into an upsert
    -});
    -doc.name; // Will Riker
    -doc.age; // 29
    -
    \ No newline at end of file diff --git a/docs/tutorials/findoneandupdate.md b/docs/tutorials/findoneandupdate.md index 83ab21a9c36..48f997231fe 100644 --- a/docs/tutorials/findoneandupdate.md +++ b/docs/tutorials/findoneandupdate.md @@ -34,7 +34,7 @@ which has the same option.

    Atomic Updates

    -With the exception of an [unindexed upsert](https://docs.mongodb.com/manual/reference/method/db.collection.findAndModify/#upsert-and-unique-index), [`findOneAndUpdate()` is atomic](https://docs.mongodb.com/manual/core/write-operations-atomicity/#atomicity). That means you can assume the document doesn't change between when MongoDB finds the document and when it updates the document, _unless_ you're doing an [upsert](#upsert). +With the exception of an [unindexed upsert](https://docs.mongodb.com/manual/reference/method/db.collection.findAndModify/#upsert-with-unique-index), [`findOneAndUpdate()` is atomic](https://docs.mongodb.com/manual/core/write-operations-atomicity/#atomicity). That means you can assume the document doesn't change between when MongoDB finds the document and when it updates the document, _unless_ you're doing an [upsert](#upsert). For example, if you're using `save()` to update a document, the document can change in MongoDB in between when you load the document using `findOne()` and when you save the document using `save()` as show below. For many use cases, the `save()` race condition is a non-issue. But you can work around it with `findOneAndUpdate()` (or [transactions](/docs/transactions.html)) if you need to. diff --git a/docs/tutorials/getters-setters.html b/docs/tutorials/getters-setters.html deleted file mode 100644 index 09ed59c79f5..00000000000 --- a/docs/tutorials/getters-setters.html +++ /dev/null @@ -1,181 +0,0 @@ -Mongoose v5.6.4-pre: Mongoose Tutorials: Getters/Setters in Mongoose

    Getters/Setters in Mongoose

    - - - - -

    Mongoose getters and setters allow you to execute custom logic when getting or setting a property on a Mongoose document. Getters let you transform data in MongoDB into a more user friendly form, and setters let you transform user data before it gets to MongoDB.

    -

    Getters

    -

    Suppose you have a User collection and you want to obfuscate user emails to protect your users' privacy. Below is a basic userSchema that obfuscates the user's email address.

    -
    const userSchema = new Schema({
    -  email: {
    -    type: String,
    -    get: obfuscate
    -  }
    -});
    -
    -// Mongoose passes the raw value in MongoDB `email` to the getter
    -function obfuscate(email) {
    -  const separatorIndex = email.indexOf('@');
    -  if (separatorIndex < 3) {
    -    // 'ab@gmail.com' -> '**@gmail.com'
    -    return email.slice(0, separatorIndex).replace(/./g, '*') +
    -      email.slice(separatorIndex);
    -  }
    -  // 'test42@gmail.com' -> 'te****@gmail.com'
    -  return email.slice(0, 2) +
    -    email.slice(2, separatorIndex).replace(/./g, '*') +
    -    email.slice(separatorIndex);
    -}
    -
    -const User = mongoose.model('User', userSchema);
    -const user = new User({ email: 'ab@gmail.com' });
    -user.email; // **@gmail.com
    -

    Keep in mind that getters do not impact the underlying data stored in -MongoDB. If you save user, the email property will be 'ab@gmail.com' in -the database.

    -

    By default, Mongoose executes getters when converting a document to JSON, -including Express' res.json() function.

    -
    app.get(function(req, res) {
    -  return User.findOne().
    -    // The `email` getter will run here
    -    then(doc => res.json(doc)).
    -    catch(err => res.status(500).json({ message: err.message }));
    -});
    -

    To disable running getters when converting a document to JSON, set the toJSON.getters option to false in your schema as shown below.

    -
    const userSchema = new Schema({
    -  email: {
    -    type: String,
    -    get: obfuscate
    -  }
    -}, { toJSON: { getters: false } });
    -

    To skip getters on a one-off basis, use user.get() with the getters option set to false as shown below.

    -
    user.get('email', null, { getters: false }); // 'ab@gmail.com'
    -

    Setters

    -

    Suppose you want to make sure all user emails in your database are lowercased to -make it easy to search without worrying about case. Below is an example -userSchema that ensures emails are lowercased.

    -
    const userSchema = new Schema({
    -  email: {
    -    type: String,
    -    set: v => v.toLowerCase()
    -  }
    -});
    -
    -const User = mongoose.model('User', userSchema);
    -
    -const user = new User({ email: 'TEST@gmail.com' });
    -user.email; // 'test@gmail.com'
    -
    -// The raw value of `email` is lowercased
    -user.get('email', null, { getters: false }); // 'test@gmail.com'
    -
    -user.set({ email: 'NEW@gmail.com' });
    -user.email; // 'new@gmail.com'
    -

    Mongoose also runs setters on update operations, like updateOne(). Mongoose will -upsert a document with a -lowercased email in the below example.

    -
    await User.updateOne({}, { email: 'TEST@gmail.com' }, { upsert: true });
    -
    -const doc = await User.findOne();
    -doc.email; // 'test@gmail.com'
    -

    In a setter function, this can be either the document being set or the query -being run. If you don't want your setter to run when you call updateOne(), -you add an if statement that checks if this is a Mongoose document as shown -below.

    -
    const userSchema = new Schema({
    -  email: {
    -    type: String,
    -    set: toLower
    -  }
    -});
    -
    -function toLower(email) {
    -  // Don't transform `email` if using `updateOne()` or `updateMany()`
    -  if (!(this instanceof mongoose.Document)) {
    -    return email;
    -  }
    -  return email.toLowerCase();
    -}
    -
    -const User = mongoose.model('User', userSchema);
    -await User.updateOne({}, { email: 'TEST@gmail.com' }, { upsert: true });
    -
    -const doc = await User.findOne();
    -doc.email; // 'TEST@gmail.com'
    -

    Differences vs ES6 Getters/Setters

    -

    Mongoose setters are different from ES6 setters because they allow you to transform the value being set. With ES6 setters, you -would need to store an internal _email property to use a setter. With Mongoose, -you do not need to define an internal _email property or define a -corresponding getter for email.

    -
    class User {
    -  // This won't convert the email to lowercase! That's because `email`
    -  // is just a setter, the actual `email` property doesn't store any data.
    -  set email(v) {
    -    return v.toLowerCase();
    -  }
    -}
    -
    -const user = new User();
    -user.email = 'TEST@gmail.com';
    -
    -user.email; // undefined
    -
    \ No newline at end of file diff --git a/docs/tutorials/lean.html b/docs/tutorials/lean.html deleted file mode 100644 index 8e349b60e9e..00000000000 --- a/docs/tutorials/lean.html +++ /dev/null @@ -1,261 +0,0 @@ -Mongoose v5.6.4-pre: Mongoose Tutorials: Faster Mongoose Queries With Lean

    Faster Mongoose Queries With Lean

    - - - - -

    The lean option tells Mongoose to skip -hydrating the result documents. This -makes queries faster and less memory intensive, but the result documents are -plain old JavaScript objects (POJOs), not Mongoose documents. -In this tutorial, you'll learn more about the tradeoffs of using lean().

    - -

    Using Lean

    - -

    By default, Mongoose queries return an instance of the -Mongoose Document class. Documents are much -heavier than vanilla JavaScript objects, because they have a lot of internal -state for change tracking. Enabling the lean option tells Mongoose to skip -instantiating a full Mongoose document and just give you the POJO.

    -
    const leanDoc = await MyModel.findOne().lean();
    -

    How much smaller are lean documents? Here's a comparison.

    -
    const schema = new mongoose.Schema({ name: String });
    -const MyModel = mongoose.model('Test', schema);
    -
    -await MyModel.create({ name: 'test' });
    -
    -// Module that estimates the size of an object in memory
    -const sizeof = require('object-sizeof');
    -
    -const normalDoc = await MyModel.findOne();
    -// To enable the `lean` option for a query, use the `lean()` function.
    -const leanDoc = await MyModel.findOne().lean();
    -
    -sizeof(normalDoc); // >= 1000
    -sizeof(leanDoc); // 86, 10x smaller!
    -
    -// In case you were wondering, the JSON form of a Mongoose doc is the same
    -// as the POJO. This additional memory only affects how much memory your
    -// Node.js process uses, not how much data is sent over the network.
    -JSON.stringify(normalDoc).length === JSON.stringify(leanDoc.length); // true
    -

    Under the hood, after executing a query, Mongoose converts the query results -from POJOs to Mongoose documents. If you turn on the lean option, Mongoose -skips this step.

    -
    const normalDoc = await MyModel.findOne();
    -const leanDoc = await MyModel.findOne().lean();
    -
    -normalDoc instanceof mongoose.Document; // true
    -normalDoc.constructor.name; // 'model'
    -
    -leanDoc instanceof mongoose.Document; // false
    -leanDoc.constructor.name; // 'Object'
    -

    The downside of enabling lean is that lean docs don't have:

    -
      -
    • Change tracking
    • -
    • Casting and validation
    • -
    • Getters and setters
    • -
    • Virtuals
    • -
    • save()
    • -
    -

    For example, the following code sample shows that the Person model's getters -and virtuals don't run if you enable lean.

    -
    // Define a `Person` model. Schema has 2 custom getters and a `fullName`
    -// virtual. Neither the getters nor the virtuals will run if lean is enabled.
    -const personSchema = new mongoose.Schema({
    -  firstName: {
    -    type: String,
    -    get: capitalizeFirstLetter
    -  },
    -  lastName: {
    -    type: String,
    -    get: capitalizeFirstLetter
    -  }
    -});
    -personSchema.virtual('fullName').get(function() {
    -  return `${this.firstName} ${this.lastName}`;
    -});
    -function capitalizeFirstLetter(v) {
    -  // Convert 'bob' -> 'Bob'
    -  return v.charAt(0).toUpperCase() + v.substr(1);
    -}
    -const Person = mongoose.model('Person', personSchema);
    -
    -// Create a doc and load it as a lean doc
    -await Person.create({ firstName: 'benjamin', lastName: 'sisko' });
    -const normalDoc = await Person.findOne();
    -const leanDoc = await Person.findOne().lean();
    -
    -normalDoc.fullName; // 'Benjamin Sisko'
    -normalDoc.firstName; // 'Benjamin', because of `capitalizeFirstLetter()`
    -normalDoc.lastName; // 'Sisko', because of `capitalizeFirstLetter()`
    -
    -leanDoc.fullName; // undefined
    -leanDoc.firstName; // 'benjamin', custom getter doesn't run
    -leanDoc.lastName; // 'sisko', custom getter doesn't run
    -

    Lean and Populate

    - -

    Populate works with lean(). If you -use both populate() and lean(), the lean option propagates to the -populated documents as well. In the below example, both the top-level -'Group' documents and the populated 'Person' documents will be lean.

    -
    // Create models
    -const Group = mongoose.model('Group', new mongoose.Schema({
    -  name: String,
    -  members: [{ type: mongoose.ObjectId, ref: 'Person' }]
    -}));
    -const Person = mongoose.model('Person', new mongoose.Schema({
    -  name: String
    -}));
    -
    -// Initialize data
    -const people = await Person.create([
    -  { name: 'Benjamin Sisko' },
    -  { name: 'Kira Nerys' }
    -]);
    -await Group.create({
    -  name: 'Star Trek: Deep Space Nine Characters',
    -  members: people.map(p => p._id)
    -});
    -
    -// Execute a lean query
    -const group = await Group.findOne().lean().populate('members');
    -group.members[0].name; // 'Benjamin Sisko'
    -group.members[1].name; // 'Kira Nerys'
    -
    -// Both the `group` and the populated `members` are lean.
    -group instanceof mongoose.Document; // false
    -group.members[0] instanceof mongoose.Document; // false
    -group.members[1] instanceof mongoose.Document; // false
    -

    Virtual populate also works with lean.

    -
    // Create models
    -const groupSchema = new mongoose.Schema({ name: String });
    -groupSchema.virtual('members', {
    -  ref: 'Person',
    -  localField: '_id',
    -  foreignField: 'groupId'
    -});
    -const Group = mongoose.model('Group', groupSchema);
    -const Person = mongoose.model('Person', new mongoose.Schema({
    -  name: String,
    -  groupId: mongoose.ObjectId
    -}));
    -
    -// Initialize data
    -const g = await Group.create({ name: 'DS9 Characters' });
    -const people = await Person.create([
    -  { name: 'Benjamin Sisko', groupId: g._id },
    -  { name: 'Kira Nerys', groupId: g._id }
    -]);
    -
    -// Execute a lean query
    -const group = await Group.findOne().lean().populate({
    -  path: 'members',
    -  options: { sort: { name: 1 } }
    -});
    -group.members[0].name; // 'Benjamin Sisko'
    -group.members[1].name; // 'Kira Nerys'
    -
    -// Both the `group` and the populated `members` are lean.
    -group instanceof mongoose.Document; // false
    -group.members[0] instanceof mongoose.Document; // false
    -group.members[1] instanceof mongoose.Document; // false
    -

    When to Use Lean

    - -

    If you're executing a query and sending the results without modification to, -say, an Express response, you should -use lean. In general, if you do not modify the query results and do not use -custom getters, you should use -lean(). If you modify the query results or rely on features like getters -or transforms, you should not -use lean().

    -

    Below is an example of an Express route -that is a good candidate for lean(). This route does not modify the person -doc and doesn't rely on any Mongoose-specific functionality.

    -
    // As long as you don't need any of the Person model's virtuals or getters,
    -// you can use `lean()`.
    -app.get('/person/:id', function(req, res) {
    -  Person.findOne({ _id: req.params.id }).lean().
    -    then(person => res.json({ person })).
    -    catch(error => res.json({ error: error.message }));
    -});
    -

    Below is an example of an Express route that should not use lean(). As -a general rule of thumb, GET routes are good candidates for lean() in a -RESTful API. -On the other hand, PUT, POST, etc. routes generally should not use lean().

    -
    // This route should **not** use `lean()`, because lean means no `save()`.
    -app.put('/person/:id', function(req, res) {
    -  Person.findOne({ _id: req.params.id }).
    -    then(person => {
    -      assert.ok(person);
    -      Object.assign(person, req.body);
    -      return person.save();
    -    }).
    -    then(person => res.json({ person })).
    -    catch(error => res.json({ error: error.message }));
    -});
    -

    Remember that virtuals do not end up in lean() query results. Use the -mongoose-lean-virtuals plugin -to add virtuals to your lean query results.

    -
    \ No newline at end of file diff --git a/docs/tutorials/query_casting.html b/docs/tutorials/query_casting.html deleted file mode 100644 index 1a7b068de14..00000000000 --- a/docs/tutorials/query_casting.html +++ /dev/null @@ -1,140 +0,0 @@ -Mongoose v5.6.4-pre: Mongoose Tutorials: Query Casting

    Query Casting

    - - - - -

    The first parameter to Model.find(), Query#find(), Model.findOne(), etc. is called filter. In older content this parameter is sometimes called query or conditions. For example:

    -
    const query = Character.find({ name: 'Jean-Luc Picard' });
    -query.getFilter(); // `{ name: 'Jean-Luc Picard' }`
    -
    -// Subsequent chained calls merge new properties into the filter
    -query.find({ age: { $gt: 50 } });
    -query.getFilter(); // `{ name: 'Jean-Luc Picard', age: { $gt: 50 } }`
    -

    When you execute the query using Query#exec() or Query#then(), Mongoose casts the filter to match your schema.

    -
    // Note that `_id` and `age` are strings. Mongoose will cast `_id` to
    -// a MongoDB ObjectId and `age.$gt` to a number.
    -const query = Character.findOne({
    -  _id: '5cdc267dd56b5662b7b7cc0c',
    -  age: { $gt: '50' }
    -});
    -
    -// `{ _id: '5cdc267dd56b5662b7b7cc0c', age: { $gt: '50' } }`
    -// Query hasn't been executed yet, so Mongoose hasn't casted the filter.
    -query.getFilter();
    -
    -const doc = await query.exec();
    -doc.name; // "Jean-Luc Picard"
    -
    -// Mongoose casted the filter, so `_id` became an ObjectId and `age.$gt`
    -// became a number.
    -query.getFilter()._id instanceof mongoose.Types.ObjectId; // true
    -typeof query.getFilter().age.$gt === 'number'; // true
    -

    If Mongoose fails to cast the filter to your schema, your query will throw a CastError.

    -
    const query = Character.findOne({ age: { $lt: 'not a number' } });
    -
    -const err = await query.exec().then(() => null, err => err);
    -err instanceof mongoose.CastError; // true
    -// Cast to number failed for value "not a number" at path "age" for
    -// model "Character"
    -err.message;
    -

    The strictQuery Option

    -

    By default, Mongoose does not cast filter properties that aren't in your schema.

    -
    const query = Character.findOne({ notInSchema: { $lt: 'not a number' } });
    -
    -// No error because `notInSchema` is not defined in the schema
    -await query.exec();
    -

    You can configure this behavior using the strictQuery option for schemas. This option is analagous to the strict option. Setting strictQuery to true removes non-schema properties from the filter:

    -
    mongoose.deleteModel('Character');
    -const schema = new mongoose.Schema({ name: String, age: Number }, {
    -  strictQuery: true
    -});
    -Character = mongoose.model('Character', schema);
    -
    -const query = Character.findOne({ notInSchema: { $lt: 'not a number' } });
    -
    -await query.exec();
    -query.getFilter(); // Empty object `{}`, Mongoose removes `notInSchema`
    -

    To make Mongoose throw an error if your filter has a property that isn't in the schema, set strictQuery to 'throw':

    -
    mongoose.deleteModel('Character');
    -const schema = new mongoose.Schema({ name: String, age: Number }, {
    -  strictQuery: 'throw'
    -});
    -Character = mongoose.model('Character', schema);
    -
    -const query = Character.findOne({ notInSchema: { $lt: 'not a number' } });
    -
    -const err = await query.exec().then(() => null, err => err);
    -err.name; // 'StrictModeError'
    -// Path "notInSchema" is not in schema and strictQuery is 'throw'.
    -err.message;
    -

    Implicit $in

    -

    Because of schemas, Mongoose knows what types fields should be, so it can provide some neat syntactic sugar. For example, if you forget to put $in on a non-array field, Mongoose will add $in for you.

    -
    // Normally wouldn't find anything because `name` is a string, but
    -// Mongoose automatically inserts `$in`
    -const query = Character.findOne({ name: ['Jean-Luc Picard', 'Will Riker'] });
    -
    -const doc = await query.exec();
    -doc.name; // "Jean-Luc Picard"
    -
    -// `{ name: { $in: ['Jean-Luc Picard', 'Will Riker'] } }`
    -query.getFilter();
    -
    \ No newline at end of file diff --git a/docs/tutorials/virtuals.html b/docs/tutorials/virtuals.html deleted file mode 100644 index ab11f943631..00000000000 --- a/docs/tutorials/virtuals.html +++ /dev/null @@ -1,215 +0,0 @@ -Mongoose v5.6.4-pre: Mongoose Tutorials: Virtuals

    Virtuals

    - - - - -

    In Mongoose, a virtual is a property that is not stored in MongoDB. -Virtuals are typically used for computed properties on documents.

    - -

    Your First Virtual

    -

    Suppose you have a User model. Every user has an email, but you also -want the email's domain. For example, the domain portion of -'test@gmail.com' is 'gmail.com'.

    -

    Below is one way to implement the domain property using a virtual. -You define virtuals on a schema using the Schema#virtual() function.

    -
    const userSchema = mongoose.Schema({
    -  email: String
    -});
    -// Create a virtual property `domain` that's computed from `email`.
    -userSchema.virtual('domain').get(function() {
    -  return this.email.slice(this.email.indexOf('@') + 1);
    -});
    -const User = mongoose.model('User', userSchema);
    -
    -let doc = await User.create({ email: 'test@gmail.com' });
    -// `domain` is now a property on User documents.
    -doc.domain; // 'gmail.com'
    -

    The Schema#virtual() function returns a VirtualType object. Unlike normal document properties, -virtuals do not have any underlying value and Mongoose does not do -any type coercion on virtuals. However, virtuals do have -getters and setters, which make -them ideal for computed properties, like the domain example above.

    -

    Virtual Setters

    -

    You can also use virtuals to set multiple properties at once as an -alternative to custom setters on normal properties. For example, suppose -you have two string properties: firstName and lastName. You can -create a virtual property fullName that lets you set both of -these properties at once. The key detail is that, in virtual getters and -setters, this refers to the document the virtual is attached to.

    -
    const userSchema = mongoose.Schema({
    -  firstName: String,
    -  lastName: String
    -});
    -// Create a virtual property `fullName` with a getter and setter.
    -userSchema.virtual('fullName').
    -  get(function() { return `${this.firstName} ${this.lastName}`; }).
    -  set(function(v) {
    -    // `v` is the value being set, so use the value to set
    -    // `firstName` and `lastName`.
    -    const firstName = v.substring(0, v.indexOf(' '));
    -    const lastName = v.substring(v.indexOf(' ') + 1);
    -    this.set({ firstName, lastName });
    -  });
    -const User = mongoose.model('User', userSchema);
    -
    -const doc = new User();
    -// Vanilla JavaScript assignment triggers the setter
    -doc.fullName = 'Jean-Luc Picard';
    -
    -doc.fullName; // 'Jean-Luc Picard'
    -doc.firstName; // 'Jean-Luc'
    -doc.lastName; // 'Picard'
    -

    Virtuals in JSON

    -

    By default, Mongoose does not include virtuals when you convert a document to -JSON. For example, if you pass a document to Express' res.json() function, -virtuals will not be included by default.

    -

    To include virtuals in res.json(), you need to set the -toJSON schema option to { virtuals: true }.

    -
    const opts = { toJSON: { virtuals: true } };
    -const userSchema = mongoose.Schema({
    -  _id: Number,
    -  email: String
    -}, opts);
    -// Create a virtual property `domain` that's computed from `email`.
    -userSchema.virtual('domain').get(function() {
    -  return this.email.slice(this.email.indexOf('@') + 1);
    -});
    -const User = mongoose.model('User', userSchema);
    -
    -const doc = new User({ _id: 1, email: 'test@gmail.com' });
    -
    -doc.toJSON().domain; // 'gmail.com'
    -// {"_id":1,"email":"test@gmail.com","domain":"gmail.com","id":"1"}
    -JSON.stringify(doc); 
    -
    -// To skip applying virtuals, pass `virtuals: false` to `toJSON()`
    -doc.toJSON({ virtuals: false }).domain; // undefined
    -

    Virtuals with Lean

    -

    Virtuals are properties on Mongoose documents. If you use the -lean option, that means your queries return POJOs -rather than full Mongoose documents. That means no virtuals if you use -lean().

    -
    const fullDoc = await User.findOne();
    -fullDoc.domain; // 'gmail.com'
    -
    -const leanDoc = await User.findOne().lean();
    -leanDoc.domain; // undefined
    -

    If you use lean() for performance, but still need virtuals, Mongoose -has an -officially supported mongoose-lean-virtuals plugin -that decorates lean documents with virtuals.

    -

    Limitations

    -

    Mongoose virtuals are not stored in MongoDB, which means you can't query -based on Mongoose virtuals.

    -
    // Will **not** find any results, because `domain` is not stored in
    -// MongoDB.
    -const doc = await User.findOne({ domain: 'gmail.com' });
    -doc; // undefined
    -

    If you want to query by a computed property, you should set the property using -a custom setter or pre save middleware.

    -

    Populate

    -

    Mongoose also supports populating virtuals. A populated -virtual contains documents from another collection. To define a populated -virtual, you need to specify:

    -
      -
    • The ref option, which tells Mongoose which model to populate documents from.
    • -
    • The localField and foreignField options. Mongoose will populate documents from the model in ref whose foreignField matches this document's localField.
    • -
    -
    const userSchema = mongoose.Schema({ _id: Number, email: String });
    -const blogPostSchema = mongoose.Schema({
    -  title: String,
    -  authorId: Number
    -});
    -// When you `populate()` the `author` virtual, Mongoose will find the
    -// first document in the User model whose `_id` matches this document's
    -// `authorId` property.
    -blogPostSchema.virtual('author', {
    -  ref: 'User',
    -  localField: 'authorId',
    -  foreignField: '_id',
    -  justOne: true
    -});
    -const User = mongoose.model('User', userSchema);
    -const BlogPost = mongoose.model('BlogPost', blogPostSchema);
    -
    -await BlogPost.create({ title: 'Introduction to Mongoose', authorId: 1 });
    -await User.create({ _id: 1, email: 'test@gmail.com' });
    -
    -const doc = await BlogPost.findOne().populate('author');
    -doc.author.email; // 'test@gmail.com'
    -

    Further Reading

    - -
    \ No newline at end of file diff --git a/docs/typescript.md b/docs/typescript.md new file mode 100644 index 00000000000..3149e67a207 --- /dev/null +++ b/docs/typescript.md @@ -0,0 +1,93 @@ +# TypeScript Support + +Mongoose introduced [officially supported TypeScript bindings in v5.11.0](https://thecodebarbarian.com/working-with-mongoose-in-typescript.html). +Mongoose's `index.d.ts` file supports a wide variety of syntaxes and strives to be compatible with `@types/mongoose` where possible. +This guide describes Mongoose's recommended approach to working with Mongoose in TypeScript. + +### Creating Your First Document + +To get started with Mongoose in TypeScript, you need to: + +1. Create an interface representing a document in MongoDB. +2. Create a [Schema](/docs/guide.html) corresponding to the document interface. +3. Create a Model. +4. [Connect to MongoDB](/docs/connections.html). + +```typescript +import { Schema, model, connect } from 'mongoose'; + +// 1. Create an interface representing a document in MongoDB. +interface User { + name: string; + email: string; + avatar?: string; +} + +// 2. Create a Schema corresponding to the document interface. +const schema = new Schema({ + name: { type: String, required: true }, + email: { type: String, required: true }, + avatar: String +}); + +// 3. Create a Model. +const UserModel = model('User', schema); + +run().catch(err => console.log(err)); + +async function run(): Promise { + // 4. Connect to MongoDB + await connect('mongodb://localhost:27017/test', { + useNewUrlParser: true, + useUnifiedTopology: true + }); + + const doc = new UserModel({ + name: 'Bill', + email: 'bill@initech.com', + avatar: 'https://i.imgur.com/dM7Thhn.png' + }); + await doc.save(); + + console.log(doc.email); // 'bill@initech.com' +} +``` + +You as the developer are responsible for ensuring that your document interface lines up with your Mongoose schema. +For example, Mongoose won't report an error if `email` is `required` in your Mongoose schema but optional in your document interface. + +### Using `extends Document` + +Alternatively, your document interface can extend Mongoose's `Document` class. +Many Mongoose TypeScript codebases use the below approach. + +```typescript +import { Document, Schema, model, connect } from 'mongoose'; + +interface User extends Document { + name: string; + email: string; + avatar?: string; +} +``` + +This approach works, but we recommend your document interface _not_ extend `Document`. +Using `extends Document` makes it difficult for Mongoose to infer which properties are present on [query filters](/docs/queries.html), [lean documents](/docs/tutorials/lean.html), and other cases. + +We recommend your document interface contain the properties defined in your schema and line up with what your documents look like in MongoDB. +Although you can add [instance methods](/docs/guide.html#methods) to your document interface, we do not recommend doing so. + +### Using Custom Bindings + +If Mongoose's built-in `index.d.ts` file does not work for you, you can remove it in a postinstall script in your `package.json` as shown below. +However, before you do, please [open an issue on Mongoose's GitHub page](https://github.com/Automattic/mongoose/issues/new) and describe the issue you're experiencing. + +``` +{ + "postinstall": "rm ./node_modules/mongoose/index.d.ts" +} +``` + +### Next Up + +Now that you've seen the basics of how to use Mongoose in TypeScript, let's take a look at [statics in TypeScript](/docs/typescript/statics.html). \ No newline at end of file diff --git a/docs/typescript/index.js b/docs/typescript/index.js new file mode 100644 index 00000000000..3d56082dde5 --- /dev/null +++ b/docs/typescript/index.js @@ -0,0 +1,14 @@ +'use strict'; + +const fs = require('fs'); + +const tutorials = fs.readdirSync(__dirname).filter(file => file.endsWith('.md')); + +module.exports = tutorials.reduce((map, filename) => { + const content = fs.readFileSync(`${__dirname}/${filename}`, 'utf8'); + map[`docs/typescript/${filename}`] = { + title: `Mongoose: ${content.split('\n')[0].replace(/^#+/, '').trim()}`, + markdown: true + }; + return map; +}, {}); \ No newline at end of file diff --git a/docs/typescript/populate.md b/docs/typescript/populate.md new file mode 100644 index 00000000000..1d3203b2a22 --- /dev/null +++ b/docs/typescript/populate.md @@ -0,0 +1,50 @@ +# Populate with TypeScript + +[Mongoose's TypeScript bindings](https://thecodebarbarian.com/working-with-mongoose-in-typescript.html) export a `PopulatedDoc` type that helps you define populated documents in your TypeScript definitions: + +```typescript +import { Schema, model, Document, PopulatedDoc } from 'mongoose'; + +// `child` is either an ObjectId or a populated document +interface Parent { + child?: PopulatedDoc, + name?: string +} +const ParentModel = model('Parent', new Schema({ + child: { type: 'ObjectId', ref: 'Child' }, + name: String +})); + +interface Child { + name?: string; +} +const childSchema: Schema = new Schema({ name: String }); +const ChildModel = model('Child', childSchema); + +ParentModel.findOne({}).populate('child').orFail().then((doc: Parent) => { + // Works + doc.child.name.trim(); +}); +``` + +Below is a simplified implementation of the `PopulatedDoc` type. It takes 2 generic parameters: the populated document type `PopulatedType`, and the unpopulated type `RawId`. +`RawId` defaults to an ObjectId. + +```typescript +type PopulatedDoc = PopulatedType | RawId; +``` + +You as the developer are responsible for enforcing strong typing between populated and non-populated docs. +Below is an example. + +```typescript +ParentModel.findOne({}).populate('child').orFail().then((doc: Parent) => { + // `doc` doesn't have type information that `child` is populated + useChildDoc(doc.child); +}); + +// You can use a function signature to make type checking more strict. +function useChildDoc(child: Child): void { + console.log(child.name.trim()); +} +``` \ No newline at end of file diff --git a/docs/typescript/query-helpers.md b/docs/typescript/query-helpers.md new file mode 100644 index 00000000000..40f285f1e94 --- /dev/null +++ b/docs/typescript/query-helpers.md @@ -0,0 +1,61 @@ +# Query Helpers in TypeScript + +[Query helpers](http://thecodebarbarian.com/mongoose-custom-query-methods.html) let you define custom helper methods on Mongoose queries. +Query helpers make queries more semantic using chaining syntax. + +```javascript +ProjectSchema.query.byName = function(name) { + return this.find({ name: name }); +}; +var Project = mongoose.model('Project', ProjectSchema); + +// Works. Any Project query, whether it be `find()`, `findOne()`, +// `findOneAndUpdate()`, `delete()`, etc. now has a `byName()` helper +Project.find().where('stars').gt(1000).byName('mongoose'); +``` + +In TypeScript, Mongoose's `Model` takes 3 generic parameters: + +1. The `DocType` +2. a `TQueryHelpers` type +3. a `TMethods` type + +The 2nd generic parameter, `TQueryHelpers`, should be an interface that contains a function signature for each of your query helpers. +Below is an example of creating a `ProjectModel` with a `byName` query helper. + +```typescript +import { Document, Model, Query, Schema, connect, model } from 'mongoose'; + +interface Project { + name: string; + stars: number; +} + +const schema = new Schema({ + name: { type: String, required: true }, + stars: { type: Number, required: true } +}); +// Query helpers should return `Query> & ProjectQueryHelpers` +// to enable chaining. +interface ProjectQueryHelpers { + byName(name: string): Query> & ProjectQueryHelpers; +} +schema.query.byName = function(name): Query> & ProjectQueryHelpers { + return this.find({ name: name }); +}; + +// 2nd param to `model()` is the Model class to return. +const ProjectModel = model>('Project', schema); + +run().catch(err => console.log(err)); + +async function run(): Promise { + await connect('mongodb://localhost:27017/test', { + useNewUrlParser: true, + useUnifiedTopology: true + }); + + // Equivalent to `ProjectModel.find({ stars: { $gt: 1000 }, name: 'mongoose' })` + await ProjectModel.find().where('stars').gt(1000).byName('mongoose'); +} +``` \ No newline at end of file diff --git a/docs/typescript/schemas.md b/docs/typescript/schemas.md new file mode 100644 index 00000000000..87aeabf53b8 --- /dev/null +++ b/docs/typescript/schemas.md @@ -0,0 +1,89 @@ +# Schemas in TypeScript + +Mongoose [schemas](/docs/guide.html) are how you tell Mongoose what your documents look like. +Mongoose schemas are separate from TypeScript interfaces, so you need to define both a _document interface_ and a _schema_. + +```typescript +import { Schema } from 'mongoose'; + +// Document interface +interface User { + name: string; + email: string; + avatar?: string; +} + +// Schema +const schema = new Schema({ + name: { type: String, required: true }, + email: { type: String, required: true }, + avatar: String +}); +``` + +By default, Mongoose does **not** check if your document interface lines up with your schema. +For example, the above code won't throw an error if `email` is optional in the document interface, but `required` in `schema`. + +## Defining Middleware + +The Mongoose `Schema` class in TypeScript has 3 generic parameters: + +```typescript +class Schema = Model, SchemaDefinitionType = undefined> extends events.EventEmitter { + // ... +} +``` + +The first generic param, `DocType`, represents the type that Mongoose uses as `this` for document middleware. +For example: + +```typescript +schema.pre('save', function(): void { + console.log(this.name); // TypeScript knows that `this` is a `User` by default +}); +``` + +## Checking Field Names + +The 3rd generic param, `SchemaDefinitionType`, checks to make sure that every path in your schema is defined in your document interface. +For example, the below code will fail to compile because `emaill` is a path in the schema, but not in the `SchemaDefinitionType`. + +```typescript +import { Schema, Model } from 'mongoose'; + +interface User { + name: string; + email: string; + avatar?: string; +} + +// Object literal may only specify known properties, but 'emaill' does not exist in type ... +// Did you mean to write 'email'? +const schema = new Schema, User>({ + name: { type: String, required: true }, + emaill: { type: String, required: true }, + avatar: String +}); +``` + +However, Mongoose does **not ** check for paths that are in the document interface, but not in the schema. +For example, the below code compiles. + +```typescript +import { Schema, Model } from 'mongoose'; + +interface User { + name: string; + email: string; + avatar?: string; + createdAt: number; +} + +const schema = new Schema, User>({ + name: { type: String, required: true }, + email: { type: String, required: true }, + avatar: String +}); +``` + +This is because Mongoose has numerous features that add paths to your schema, like [timestamps](https://masteringjs.io/tutorials/mongoose/timestamps), [plugins](/docs/plugins.html), etc. without you explicitly putting these paths in the `Schema()` constructor. \ No newline at end of file diff --git a/docs/typescript/statics.md b/docs/typescript/statics.md new file mode 100644 index 00000000000..6685acb91b1 --- /dev/null +++ b/docs/typescript/statics.md @@ -0,0 +1,25 @@ +# Statics in TypeScript + +Mongoose [models](/docs/models.html) do **not** have an explicit generic parameter for [statics](/docs/guide.html#statics). +If your model has statics, we recommend creating an interface that [extends](https://www.typescriptlang.org/docs/handbook/interfaces.html) Mongoose's `Model` interface as shown below. + +```typescript +import { Model, Schema, model } from 'mongoose'; + +interface IUser { + name: string; +} + +interface UserModel extends Model { + myStaticMethod(): number; +} + +const schema = new Schema({ name: String }); +schema.static('myStaticMethod', function myStaticMethod() { + return 42; +}); + +const User = model('User', schema); + +const answer: number = User.myStaticMethod(); // 42 +``` \ No newline at end of file diff --git a/docs/validation.html b/docs/validation.html deleted file mode 100644 index a5e29d73303..00000000000 --- a/docs/validation.html +++ /dev/null @@ -1,540 +0,0 @@ -Mongoose v5.6.0: Validation

    Validation

    Before we get into the specifics of validation syntax, please keep the following rules in mind:

    -
      -
    • Validation is defined in the SchemaType
    • -
    • Validation is middleware. Mongoose registers validation as a pre('save') hook on every schema by default.
    • -
    • You can manually run validation using doc.validate(callback) or doc.validateSync()
    • -
    • Validators are not run on undefined values. The only exception is the required validator.
    • -
    • Validation is asynchronously recursive; when you call Model#save, sub-document validation is executed as well. If an error occurs, your Model#save callback receives it
    • -
    • Validation is customizable
    • -
    - -
    var schema = new Schema({
    -  name: {
    -    type: String,
    -    required: true
    -  }
    -});
    -var Cat = db.model('Cat', schema);
    -
    -// This cat has no name :(
    -var cat = new Cat();
    -cat.save(function(error) {
    -  assert.equal(error.errors['name'].message,
    -    'Path `name` is required.');
    -
    -  error = cat.validateSync();
    -  assert.equal(error.errors['name'].message,
    -    'Path `name` is required.');
    -});
    -

    Built-in Validators

    Mongoose has several built-in validators.

    - -

    Each of the validator links above provide more information about how to enable them and customize their error messages.

    - -
    var breakfastSchema = new Schema({
    -  eggs: {
    -    type: Number,
    -    min: [6, 'Too few eggs'],
    -    max: 12
    -  },
    -  bacon: {
    -    type: Number,
    -    required: [true, 'Why no bacon?']
    -  },
    -  drink: {
    -    type: String,
    -    enum: ['Coffee', 'Tea'],
    -    required: function() {
    -      return this.bacon > 3;
    -    }
    -  }
    -});
    -var Breakfast = db.model('Breakfast', breakfastSchema);
    -
    -var badBreakfast = new Breakfast({
    -  eggs: 2,
    -  bacon: 0,
    -  drink: 'Milk'
    -});
    -var error = badBreakfast.validateSync();
    -assert.equal(error.errors['eggs'].message,
    -  'Too few eggs');
    -assert.ok(!error.errors['bacon']);
    -assert.equal(error.errors['drink'].message,
    -  '`Milk` is not a valid enum value for path `drink`.');
    -
    -badBreakfast.bacon = 5;
    -badBreakfast.drink = null;
    -
    -error = badBreakfast.validateSync();
    -assert.equal(error.errors['drink'].message, 'Path `drink` is required.');
    -
    -badBreakfast.bacon = null;
    -error = badBreakfast.validateSync();
    -assert.equal(error.errors['bacon'].message, 'Why no bacon?');
    -

    The unique Option is Not a Validator

    A common gotcha for beginners is that the unique option for schemas -is not a validator. It's a convenient helper for building MongoDB unique indexes. -See the FAQ for more information.

    - -
    var uniqueUsernameSchema = new Schema({
    -  username: {
    -    type: String,
    -    unique: true
    -  }
    -});
    -var U1 = db.model('U1', uniqueUsernameSchema);
    -var U2 = db.model('U2', uniqueUsernameSchema);
    -
    -var dup = [{ username: 'Val' }, { username: 'Val' }];
    -U1.create(dup, function(error) {
    -  // Race condition! This may save successfully, depending on whether
    -  // MongoDB built the index before writing the 2 docs.
    -});
    -
    -// Need to wait for the index to finish building before saving,
    -// otherwise unique constraints may be violated.
    -U2.once('index', function(error) {
    -  assert.ifError(error);
    -  U2.create(dup, function(error) {
    -    // Will error, but will *not* be a mongoose validation error, it will be
    -    // a duplicate key error.
    -    assert.ok(error);
    -    assert.ok(!error.errors);
    -    assert.ok(error.message.indexOf('duplicate key error') !== -1);
    -  });
    -});
    -
    -// There's also a promise-based equivalent to the event emitter API.
    -// The `init()` function is idempotent and returns a promise that
    -// will resolve once indexes are done building;
    -U2.init().then(function() {
    -  U2.create(dup, function(error) {
    -    // Will error, but will *not* be a mongoose validation error, it will be
    -    // a duplicate key error.
    -    assert.ok(error);
    -    assert.ok(!error.errors);
    -    assert.ok(error.message.indexOf('duplicate key error') !== -1);
    -  });
    -});
    -

    Custom Validators

    If the built-in validators aren't enough, you can define custom validators -to suit your needs.

    -

    Custom validation is declared by passing a validation function. -You can find detailed instructions on how to do this in the -SchemaType#validate() API docs.

    - -
    var userSchema = new Schema({
    -  phone: {
    -    type: String,
    -    validate: {
    -      validator: function(v) {
    -        return /\d{3}-\d{3}-\d{4}/.test(v);
    -      },
    -      message: props => `${props.value} is not a valid phone number!`
    -    },
    -    required: [true, 'User phone number required']
    -  }
    -});
    -
    -var User = db.model('user', userSchema);
    -var user = new User();
    -var error;
    -
    -user.phone = '555.0123';
    -error = user.validateSync();
    -assert.equal(error.errors['phone'].message,
    -  '555.0123 is not a valid phone number!');
    -
    -user.phone = '';
    -error = user.validateSync();
    -assert.equal(error.errors['phone'].message,
    -  'User phone number required');
    -
    -user.phone = '201-555-0123';
    -// Validation succeeds! Phone number is defined
    -// and fits `DDD-DDD-DDDD`
    -error = user.validateSync();
    -assert.equal(error, null);
    -

    Async Custom Validators

    Custom validators can also be asynchronous. If your validator function -returns a promise (like an async function), mongoose will wait for that -promise to settle. If you prefer callbacks, set the isAsync option, -and mongoose will pass a callback as the 2nd argument to your validator -function.

    - -
    var userSchema = new Schema({
    -  name: {
    -    type: String,
    -    // You can also make a validator async by returning a promise. If you
    -    // return a promise, do **not** specify the `isAsync` option.
    -    validate: function(v) {
    -      return new Promise(function(resolve, reject) {
    -        setTimeout(function() {
    -          resolve(false);
    -        }, 5);
    -      });
    -    }
    -  },
    -  phone: {
    -    type: String,
    -    validate: {
    -      isAsync: true,
    -      validator: function(v, cb) {
    -        setTimeout(function() {
    -          var phoneRegex = /\d{3}-\d{3}-\d{4}/;
    -          var msg = v + ' is not a valid phone number!';
    -          // First argument is a boolean, whether validator succeeded
    -          // 2nd argument is an optional error message override
    -          cb(phoneRegex.test(v), msg);
    -        }, 5);
    -      },
    -      // Default error message, overridden by 2nd argument to `cb()` above
    -      message: 'Default error message'
    -    },
    -    required: [true, 'User phone number required']
    -  }
    -});
    -
    -var User = db.model('User', userSchema);
    -var user = new User();
    -var error;
    -
    -user.phone = '555.0123';
    -user.name = 'test';
    -user.validate(function(error) {
    -  assert.ok(error);
    -  assert.equal(error.errors['phone'].message,
    -    '555.0123 is not a valid phone number!');
    -  assert.equal(error.errors['name'].message,
    -    'Validator failed for path `name` with value `test`');
    -});
    -

    Validation Errors

    Errors returned after failed validation contain an errors object -whose values are ValidatorError objects. Each -ValidatorError has kind, path, -value, and message properties. -A ValidatorError also may have a reason property. If an error was -thrown in the validator, this property will contain the error that was -thrown.

    - -
    var toySchema = new Schema({
    -  color: String,
    -  name: String
    -});
    -
    -var validator = function(value) {
    -  return /red|white|gold/i.test(value);
    -};
    -toySchema.path('color').validate(validator,
    -  'Color `{VALUE}` not valid', 'Invalid color');
    -toySchema.path('name').validate(function(v) {
    -  if (v !== 'Turbo Man') {
    -    throw new Error('Need to get a Turbo Man for Christmas');
    -  }
    -  return true;
    -}, 'Name `{VALUE}` is not valid');
    -
    -var Toy = db.model('Toy', toySchema);
    -
    -var toy = new Toy({ color: 'Green', name: 'Power Ranger' });
    -
    -toy.save(function (err) {
    -  // `err` is a ValidationError object
    -  // `err.errors.color` is a ValidatorError object
    -  assert.equal(err.errors.color.message, 'Color `Green` not valid');
    -  assert.equal(err.errors.color.kind, 'Invalid color');
    -  assert.equal(err.errors.color.path, 'color');
    -  assert.equal(err.errors.color.value, 'Green');
    -
    -  // This is new in mongoose 5. If your validator throws an exception,
    -  // mongoose will use that message. If your validator returns `false`,
    -  // mongoose will use the 'Name `Power Ranger` is not valid' message.
    -  assert.equal(err.errors.name.message,
    -    'Need to get a Turbo Man for Christmas');
    -  assert.equal(err.errors.name.value, 'Power Ranger');
    -  // If your validator threw an error, the `reason` property will contain
    -  // the original error thrown, including the original stack trace.
    -  assert.equal(err.errors.name.reason.message,
    -    'Need to get a Turbo Man for Christmas');
    -
    -  assert.equal(err.name, 'ValidationError');
    -});
    -

    Cast Errors

    Before running validators, Mongoose attempts to coerce values to the -correct type. This process is called casting the document. If -casting fails for a given path, the error.errors object will contain -a CastError object.

    -

    Casting runs before validation, and validation does not run if casting -fails. That means your custom validators may assume v is null, -undefined, or an instance of the type specified in your schema.

    - -
    const vehicleSchema = new mongoose.Schema({
    -  numWheels: { type: Number, max: 18 }
    -});
    -const Vehicle = db.model('Vehicle', vehicleSchema);
    -
    -const doc = new Vehicle({ numWheels: 'not a number' });
    -const err = doc.validateSync();
    -
    -err.errors['numWheels'].name; // 'CastError'
    -// 'Cast to Number failed for value "not a number" at path "numWheels"'
    -err.errors['numWheels'].message;
    -

    Required Validators On Nested Objects

    Defining validators on nested objects in mongoose is tricky, because -nested objects are not fully fledged paths.

    - -
    var personSchema = new Schema({
    -  name: {
    -    first: String,
    -    last: String
    -  }
    -});
    -
    -assert.throws(function() {
    -  // This throws an error, because 'name' isn't a full fledged path
    -  personSchema.path('name').required(true);
    -}, /Cannot.*'required'/);
    -
    -// To make a nested object required, use a single nested schema
    -var nameSchema = new Schema({
    -  first: String,
    -  last: String
    -});
    -
    -personSchema = new Schema({
    -  name: {
    -    type: nameSchema,
    -    required: true
    -  }
    -});
    -
    -var Person = db.model('Person', personSchema);
    -
    -var person = new Person();
    -var error = person.validateSync();
    -assert.ok(error.errors['name']);
    -

    Update Validators

    In the above examples, you learned about document validation. Mongoose also -supports validation for update(), -updateOne(), -updateMany(), -and findOneAndUpdate() operations. -Update validators are off by default - you need to specify -the runValidators option.

    -

    To turn on update validators, set the runValidators option for -update(), updateOne(), updateMany(), or findOneAndUpdate(). -Be careful: update validators are off by default because they have several -caveats.

    - -
    var toySchema = new Schema({
    -  color: String,
    -  name: String
    -});
    -
    -var Toy = db.model('Toys', toySchema);
    -
    -Toy.schema.path('color').validate(function (value) {
    -  return /red|green|blue/i.test(value);
    -}, 'Invalid color');
    -
    -var opts = { runValidators: true };
    -Toy.updateOne({}, { color: 'not a color' }, opts, function (err) {
    -  assert.equal(err.errors.color.message,
    -    'Invalid color');
    -});
    -

    Update Validators and this

    There are a couple of key differences between update validators and -document validators. In the color validation function above, this refers -to the document being validated when using document validation. -However, when running update validators, the document being updated -may not be in the server's memory, so by default the value of this is -not defined.

    - -
    var toySchema = new Schema({
    -  color: String,
    -  name: String
    -});
    -
    -toySchema.path('color').validate(function(value) {
    -  // When running in `validate()` or `validateSync()`, the
    -  // validator can access the document using `this`.
    -  // Does **not** work with update validators.
    -  if (this.name.toLowerCase().indexOf('red') !== -1) {
    -    return value !== 'red';
    -  }
    -  return true;
    -});
    -
    -var Toy = db.model('ActionFigure', toySchema);
    -
    -var toy = new Toy({ color: 'red', name: 'Red Power Ranger' });
    -var error = toy.validateSync();
    -assert.ok(error.errors['color']);
    -
    -var update = { color: 'red', name: 'Red Power Ranger' };
    -var opts = { runValidators: true };
    -
    -Toy.updateOne({}, update, opts, function(error) {
    -  // The update validator throws an error:
    -  // "TypeError: Cannot read property 'toLowerCase' of undefined",
    -  // because `this` is **not** the document being updated when using
    -  // update validators
    -  assert.ok(error);
    -});
    -

    The context option

    The context option lets you set the value of this in update validators -to the underlying query.

    - -
    toySchema.path('color').validate(function(value) {
    -  // When running update validators with the `context` option set to
    -  // 'query', `this` refers to the query object.
    -  if (this.getUpdate().$set.name.toLowerCase().indexOf('red') !== -1) {
    -    return value === 'red';
    -  }
    -  return true;
    -});
    -
    -var Toy = db.model('Figure', toySchema);
    -
    -var update = { color: 'blue', name: 'Red Power Ranger' };
    -// Note the context option
    -var opts = { runValidators: true, context: 'query' };
    -
    -Toy.updateOne({}, update, opts, function(error) {
    -  assert.ok(error.errors['color']);
    -});
    -

    Update Validators Only Run On Updated Paths

    The other key difference that update validators only run on the paths -specified in the update. For instance, in the below example, because -'name' is not specified in the update operation, update validation will -succeed.

    -

    When using update validators, required validators only fail when -you try to explicitly $unset the key.

    - -
    var kittenSchema = new Schema({
    -  name: { type: String, required: true },
    -  age: Number
    -});
    -
    -var Kitten = db.model('Kitten', kittenSchema);
    -
    -var update = { color: 'blue' };
    -var opts = { runValidators: true };
    -Kitten.updateOne({}, update, opts, function(err) {
    -  // Operation succeeds despite the fact that 'name' is not specified
    -});
    -
    -var unset = { $unset: { name: 1 } };
    -Kitten.updateOne({}, unset, opts, function(err) {
    -  // Operation fails because 'name' is required
    -  assert.ok(err);
    -  assert.ok(err.errors['name']);
    -});
    -

    Update Validators Only Run For Some Operations

    One final detail worth noting: update validators only run on the -following update operators:

    -
      -
    • $set
    • -
    • $unset
    • -
    • $push (>= 4.8.0)
    • -
    • $addToSet (>= 4.8.0)
    • -
    • $pull (>= 4.12.0)
    • -
    • $pullAll (>= 4.12.0)
    • -
    -

    For instance, the below update will succeed, regardless of the value of -number, because update validators ignore $inc.

    -

    Also, $push, $addToSet, $pull, and $pullAll validation does -not run any validation on the array itself, only individual elements -of the array.

    - -
    var testSchema = new Schema({
    -  number: { type: Number, max: 0 },
    -  arr: [{ message: { type: String, maxlength: 10 } }]
    -});
    -
    -// Update validators won't check this, so you can still `$push` 2 elements
    -// onto the array, so long as they don't have a `message` that's too long.
    -testSchema.path('arr').validate(function(v) {
    -  return v.length < 2;
    -});
    -
    -var Test = db.model('Test', testSchema);
    -
    -var update = { $inc: { number: 1 } };
    -var opts = { runValidators: true };
    -Test.updateOne({}, update, opts, function(error) {
    -  // There will never be a validation error here
    -  update = { $push: [{ message: 'hello' }, { message: 'world' }] };
    -  Test.updateOne({}, update, opts, function(error) {
    -    // This will never error either even though the array will have at
    -    // least 2 elements.
    -  });
    -});
    -

    On $push and $addToSet

    New in 4.8.0: update validators also run on $push and $addToSet

    - -
    var testSchema = new Schema({
    -  numbers: [{ type: Number, max: 0 }],
    -  docs: [{
    -    name: { type: String, required: true }
    -  }]
    -});
    -
    -var Test = db.model('TestPush', testSchema);
    -
    -var update = {
    -  $push: {
    -    numbers: 1,
    -    docs: { name: null }
    -  }
    -};
    -var opts = { runValidators: true };
    -Test.updateOne({}, update, opts, function(error) {
    -  assert.ok(error.errors['numbers']);
    -  assert.ok(error.errors['docs']);
    -});
    -
    \ No newline at end of file diff --git a/docs/validation.md b/docs/validation.md new file mode 100644 index 00000000000..56cc4071f20 --- /dev/null +++ b/docs/validation.md @@ -0,0 +1,195 @@ +## Validation + +Before we get into the specifics of validation syntax, please keep the following rules in mind: + +- Validation is defined in the [SchemaType](./schematypes.html) +- Validation is [middleware](./middleware.html). Mongoose registers validation as a `pre('save')` hook on every schema by default. +- You can disable automatic validation before save by setting the [validateBeforeSave](./guide.html#validateBeforeSave) option +- You can manually run validation using `doc.validate(callback)` or `doc.validateSync()` +- You can manually mark a field as invalid (causing validation to fail) by using [`doc.invalidate(...)`](./api.html#document_Document-invalidate) +- Validators are not run on undefined values. The only exception is the [`required` validator](./api.html#schematype_SchemaType-required). +- Validation is asynchronously recursive; when you call [Model#save](./api.html#model_Model-save), sub-document validation is executed as well. If an error occurs, your [Model#save](./api.html#model_Model-save) callback receives it +- Validation is customizable + +```javascript +[require:Validation$] +``` + +### Built-in Validators + +Mongoose has several built-in validators. + +- All [SchemaTypes](./schematypes.html) have the built-in [required](./api.html#schematype_SchemaType-required) validator. The required validator uses the [SchemaType's `checkRequired()` function](./api.html#schematype_SchemaType-checkRequired) to determine if the value satisfies the required validator. +- [Numbers](./api.html#schema-number-js) have [`min` and `max`](./schematypes.html#number-validators) validators. +- [Strings](./api.html#schema-string-js) have [`enum`, `match`, `minLength`, and `maxLength`](./schematypes.html#string-validators) validators. + +Each of the validator links above provide more information about how to enable them and customize their error messages. + +```javascript +[require:Built-in Validators] +``` + +### Custom Error Messages + +You can configure the error message for individual validators in your schema. There are two equivalent +ways to set the validator error message: + +- Array syntax: `min: [6, 'Must be at least 6, got {VALUE}']` +- Object syntax: `enum: { values: ['Coffee', 'Tea'], message: '{VALUE} is not supported' }` + +Mongoose also supports rudimentary templating for error messages. +Mongoose replaces `{VALUE}` with the value being validated. + +```javascript +[require:Custom Error Messages] +``` + +### The `unique` Option is Not a Validator + +A common gotcha for beginners is that the `unique` option for schemas +is *not* a validator. It's a convenient helper for building [MongoDB unique indexes](https://docs.mongodb.com/manual/core/index-unique/). +See the [FAQ](/docs/faq.html) for more information. + +```javascript +[require:The `unique` Option is Not a Validator] +``` + +### Custom Validators + +If the built-in validators aren't enough, you can define custom validators +to suit your needs. + +Custom validation is declared by passing a validation function. +You can find detailed instructions on how to do this in the +[`SchemaType#validate()` API docs](./api.html#schematype_SchemaType-validate). + +```javascript +[require:Custom Validators] +``` + +### Async Custom Validators + +Custom validators can also be asynchronous. If your validator function +returns a promise (like an `async` function), mongoose will wait for that +promise to settle. If the returned promise rejects, or fulfills with +the value `false`, Mongoose will consider that a validation error. + +```javascript +[require:Async Custom Validators] +``` + +### Validation Errors + +Errors returned after failed validation contain an `errors` object +whose values are `ValidatorError` objects. Each +[ValidatorError](./api.html#error-validation-js) has `kind`, `path`, +`value`, and `message` properties. +A ValidatorError also may have a `reason` property. If an error was +thrown in the validator, this property will contain the error that was +thrown. + +```javascript +[require:Validation Errors] +``` + +### Cast Errors + +Before running validators, Mongoose attempts to coerce values to the +correct type. This process is called _casting_ the document. If +casting fails for a given path, the `error.errors` object will contain +a `CastError` object. + +Casting runs before validation, and validation does not run if casting +fails. That means your custom validators may assume `v` is `null`, +`undefined`, or an instance of the type specified in your schema. + +```javascript +[require:Cast Errors] +``` + +### Required Validators On Nested Objects + +Defining validators on nested objects in mongoose is tricky, because +nested objects are not fully fledged paths. + +```javascript +[require:Required Validators On Nested Objects] +``` + +### Update Validators + +In the above examples, you learned about document validation. Mongoose also +supports validation for [`update()`](/docs/api.html#query_Query-update), +[`updateOne()`](/docs/api.html#query_Query-updateOne), +[`updateMany()`](/docs/api.html#query_Query-updateMany), +and [`findOneAndUpdate()`](/docs/api.html#query_Query-findOneAndUpdate) operations. +Update validators are off by default - you need to specify +the `runValidators` option. + +To turn on update validators, set the `runValidators` option for +`update()`, `updateOne()`, `updateMany()`, or `findOneAndUpdate()`. +Be careful: update validators are off by default because they have several +caveats. + +```javascript +[require:Update Validators$] +``` + +### Update Validators and `this` + +There are a couple of key differences between update validators and +document validators. In the color validation function above, `this` refers +to the document being validated when using document validation. +However, when running update validators, the document being updated +may not be in the server's memory, so by default the value of `this` is +not defined. + +```javascript +[require:Update Validators and `this`] +``` + +### The `context` option + +The `context` option lets you set the value of `this` in update validators +to the underlying query. + +```javascript +[require:The `context` option] +``` + +### Update Validators Only Run On Updated Paths + +The other key difference is that update validators only run on the paths +specified in the update. For instance, in the below example, because +'name' is not specified in the update operation, update validation will +succeed. + +When using update validators, `required` validators **only** fail when +you try to explicitly `$unset` the key. + +```javascript +[require:Update Validators Only Run On Updated Paths] +``` + +### Update Validators Only Run For Some Operations + +One final detail worth noting: update validators **only** run on the +following update operators: + +- `$set` +- `$unset` +- `$push` (>= 4.8.0) +- `$addToSet` (>= 4.8.0) +- `$pull` (>= 4.12.0) +- `$pullAll` (>= 4.12.0) + +For instance, the below update will succeed, regardless of the value of +`number`, because update validators ignore `$inc`. + +Also, `$push`, `$addToSet`, `$pull`, and `$pullAll` validation does +**not** run any validation on the array itself, only individual elements +of the array. + +```javascript +[require:Update Validators Only Run For Some Operations] +``` \ No newline at end of file diff --git a/index.d.ts b/index.d.ts index 9769a80cedf..695fe60b3b4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -12,6 +12,8 @@ declare module 'mongoose' { uninitialized = 99, } + class NativeDate extends global.Date {} + /** The Mongoose Date [SchemaType](/docs/schematypes.html). */ export type Date = Schema.Types.Date; @@ -35,8 +37,7 @@ declare module 'mongoose' { * Mongoose constructor. The exports object of the `mongoose` module is an instance of this * class. Most apps will only use this one instance. */ - // eslint-disable-next-line @typescript-eslint/ban-types - export const Mongoose: new (options?: object | null) => typeof mongoose; + export const Mongoose: new (options?: MongooseOptions | null) => typeof mongoose; /** * The Mongoose Number [SchemaType](/docs/schematypes.html). Used for @@ -53,7 +54,7 @@ declare module 'mongoose' { */ export type ObjectId = Schema.Types.ObjectId; - export const Promise: any; + export let Promise: any; export const PromiseProvider: any; /** The various Mongoose SchemaTypes. */ @@ -63,8 +64,8 @@ declare module 'mongoose' { export const STATES: typeof ConnectionStates; /** Opens Mongoose's default connection to MongoDB, see [connections docs](https://mongoosejs.com/docs/connections.html) */ - export function connect(uri: string, options: ConnectOptions, callback: (err: CallbackError) => void): void; - export function connect(uri: string, callback: (err: CallbackError) => void): void; + export function connect(uri: string, options: ConnectOptions, callback: CallbackWithoutResult): void; + export function connect(uri: string, callback: CallbackWithoutResult): void; export function connect(uri: string, options?: ConnectOptions): Promise; /** The Mongoose module's default connection. Equivalent to `mongoose.connections[0]`, see [`connections`](#mongoose_Mongoose-connections). */ @@ -78,7 +79,7 @@ declare module 'mongoose' { /** Creates a Connection instance. */ export function createConnection(uri: string, options?: ConnectOptions): Connection & Promise; export function createConnection(): Connection; - export function createConnection(uri: string, options: ConnectOptions, callback: (err: CallbackError, conn: Connection) => void): void; + export function createConnection(uri: string, options: ConnectOptions, callback: Callback): void; /** * Removes the model named `name` from the default connection, if it exists. @@ -88,10 +89,10 @@ declare module 'mongoose' { export function deleteModel(name: string | RegExp): typeof mongoose; export function disconnect(): Promise; - export function disconnect(cb: (err: CallbackError) => void): void; + export function disconnect(cb: CallbackWithoutResult): void; /** Gets mongoose options */ - export function get(key: string): any; + export function get(key: K): MongooseOptions[K]; /** * Returns true if Mongoose can cast the given value to an ObjectId, or @@ -99,10 +100,16 @@ declare module 'mongoose' { */ export function isValidObjectId(v: any): boolean; - export function model(name: string, schema?: Schema, collection?: string, skipInit?: boolean): Model; - export function model>( + export function model(name: string, schema?: Schema, collection?: string, skipInit?: boolean): Model; + export function model, TQueryHelpers = {}>( + name: string, + schema?: Schema, + collection?: string, + skipInit?: boolean + ): U; + export function model, TQueryHelpers = {}>( name: string, - schema?: Schema, + schema?: Schema, collection?: string, skipInit?: boolean ): U; @@ -118,16 +125,16 @@ declare module 'mongoose' { * [timestamps](/docs/guide.html#timestamps). You may stub out this function * using a tool like [Sinon](https://www.npmjs.com/package/sinon) for testing. */ - export function now(): Date; + export function now(): NativeDate; /** Declares a global plugin executed on all Schemas. */ export function plugin(fn: (schema: Schema, opts?: any) => void, opts?: any): typeof mongoose; /** Getter/setter around function for pluralizing collection names. */ - export function pluralize(fn?: (str: string) => string): (str: string) => string; + export function pluralize(fn?: ((str: string) => string) | null): ((str: string) => string) | null; /** Sets mongoose options */ - export function set(key: string, value: any): void; + export function set(key: K, value: MongooseOptions[K]): typeof mongoose; /** * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions) @@ -135,7 +142,7 @@ declare module 'mongoose' { * and [transactions](http://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html). */ export function startSession(options?: mongodb.SessionOptions): Promise; - export function startSession(options: mongodb.SessionOptions, cb: (err: any, session: mongodb.ClientSession) => void): void; + export function startSession(options: mongodb.SessionOptions, cb: Callback): void; /** The Mongoose version */ export const version: string; @@ -144,6 +151,135 @@ declare module 'mongoose' { type Mongoose = typeof mongoose; + interface MongooseOptions { + /** true by default. Set to false to skip applying global plugins to child schemas */ + applyPluginsToChildSchemas?: boolean; + + /** + * 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. + */ + applyPluginsToDiscriminators?: boolean; + + /** + * Set to `true` to make Mongoose call` 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?: boolean; + + /** + * true by default. Set to false to disable automatic index creation + * for all models associated with this Mongoose instance. + */ + autoIndex?: boolean; + + /** enable/disable mongoose's buffering mechanism for all connections and models */ + bufferCommands?: boolean; + + bufferTimeoutMS?: number; + + /** false by default. Set to `true` to `clone()` all schemas before compiling into a model. */ + cloneSchemas?: boolean; + + /** + * 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(', ')})`. + */ + debug?: + | boolean + | { color?: boolean; shell?: boolean } + | stream.Writable + | ((collectionName: string, methodName: string, ...methodArgs: any[]) => void); + + /** If set, attaches [maxTimeMS](https://docs.mongodb.com/manual/reference/operator/meta/maxTimeMS/) to every query */ + maxTimeMS?: number; + + /** + * 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. + */ + objectIdGetter?: boolean; + + /** + * Set to `true` to default to overwriting models with the same name when calling + * `mongoose.model()`, as opposed to throwing an `OverwriteModelError`. + */ + overwriteModels?: boolean; + + /** + * 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. + */ + returnOriginal?: boolean; + + /** + * false by default. Set to true to enable [update validators]( + * https://mongoosejs.com/docs/validation.html#update-validators + * ) for all validators by default. + */ + runValidators?: boolean; + + sanitizeProjection?: boolean; + + /** + * 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. + */ + selectPopulatedPaths?: boolean; + + setDefaultsOnInsert?: boolean; + + /** true by default, may be `false`, `true`, or `'throw'`. Sets the default strict mode for schemas. */ + strict?: boolean | 'throw'; + + /** + * false by default, may be `false`, `true`, or `'throw'`. Sets the default + * [strictQuery](https://mongoosejs.com/docs/guide.html#strictQuery) mode for schemas. + */ + strictQuery?: boolean | 'throw'; + + /** + * `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to + * `toJSON()`, for determining how Mongoose documents get serialized by `JSON.stringify()` + */ + toJSON?: ToObjectOptions; + + /** `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to `toObject()` */ + toObject?: ToObjectOptions; + + /** true by default, may be `false` or `true`. Sets the default typePojoToMixed for schemas. */ + typePojoToMixed?: boolean; + + /** + * false by default. Set to `true` to make Mongoose's default index build use `createIndex()` + * instead of `ensureIndex()` to avoid deprecation warnings from the MongoDB driver. + */ + useCreateIndex?: boolean; + + /** + * true by default. Set to `false` to make `findOneAndUpdate()` and `findOneAndRemove()` + * use native `findOneAndUpdate()` rather than `findAndModify()`. + */ + useFindAndModify?: boolean; + + /** false by default. Set to `true` to make all connections set the `useNewUrlParser` option by default */ + useNewUrlParser?: boolean; + + usePushEach?: boolean; + + /** false by default. Set to `true` to make all connections set the `useUnifiedTopology` option by default */ + useUnifiedTopology?: boolean; + } + // eslint-disable-next-line @typescript-eslint/no-empty-interface interface ClientSession extends mongodb.ClientSession { } @@ -164,12 +300,16 @@ declare module 'mongoose' { autoCreate?: boolean; /** False by default. If `true`, this connection will use `createIndex()` instead of `ensureIndex()` for automatic index builds via `Model.init()`. */ useCreateIndex?: boolean; + /** false by default. Set to `true` to make all connections set the `useNewUrlParser` option by default */ + useNewUrlParser?: boolean; + /** false by default. Set to `true` to make all connections set the `useUnifiedTopology` option by default */ + useUnifiedTopology?: boolean; } class Connection extends events.EventEmitter { /** Closes the connection */ - close(callback: (err: CallbackError) => void): void; - close(force: boolean, callback: (err: CallbackError) => void): void; + close(callback: CallbackWithoutResult): void; + close(force: boolean, callback: CallbackWithoutResult): void; close(force?: boolean): Promise; /** Retrieves a collection, creating it if not cached. */ @@ -190,8 +330,8 @@ declare module 'mongoose' { * and [views](https://docs.mongodb.com/manual/core/views/) from mongoose. */ createCollection(name: string, options?: mongodb.CollectionCreateOptions): Promise>; - createCollection(name: string, cb: (err: CallbackError, collection: mongodb.Collection) => void): void; - createCollection(name: string, options: mongodb.CollectionCreateOptions, cb?: (err: CallbackError, collection: mongodb.Collection) => void): Promise>; + createCollection(name: string, cb: Callback>): void; + createCollection(name: string, options: mongodb.CollectionCreateOptions, cb?: Callback): Promise>; /** * Removes the model named `name` from this connection, if it exists. You can @@ -205,14 +345,14 @@ declare module 'mongoose' { * all documents and indexes. */ dropCollection(collection: string): Promise; - dropCollection(collection: string, cb: (err: CallbackError) => void): void; + dropCollection(collection: string, cb: CallbackWithoutResult): void; /** * Helper for `dropDatabase()`. Deletes the given database, including all * collections, documents, and indexes. */ dropDatabase(): Promise; - dropDatabase(cb: (err: CallbackError) => void): void; + dropDatabase(cb: CallbackWithoutResult): void; /** Gets the value of the option `key`. Equivalent to `conn.options[key]` */ get(key: string): any; @@ -243,10 +383,16 @@ declare module 'mongoose' { models: { [index: string]: Model }; /** Defines or retrieves a model. */ - model(name: string, schema?: Schema, collection?: string): Model; - model>( + model(name: string, schema?: Schema, collection?: string): Model; + model, TQueryHelpers = {}>( name: string, - schema?: Schema, + schema?: Schema, + collection?: string, + skipInit?: boolean + ): U; + model, TQueryHelpers = {}>( + name: string, + schema?: Schema, collection?: string, skipInit?: boolean ): U; @@ -303,7 +449,7 @@ declare module 'mongoose' { * and [transactions](http://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html). */ startSession(options?: mongodb.SessionOptions): Promise; - startSession(options: mongodb.SessionOptions, cb: (err: any, session: mongodb.ClientSession) => void): void; + startSession(options: mongodb.SessionOptions, cb: Callback): void; /** * _Requires MongoDB >= 3.6.0._ Executes the wrapped async function @@ -314,7 +460,7 @@ declare module 'mongoose' { transaction(fn: (session: mongodb.ClientSession) => Promise): Promise; /** Switches to a different database using the same connection pool. */ - useDb(name: string, options?: { useCache?: boolean }): Connection; + useDb(name: string, options?: { useCache?: boolean, noListener?: boolean }): Connection; /** The username specified in the URI */ user: string; @@ -366,14 +512,17 @@ declare module 'mongoose' { getIndexes(): any; } - class Document { + class Document { constructor(doc?: any); /** This documents _id. */ _id?: T; /** This documents __v. */ - __v?: number; + __v?: any; + + /* Get all subdocs (by bfs) */ + $getAllSubdocs(): Document[]; /** Don't run validation on this path or persist changes to this path. */ $ignore(path: string): void; @@ -384,6 +533,9 @@ declare module 'mongoose' { /** Getter/setter, determines whether the document was removed or not. */ $isDeleted(val?: boolean): boolean; + /** Returns an array of all populated documents associated with the query */ + $getPopulatedDocs(): Document[]; + /** * 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 @@ -422,7 +574,7 @@ declare module 'mongoose' { $set(path: string, val: any, type: any, options?: any): this; $set(value: any): this; - /** Additional properties to attach to the query when calling `save()` and `isNew` is false. */ + /** Set this property to add additional query filters when Mongoose saves this document and `isNew` is false. */ $where: Record; /** If this is a discriminator model, `baseModelName` is the name of the base model. */ @@ -435,12 +587,14 @@ declare module 'mongoose' { db: Connection; /** Removes this document from the db. */ - delete(options?: QueryOptions): Query; - delete(options?: QueryOptions, cb?: (err: CallbackError, res: any) => void): void; + delete(options?: QueryOptions): QueryWithHelpers; + delete(options: QueryOptions, cb?: Callback): void; + delete(cb: Callback): void; /** Removes this document from the db. */ - deleteOne(options?: QueryOptions): Query; - deleteOne(options?: QueryOptions, cb?: (err: CallbackError, res: any) => void): void; + deleteOne(options?: QueryOptions): QueryWithHelpers; + deleteOne(options: QueryOptions, cb?: Callback): void; + deleteOne(cb: Callback): void; /** Takes a populated field and returns it to its unpopulated state. */ depopulate(path: string): this; @@ -466,7 +620,7 @@ declare module 'mongoose' { /** Explicitly executes population and returns a promise. Useful for promises integration. */ execPopulate(): Promise; - execPopulate(callback: (err: CallbackError, res: this) => void): void; + execPopulate(callback: Callback): void; /** Returns the value of a path. */ get(path: string, type?: any, options?: any): any; @@ -488,7 +642,7 @@ declare module 'mongoose' { * Called internally after a document is returned from mongodb. Normally, * you do **not** need to call this function on your own. */ - init(obj: any, opts?: any, cb?: (err: CallbackError, doc: this) => void): this; + init(obj: any, opts?: any, cb?: Callback): this; /** Marks a path as invalid, causing validation to fail. */ invalidate(path: string, errorMsg: string | NativeError, value?: any, kind?: string): NativeError | null; @@ -533,29 +687,36 @@ declare module 'mongoose' { */ overwrite(obj: DocumentDefinition): this; + /** + * If this document is a subdocument or populated document, returns the + * document's parent. Returns undefined otherwise. + */ + $parent(): Document | undefined; + /** * Populates document references, executing the `callback` when complete. * If you want to use promises instead, use this function with * [`execPopulate()`](#document_Document-execPopulate). */ - populate(path: string, callback?: (err: CallbackError, res: this) => void): this; - populate(path: string, names: string, callback?: (err: any, res: this) => void): this; - populate(opts: PopulateOptions | Array, callback?: (err: CallbackError, res: this) => void): this; + populate(path: string, callback?: Callback): this; + populate(path: string, names: string, callback?: Callback): this; + populate(opts: PopulateOptions | Array, callback?: Callback): this; /** Gets _id(s) used during population of the given `path`. If the path was not populated, returns `undefined`. */ populated(path: string): any; /** Removes this document from the db. */ - remove(options?: QueryOptions): Query; - remove(options?: QueryOptions, cb?: (err: CallbackError, res: any) => void): void; + remove(options?: QueryOptions): Promise; + remove(options?: QueryOptions, cb?: Callback): void; /** Sends a replaceOne command with this document `_id` as the query selector. */ - replaceOne(replacement?: DocumentDefinition, options?: QueryOptions | null, callback?: (err: any, res: any) => void): Query; + replaceOne(replacement?: DocumentDefinition, options?: QueryOptions | null, callback?: Callback): Query; + replaceOne(replacement?: Object, options?: QueryOptions | null, callback?: Callback): Query; /** Saves this document by inserting a new document into the database if [document.isNew](/docs/api.html#document_Document-isNew) is `true`, or sends an [updateOne](/docs/api.html#document_Document-updateOne) operation with just the modified paths if `isNew` is `false`. */ save(options?: SaveOptions): Promise; - save(options?: SaveOptions, fn?: (err: CallbackError, doc: this) => void): void; - save(fn?: (err: CallbackError, doc: this) => void): void; + save(options?: SaveOptions, fn?: Callback): void; + save(fn?: Callback): void; /** The document's schema. */ schema: Schema; @@ -567,36 +728,60 @@ declare module 'mongoose' { /** The return value of this method is used in calls to JSON.stringify(doc). */ toJSON(options?: ToObjectOptions): LeanDocument; + toJSON(options?: ToObjectOptions): T; /** Converts this document into a plain-old JavaScript object ([POJO](https://masteringjs.io/tutorials/fundamentals/pojo)). */ toObject(options?: ToObjectOptions): LeanDocument; + toObject(options?: ToObjectOptions): T; /** Clears the modified state on the specified path. */ unmarkModified(path: string): void; /** Sends an update command with this document `_id` as the query selector. */ - update(update?: UpdateQuery, options?: QueryOptions | null, callback?: (err: CallbackError, res: any) => void): Query; + update(update?: UpdateQuery | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback): Query; /** Sends an updateOne command with this document `_id` as the query selector. */ - updateOne(update?: UpdateQuery, options?: QueryOptions | null, callback?: (err: CallbackError, res: any) => void): Query; + updateOne(update?: UpdateQuery | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback): Query; /** Executes registered validation rules for this document. */ - validate(pathsToValidate?: Array, options?: any): Promise; - validate(callback: (err: CallbackError) => void): void; - validate(pathsToValidate: Array, callback: (err: CallbackError) => void): void; - validate(pathsToValidate: Array, options: any, callback: (err: CallbackError) => void): void; + validate(options:{ pathsToSkip?: pathsToSkip }): Promise; + validate(pathsToValidate?: pathsToValidate, options?: any): Promise; + validate(callback: CallbackWithoutResult): void; + validate(pathsToValidate: pathsToValidate, callback: CallbackWithoutResult): void; + validate(pathsToValidate: pathsToValidate, options: any, callback: CallbackWithoutResult): void; /** Executes registered validation rules (skipping asynchronous validators) for this document. */ - validateSync(pathsToValidate?: Array, options?: any): NativeError | null; + validateSync(options:{pathsToSkip?: pathsToSkip, [k:string]: any }): Error.ValidationError | null; + validateSync(pathsToValidate?: Array, options?: any): Error.ValidationError | null; + } + + /** A list of paths to validate. If set, Mongoose will validate only the modified paths that are in the given list. */ + type pathsToValidate = string[] | string; + /** A list of paths to skip. If set, Mongoose will validate every modified path that is not in this list. */ + type pathsToSkip = string[] | string; + + interface AcceptsDiscriminator { + /** Adds a discriminator type. */ + discriminator(name: string | number, schema: Schema, value?: string | number | ObjectId): Model; + discriminator(name: string | number, schema: Schema, value?: string | number | ObjectId): U; + } + + type AnyKeys = { [P in keyof T]?: T[P] | any }; + interface AnyObject { [k: string]: any } + type EnforceDocument = T extends Document ? T : T & Document & TMethods; + + interface IndexesDiff { + /** Indexes that would be created in mongodb. */ + toCreate: Array + /** Indexes that would be dropped in mongodb. */ + toDrop: Array } export const Model: Model; - // eslint-disable-next-line no-undef - interface Model extends NodeJS.EventEmitter { - new(doc?: any): T; + interface Model extends NodeJS.EventEmitter, AcceptsDiscriminator { + new(doc?: AnyKeys & AnyObject): EnforceDocument; aggregate(pipeline?: any[]): Aggregate>; - // eslint-disable-next-line @typescript-eslint/ban-types aggregate(pipeline: any[], cb: Function): Promise>; /** Base Mongoose instance the model uses. */ @@ -616,25 +801,29 @@ declare module 'mongoose' { * trip to MongoDB. */ bulkWrite(writes: Array, options?: mongodb.CollectionBulkWriteOptions): Promise; - bulkWrite(writes: Array, options?: mongodb.CollectionBulkWriteOptions, cb?: (err: any, res: mongodb.BulkWriteOpResultObject) => void): void; + bulkWrite(writes: Array, options?: mongodb.CollectionBulkWriteOptions, cb?: Callback): void; /** Collection the model uses. */ collection: Collection; /** Creates a `count` query: counts the number of documents that match `filter`. */ - count(callback?: (err: any, count: number) => void): Query; - count(filter: FilterQuery, callback?: (err: any, count: number) => void): Query; + count(callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; + count(filter: FilterQuery, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; /** Creates a `countDocuments` query: counts the number of documents that match `filter`. */ - countDocuments(callback?: (err: any, count: number) => void): Query; - countDocuments(filter: FilterQuery, callback?: (err: any, count: number) => void): Query; + countDocuments(callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; + countDocuments(filter: FilterQuery, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; /** Creates a new document or documents */ - create>(doc: Z): Promise; - create>(docs: Array, options?: SaveOptions): Promise>; - create>(...docs: Array): Promise; - create>(doc: Z, callback: (err: CallbackError, doc: T) => void): void; - create>(docs: Array, callback: (err: CallbackError, docs: Array) => void): void; + create(docs: (T | DocumentDefinition | AnyObject)[], options?: SaveOptions): Promise[]>; + create(docs: (T | DocumentDefinition | AnyObject)[], callback: Callback[]>): void; + create(doc: T | DocumentDefinition | AnyObject): Promise>; + create(doc: T | DocumentDefinition | AnyObject, callback: Callback>): void; + create>(docs: DocContents[], options?: SaveOptions): Promise[]>; + create>(docs: DocContents[], callback: Callback[]>): void; + create>(doc: DocContents): Promise>; + create>(...docs: DocContents[]): Promise[]>; + create>(doc: DocContents, callback: Callback>): void; /** * Create the collection for this model. By default, if no indexes are specified, @@ -642,14 +831,14 @@ declare module 'mongoose' { * created. Use this method to create the collection explicitly. */ createCollection(options?: mongodb.CollectionCreateOptions): Promise>; - createCollection(options: mongodb.CollectionCreateOptions | null, callback: (err: CallbackError, collection: mongodb.Collection) => void): void; + createCollection(options: mongodb.CollectionCreateOptions | null, callback: Callback>): void; /** * Similar to `ensureIndexes()`, except for it uses the [`createIndex`](http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#createIndex) * function. */ - createIndexes(callback?: (err: any) => void): Promise; - createIndexes(options?: any, callback?: (err: any) => void): Promise; + createIndexes(callback?: CallbackWithoutResult): Promise; + createIndexes(options?: any, callback?: CallbackWithoutResult): Promise; /** Connection the model uses. */ db: Connection; @@ -659,27 +848,30 @@ declare module 'mongoose' { * Behaves like `remove()`, but deletes all documents that match `conditions` * regardless of the `single` option. */ - deleteMany(filter?: any, options?: QueryOptions, callback?: (err: CallbackError) => void): Query; + deleteMany(filter?: FilterQuery, options?: QueryOptions, callback?: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; + deleteMany(filter: FilterQuery, callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; + deleteMany(callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; /** * Deletes the first document that matches `conditions` from the collection. * Behaves like `remove()`, but deletes at most one document regardless of the * `single` option. */ - deleteOne(filter?: any, options?: QueryOptions, callback?: (err: CallbackError) => void): Query; + deleteOne(filter?: FilterQuery, options?: QueryOptions, callback?: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; + deleteOne(filter: FilterQuery, callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; + deleteOne(callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; /** * Sends `createIndex` commands to mongo for each index declared in the schema. * The `createIndex` commands are sent in series. */ - ensureIndexes(callback?: (err: any) => void): Promise; - ensureIndexes(options?: any, callback?: (err: any) => void): Promise; + ensureIndexes(callback?: CallbackWithoutResult): Promise; + ensureIndexes(options?: any, callback?: CallbackWithoutResult): Promise; /** * Event emitter that reports any errors that occurred. Useful for global error * handling. */ - // eslint-disable-next-line no-undef events: NodeJS.EventEmitter; /** @@ -687,16 +879,16 @@ declare module 'mongoose' { * equivalent to `findOne({ _id: id })`. If you want to query by a document's * `_id`, use `findById()` instead of `findOne()`. */ - findById(id: any, projection?: any | null, options?: QueryOptions | null, callback?: (err: CallbackError, doc: T | null) => void): Query; + findById(id: any, projection?: any | null, options?: QueryOptions | null, callback?: Callback | null>): QueryWithHelpers | null, EnforceDocument, TQueryHelpers, T>; /** Finds one document. */ - findOne(filter?: FilterQuery, projection?: any | null, options?: QueryOptions | null, callback?: (err: CallbackError, doc: T | null) => void): Query; + findOne(filter?: FilterQuery, projection?: any | null, options?: QueryOptions | null, callback?: Callback | null>): QueryWithHelpers | null, EnforceDocument, TQueryHelpers, T>; /** * Shortcut for creating a new Document from existing raw data, pre-saved in the DB. * The document returned has no paths marked as modified initially. */ - hydrate(obj: any): T; + hydrate(obj: any): EnforceDocument; /** * This function is responsible for building [indexes](https://docs.mongodb.com/manual/indexes/), @@ -706,15 +898,15 @@ declare module 'mongoose' { * [`connection.model()`](/docs/api.html#connection_Connection-model), so you * don't need to call it. */ - init(callback?: (err: any) => void): Promise; + init(callback?: CallbackWithoutResult): Promise>; /** Inserts one or more new documents as a single `insertMany` call to the MongoDB server. */ - insertMany(doc: T | DocumentDefinition, options: InsertManyOptions & { rawResult: true }): Promise; - insertMany(doc: T | DocumentDefinition, options?: InsertManyOptions): Promise; - insertMany(docs: Array>, options: InsertManyOptions & { rawResult: true }): Promise; - insertMany(docs: Array>, options?: InsertManyOptions): Promise>; - insertMany(doc: T | DocumentDefinition, options?: InsertManyOptions, callback?: (err: CallbackError, res: T | InsertManyResult) => void): void; - insertMany(docs: Array>, options?: InsertManyOptions, callback?: (err: CallbackError, res: Array | InsertManyResult) => void): void; + insertMany(docs: Array | AnyObject>, options: InsertManyOptions & { rawResult: true }): Promise; + insertMany(docs: Array | AnyObject>, options?: InsertManyOptions): Promise>>; + insertMany(doc: T | DocumentDefinition | AnyObject, options: InsertManyOptions & { rawResult: true }): Promise; + insertMany(doc: T | DocumentDefinition | AnyObject, options?: InsertManyOptions): Promise>; + insertMany(doc: T | DocumentDefinition | AnyObject, options?: InsertManyOptions, callback?: Callback | InsertManyResult>): void; + insertMany(docs: Array | AnyObject>, options?: InsertManyOptions, callback?: Callback> | InsertManyResult>): void; /** * Lists the indexes currently defined in MongoDB. This may or may not be @@ -722,7 +914,7 @@ declare module 'mongoose' { * use the [`autoIndex` option](/docs/guide.html#autoIndex) and if you * build indexes manually. */ - listIndexes(callback: (err: CallbackError, res: Array) => void): void; + listIndexes(callback: Callback>): void; listIndexes(): Promise>; /** The name of the model */ @@ -730,9 +922,9 @@ declare module 'mongoose' { /** Populates document references. */ populate(docs: Array, options: PopulateOptions | Array | string, - callback?: (err: any, res: T[]) => void): Promise>; + callback?: Callback<(EnforceDocument)[]>): Promise>>; populate(doc: any, options: PopulateOptions | Array | string, - callback?: (err: any, res: T) => void): Promise; + callback?: Callback>): Promise>; /** * Makes the indexes in MongoDB match the indexes defined in this model's @@ -741,25 +933,33 @@ declare module 'mongoose' { * are in your schema but not in MongoDB. */ syncIndexes(options?: Record): Promise>; - syncIndexes(options: Record | null, callback: (err: CallbackError, dropped: Array) => void): void; + syncIndexes(options: Record | null, callback: Callback>): void; + + /** + * Does a dry-run of Model.syncIndexes(), meaning that + * the result of this function would be the result of + * Model.syncIndexes(). + */ + diffIndexes(options?: Record): Promise + diffIndexes(options: Record | null, callback: (err: CallbackError, diff: IndexesDiff) => void): void /** * Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions) * for benefits like causal consistency, [retryable writes](https://docs.mongodb.com/manual/core/retryable-writes/), * and [transactions](http://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html). * */ - startSession(options?: mongodb.SessionOptions, cb?: (err: any, session: mongodb.ClientSession) => void): Promise; + startSession(options?: mongodb.SessionOptions, cb?: Callback): Promise; /** Casts and validates the given object against this model's schema, passing the given `context` to custom validators. */ - validate(callback?: (err: any) => void): Promise; - validate(optional: any, callback?: (err: any) => void): Promise; + validate(callback?: CallbackWithoutResult): Promise; + validate(optional: any, callback?: CallbackWithoutResult): Promise; + validate(optional: any, pathsToValidate: string[], callback?: CallbackWithoutResult): Promise; /** Watches the underlying collection for changes using [MongoDB change streams](https://docs.mongodb.com/manual/changeStreams/). */ watch(pipeline?: Array>, options?: mongodb.ChangeStreamOptions): mongodb.ChangeStream; /** Adds a `$where` clause to this query */ - // eslint-disable-next-line @typescript-eslint/ban-types - $where(argument: string | Function): Query, T>; + $where(argument: string | Function): QueryWithHelpers>, EnforceDocument, TQueryHelpers, T>; /** Registered discriminators for this model. */ discriminators: { [name: string]: Model } | undefined; @@ -767,63 +967,64 @@ declare module 'mongoose' { /** Translate any aliases fields/conditions so the final query or document object is pure */ translateAliases(raw: any): any; - /** Adds a discriminator type. */ - discriminator(name: string, schema: Schema, value?: string): Model; - /** Creates a `distinct` query: returns the distinct values of the given `field` that match `filter`. */ - distinct(field: string, filter?: FilterQuery, callback?: (err: any, count: number) => void): Query, T>; + distinct(field: string, filter?: FilterQuery, callback?: Callback): QueryWithHelpers, EnforceDocument, TQueryHelpers, T>; /** Creates a `estimatedDocumentCount` query: counts the number of documents in the collection. */ - estimatedDocumentCount(options?: QueryOptions, callback?: (err: any, count: number) => void): Query; + estimatedDocumentCount(options?: QueryOptions, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; /** * Returns true if at least one document exists in the database that matches * the given `filter`, and false otherwise. */ exists(filter: FilterQuery): Promise; - exists(filter: FilterQuery, callback: (err: any, res: boolean) => void): void; + exists(filter: FilterQuery, callback: Callback): void; /** Creates a `find` query: gets a list of documents that match `filter`. */ - find(callback?: (err: any, docs: T[]) => void): Query, T>; - find(filter: FilterQuery, callback?: (err: any, docs: T[]) => void): Query, T>; - find(filter: FilterQuery, projection?: any | null, options?: QueryOptions | null, callback?: (err: any, docs: T[]) => void): Query, T>; + find(callback?: Callback[]>): QueryWithHelpers>, EnforceDocument, TQueryHelpers, T>; + find(filter: FilterQuery, callback?: Callback): QueryWithHelpers>, EnforceDocument, TQueryHelpers, T>; + find(filter: FilterQuery, projection?: any | null, options?: QueryOptions | null, callback?: Callback[]>): QueryWithHelpers>, EnforceDocument, TQueryHelpers, T>; /** Creates a `findByIdAndDelete` query, filtering by the given `_id`. */ - findByIdAndDelete(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: any, doc: T | null, res: any) => void): Query; + findByIdAndDelete(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: CallbackError, doc: EnforceDocument | null, res: any) => void): QueryWithHelpers | null, EnforceDocument, TQueryHelpers, T>; /** Creates a `findByIdAndRemove` query, filtering by the given `_id`. */ - findByIdAndRemove(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: any, doc: T | null, res: any) => void): Query; + findByIdAndRemove(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: CallbackError, doc: EnforceDocument | null, res: any) => void): QueryWithHelpers | null, EnforceDocument, TQueryHelpers, T>; /** Creates a `findOneAndUpdate` query, filtering by the given `_id`. */ - findByIdAndUpdate(id: mongodb.ObjectId | any, update: UpdateQuery, options: QueryOptions & { upsert: true }, callback?: (err: any, doc: T, res: any) => void): Query; - findByIdAndUpdate(id?: mongodb.ObjectId | any, update?: UpdateQuery, options?: QueryOptions | null, callback?: (err: any, doc: T | null, res: any) => void): Query; + findByIdAndUpdate(id: mongodb.ObjectId | any, update: UpdateQuery | UpdateWithAggregationPipeline, options: QueryOptions & { rawResult: true }, callback?: (err: CallbackError, doc: mongodb.FindAndModifyWriteOpResultObject>, res: any) => void): QueryWithHelpers>, EnforceDocument, TQueryHelpers, T>; + findByIdAndUpdate(id: mongodb.ObjectId | any, update: UpdateQuery | UpdateWithAggregationPipeline, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: EnforceDocument, res: any) => void): QueryWithHelpers, EnforceDocument, TQueryHelpers, T>; + findByIdAndUpdate(id: mongodb.ObjectId | any, update: UpdateQuery | UpdateWithAggregationPipeline, callback?: (err: CallbackError, doc: T | null, res: any) => void): QueryWithHelpers | null, EnforceDocument, TQueryHelpers, T>; + findByIdAndUpdate(id?: mongodb.ObjectId | any, update?: UpdateQuery | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: (err: CallbackError, doc: EnforceDocument | null, res: any) => void): QueryWithHelpers | null, EnforceDocument, TQueryHelpers, T>; /** Creates a `findOneAndDelete` query: atomically finds the given document, deletes it, and returns the document as it was before deletion. */ - findOneAndDelete(filter?: FilterQuery, options?: QueryOptions | null, callback?: (err: any, doc: T | null, res: any) => void): Query; + findOneAndDelete(filter?: FilterQuery, options?: QueryOptions | null, callback?: (err: CallbackError, doc: EnforceDocument | null, res: any) => void): QueryWithHelpers | null, EnforceDocument, TQueryHelpers, T>; /** Creates a `findOneAndRemove` query: atomically finds the given document and deletes it. */ - findOneAndRemove(filter?: FilterQuery, options?: QueryOptions | null, callback?: (err: any, doc: T | null, res: any) => void): Query; + findOneAndRemove(filter?: FilterQuery, options?: QueryOptions | null, callback?: (err: CallbackError, doc: EnforceDocument | null, res: any) => void): QueryWithHelpers | null, EnforceDocument, TQueryHelpers, T>; /** Creates a `findOneAndReplace` query: atomically finds the given document and replaces it with `replacement`. */ - findOneAndReplace(filter: FilterQuery, replacement: DocumentDefinition, options: QueryOptions & { upsert: true }, callback?: (err: any, doc: T, res: any) => void): Query; - findOneAndReplace(filter?: FilterQuery, replacement?: DocumentDefinition, options?: QueryOptions | null, callback?: (err: any, doc: T | null, res: any) => void): Query; + findOneAndReplace(filter: FilterQuery, replacement: DocumentDefinition | AnyObject, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: EnforceDocument, res: any) => void): QueryWithHelpers, EnforceDocument, TQueryHelpers, T>; + findOneAndReplace(filter?: FilterQuery, replacement?: DocumentDefinition | AnyObject, options?: QueryOptions | null, callback?: (err: CallbackError, doc: EnforceDocument | null, res: any) => void): QueryWithHelpers | null, EnforceDocument, TQueryHelpers, T>; /** Creates a `findOneAndUpdate` query: atomically find the first document that matches `filter` and apply `update`. */ - findOneAndUpdate(filter: FilterQuery, update: UpdateQuery, options: QueryOptions & { upsert: true }, callback?: (err: any, doc: T, res: any) => void): Query; - findOneAndUpdate(filter?: FilterQuery, update?: UpdateQuery, options?: QueryOptions | null, callback?: (err: any, doc: T | null, res: any) => void): Query; + findOneAndUpdate(filter: FilterQuery, update: UpdateQuery | UpdateWithAggregationPipeline, options: QueryOptions & { rawResult: true }, callback?: (err: CallbackError, doc: mongodb.FindAndModifyWriteOpResultObject>, res: any) => void): QueryWithHelpers>, EnforceDocument, TQueryHelpers, T>; + findOneAndUpdate(filter: FilterQuery, update: UpdateQuery | UpdateWithAggregationPipeline, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: EnforceDocument, res: any) => void): QueryWithHelpers, EnforceDocument, TQueryHelpers, T>; + findOneAndUpdate(filter?: FilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: (err: CallbackError, doc: EnforceDocument | null, res: any) => void): QueryWithHelpers | null, EnforceDocument, TQueryHelpers, T>; - geoSearch(filter?: FilterQuery, options?: GeoSearchOptions, callback?: (err: CallbackError, res: Array) => void): Query, T>; + geoSearch(filter?: FilterQuery, options?: GeoSearchOptions, callback?: Callback>>): QueryWithHelpers>, EnforceDocument, TQueryHelpers, T>; /** Executes a mapReduce command. */ mapReduce( o: MapReduceOptions, - callback?: (err: any, res: any) => void + callback?: Callback ): Promise; - remove(filter?: any, callback?: (err: CallbackError) => void): Query; + remove(filter?: any, callback?: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; /** Creates a `replaceOne` query: finds the first document that matches `filter` and replaces it with `replacement`. */ - replaceOne(filter?: FilterQuery, replacement?: DocumentDefinition, options?: QueryOptions | null, callback?: (err: any, res: any) => void): Query; + replaceOne(filter?: FilterQuery, replacement?: DocumentDefinition, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; + replaceOne(filter?: FilterQuery, replacement?: Object, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; /** Schema the model uses. */ schema: Schema; @@ -832,16 +1033,23 @@ declare module 'mongoose' { * @deprecated use `updateOne` or `updateMany` instead. * Creates a `update` query: updates one or many documents that match `filter` with `update`, based on the `multi` option. */ - update(filter?: FilterQuery, update?: UpdateQuery, options?: QueryOptions | null, callback?: (err: any, res: any) => void): Query; + update(filter?: FilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; /** Creates a `updateMany` query: updates all documents that match `filter` with `update`. */ - updateMany(filter?: FilterQuery, update?: UpdateQuery, options?: QueryOptions | null, callback?: (err: any, res: any) => void): Query; + updateMany(filter?: FilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; /** Creates a `updateOne` query: updates the first document that matches `filter` with `update`. */ - updateOne(filter?: FilterQuery, update?: UpdateQuery, options?: QueryOptions | null, callback?: (err: any, res: any) => void): Query; + updateOne(filter?: FilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; /** Creates a Query, applies the passed conditions, and returns the Query. */ - where(path: string, val?: any): Query, T>; + where(path: string, val?: any): QueryWithHelpers>, EnforceDocument, TQueryHelpers, T>; + where(obj: object): QueryWithHelpers>, EnforceDocument, TQueryHelpers, T>; + where(): QueryWithHelpers>, EnforceDocument, TQueryHelpers, T>; + } + + type _UpdateWriteOpResult = mongodb.UpdateWriteOpResult['result']; + interface UpdateWriteOpResult extends _UpdateWriteOpResult { + upserted?: Array<{index: number, _id: any}>; } interface QueryOptions { @@ -871,7 +1079,7 @@ declare module 'mongoose' { omitUndefined?: boolean; overwrite?: boolean; overwriteDiscriminatorKey?: boolean; - populate?: string; + populate?: string | string[] | PopulateOptions | PopulateOptions[]; projection?: any; /** * if true, returns the raw result from the MongoDB driver @@ -882,7 +1090,12 @@ declare module 'mongoose' { * An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`. */ returnOriginal?: boolean; + /** + * Another alias for the `new` option. `returnOriginal` is deprecated so this should be used. + */ + returnDocument?: string; runValidators?: boolean; + sanitizeProjection?: boolean; /** The session associated with this query. */ session?: mongodb.ClientSession; setDefaultsOnInsert?: boolean; @@ -903,7 +1116,7 @@ declare module 'mongoose' { writeConcern?: any; } - type MongooseQueryOptions = Pick; + type MongooseQueryOptions = Pick; interface SaveOptions { checkKeys?: boolean; @@ -941,7 +1154,6 @@ declare module 'mongoose' { } interface MapReduceOptions { - // eslint-disable-next-line @typescript-eslint/ban-types map: Function | string; reduce: (key: Key, vals: T[]) => Val; /** query filter object. */ @@ -1012,13 +1224,15 @@ declare module 'mongoose' { * always set `path` to a document. Inferred from schema by default. */ justOne?: boolean; + /** transform function to call on every populated doc */ + transform?: (doc: any, id: any) => any; } interface ToObjectOptions { /** apply all getters (path and virtual getters) */ getters?: boolean; /** apply virtual getters (can override getters option) */ - virtuals?: boolean; + virtuals?: boolean | string[]; /** if `options.virtuals = true`, you can set `options.aliases = false` to skip applying aliases. This option is a no-op if `options.virtuals = false`. */ aliases?: boolean; /** remove empty objects (defaults to true) */ @@ -1035,16 +1249,31 @@ declare module 'mongoose' { useProjection?: boolean; } - type MongooseQueryMiddleware = 'count' | 'deleteMany' | 'deleteOne' | 'find' | 'findOne' | 'findOneAndDelete' | 'findOneAndRemove' | 'findOneAndUpdate' | 'remove' | 'update' | 'updateOne' | 'updateMany'; + type MongooseDocumentMiddleware = 'validate' | 'save' | 'remove' | 'updateOne' | 'deleteOne' | 'init'; + type MongooseQueryMiddleware = 'count' | 'deleteMany' | 'deleteOne' | 'distinct' | 'find' | 'findOne' | 'findOneAndDelete' | 'findOneAndRemove' | 'findOneAndUpdate' | 'remove' | 'update' | 'updateOne' | 'updateMany'; + + type SchemaPreOptions = { document?: boolean, query?: boolean }; + type SchemaPostOptions = { document?: boolean, query?: boolean }; + + type ExtractMethods = M extends Model ? TMethods : {}; + type ExtractQueryHelpers = M extends Model ? TQueryHelpers : {}; - class Schema = Model> extends events.EventEmitter { + type IndexDirection = 1 | -1 | '2d' | '2dsphere' | 'geoHaystack' | 'hashed' | 'text'; + type IndexDefinition = Record; + + type PreMiddlewareFunction = (this: T, next: (err?: CallbackError) => void) => void | Promise; + type PreSaveMiddlewareFunction = (this: T, next: (err?: CallbackError) => void, opts: SaveOptions) => void | Promise; + type PostMiddlewareFunction = (this: ThisType, res: ResType, next: (err?: CallbackError) => void) => void | Promise; + type ErrorHandlingMiddlewareFunction = (this: ThisType, err: NativeError, res: ResType, next: (err?: CallbackError) => void) => void; + + class Schema, SchemaDefinitionType = undefined, TInstanceMethods = {}> extends events.EventEmitter { /** * Create a new schema */ - constructor(definition?: SchemaDefinition, options?: SchemaOptions); + constructor(definition?: SchemaDefinition>, options?: SchemaOptions); /** Adds key path / schema type pairs to this schema. */ - add(obj: SchemaDefinition | Schema, prefix?: string): this; + add(obj: SchemaDefinition> | Schema, prefix?: string): this; /** * Array of child schemas (from document arrays and single nested subdocs) @@ -1063,16 +1292,16 @@ declare module 'mongoose' { eachPath(fn: (path: string, type: SchemaType) => void): this; /** Defines an index (most likely compound) for this schema. */ - index(fields: any, options?: any): this; + index(fields: IndexDefinition, options?: IndexOptions): this; /** * Returns a list of indexes that this schema declares, via `schema.index()` * or by `index: true` in a path's options. */ - indexes(): Array; + indexes(): Array; /** Gets a schema option. */ - get(path: string): any; + get(key: K): SchemaOptions[K]; /** * 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), @@ -1081,22 +1310,20 @@ declare module 'mongoose' { * [statics](http://mongoosejs.com/docs/guide.html#statics), and * [methods](http://mongoosejs.com/docs/guide.html#methods). */ - // eslint-disable-next-line @typescript-eslint/ban-types - loadClass(model: Function): this; + loadClass(model: Function, onlyVirtuals?: boolean): this; /** Adds an instance method to documents constructed from Models compiled from this schema. */ - // eslint-disable-next-line @typescript-eslint/ban-types - method(name: string, fn: (this: DocType, ...args: any[]) => any, opts?: any): this; - method(obj: { [name: string]: (this: DocType, ...args: any[]) => any }): this; + method(name: string, fn: (this: EnforceDocument, ...args: any[]) => any, opts?: any): this; + method(obj: { [name: string]: (this: EnforceDocument, ...args: any[]) => any }): this; /** Object of currently defined methods on this schema. */ - methods: { [F in keyof DocType]: DocType[F] } & { [name: string]: (this: DocType, ...args: any[]) => any }; + methods: { [name: string]: (this: EnforceDocument, ...args: any[]) => any }; /** The original object passed to the schema constructor */ obj: any; /** Gets/sets schema paths. */ - path(path: string): SchemaType; + path(path: string): ResultType; path(path: string, constructor: any): this; /** Lists all paths and their type in the schema. */ @@ -1108,27 +1335,40 @@ declare module 'mongoose' { pathType(path: string): string; /** Registers a plugin for this schema. */ - plugin(fn: (schema: Schema, opts?: any) => void, opts?: any): this; + plugin(fn: (schema: Schema, SchemaDefinitionType>, opts?: any) => void, opts?: any): this; /** Defines a post hook for the model. */ - post(method: 'validate' | 'save' | 'remove' | 'updateOne' | 'deleteOne' | 'init' | RegExp, fn: (this: T, res: any, next: (err?: CallbackError) => void) => void): this; - post = Query>(method: MongooseQueryMiddleware | string | RegExp, fn: (this: T, res: any, next: (err: CallbackError) => void) => void): this; - post = Aggregate>(method: 'aggregate' | RegExp, fn: (this: T, res: Array, next: (err: CallbackError) => void) => void): this; - post = M>(method: 'insertMany' | RegExp, fn: (this: T, res: any, next: (err: CallbackError) => void) => void): this; - - post(method: 'validate' | 'save' | 'remove' | 'updateOne' | 'deleteOne' | 'init' | RegExp, fn: (this: T, err: NativeError, res: any, next: (err?: CallbackError) => void) => void): this; - post = Query>(method: MongooseQueryMiddleware | string | RegExp, fn: (this: T, err: NativeError, res: any, next: (err: CallbackError) => void) => void): this; - post = Aggregate>(method: 'aggregate' | RegExp, fn: (this: T, err: NativeError, res: Array, next: (err: CallbackError) => void) => void): this; - post = M>(method: 'insertMany' | RegExp, fn: (this: T, err: NativeError, res: any, next: (err: CallbackError) => void) => void): this; + post>(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, fn: PostMiddlewareFunction): this; + post>(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, options: SchemaPostOptions, fn: PostMiddlewareFunction): this; + post = Query>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, fn: PostMiddlewareFunction): this; + post = Query>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, options: SchemaPostOptions, fn: PostMiddlewareFunction): this; + post = Aggregate>(method: 'aggregate' | RegExp, fn: PostMiddlewareFunction>): this; + post = Aggregate>(method: 'aggregate' | RegExp, options: SchemaPostOptions, fn: PostMiddlewareFunction>): this; + post(method: 'insertMany' | RegExp, fn: PostMiddlewareFunction): this; + post(method: 'insertMany' | RegExp, options: SchemaPostOptions, fn: PostMiddlewareFunction): this; + + post>(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, fn: ErrorHandlingMiddlewareFunction): this; + post>(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, options: SchemaPostOptions, fn: ErrorHandlingMiddlewareFunction): this; + post = Query>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, fn: ErrorHandlingMiddlewareFunction): this; + post = Query>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, options: SchemaPostOptions, fn: ErrorHandlingMiddlewareFunction): this; + post = Aggregate>(method: 'aggregate' | RegExp, fn: ErrorHandlingMiddlewareFunction>): this; + post = Aggregate>(method: 'aggregate' | RegExp, options: SchemaPostOptions, fn: ErrorHandlingMiddlewareFunction>): this; + post(method: 'insertMany' | RegExp, fn: ErrorHandlingMiddlewareFunction): this; + post(method: 'insertMany' | RegExp, options: SchemaPostOptions, fn: ErrorHandlingMiddlewareFunction): this; /** Defines a pre hook for the model. */ - pre(method: 'validate' | 'save' | 'remove' | 'updateOne' | 'deleteOne' | 'init' | RegExp, fn: (this: T, next: (err?: CallbackError) => void) => void): this; - pre = Query>(method: MongooseQueryMiddleware | string | RegExp, fn: (this: T, next: (err: CallbackError) => void) => void): this; - pre = Aggregate>(method: 'aggregate' | RegExp, fn: (this: T, next: (err: CallbackError) => void) => void): this; - pre = M>(method: 'insertMany' | RegExp, fn: (this: T, next: (err: CallbackError) => void) => void): this; + pre>(method: 'save', fn: PreSaveMiddlewareFunction): this; + pre>(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, fn: PreMiddlewareFunction): this; + pre>(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, options: SchemaPreOptions, fn: PreMiddlewareFunction): this; + pre = Query>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, fn: PreMiddlewareFunction): this; + pre = Query>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, options: SchemaPreOptions, fn: PreMiddlewareFunction): this; + pre = Aggregate>(method: 'aggregate' | RegExp, fn: PreMiddlewareFunction): this; + pre = Aggregate>(method: 'aggregate' | RegExp, options: SchemaPreOptions, fn: PreMiddlewareFunction): this; + pre(method: 'insertMany' | RegExp, fn: (this: T, next: (err?: CallbackError) => void, docs: any | Array) => void | Promise): this; + pre(method: 'insertMany' | RegExp, options: SchemaPreOptions, fn: (this: T, next: (err?: CallbackError) => void, docs: any | Array) => void | Promise): this; /** Object of currently defined query helpers on this schema. */ - query: any; + query: { [name: string]: (this: QueryWithHelpers>, ...args: any[]) => any }; /** Adds a method call to the queue. */ queue(name: string, args: any[]): this; @@ -1140,19 +1380,17 @@ declare module 'mongoose' { requiredPaths(invalidate?: boolean): string[]; /** Sets a schema option. */ - set(path: string, value: any, _tags?: any): this; + set(key: K, value: SchemaOptions[K], _tags?: any): this; /** Adds static "class" methods to Models compiled from this schema. */ - // eslint-disable-next-line @typescript-eslint/ban-types static(name: string, fn: (this: M, ...args: any[]) => any): this; - // eslint-disable-next-line @typescript-eslint/ban-types static(obj: { [name: string]: (this: M, ...args: any[]) => any }): this; /** Object of currently defined statics on this schema. */ - statics: { [F in keyof M]: M[F] } & { [name: string]: (this: M, ...args: any[]) => any }; + statics: { [name: string]: (this: M, ...args: any[]) => any }; /** Creates a virtual type with the given name. */ - virtual(name: string, options?: any): VirtualType; + virtual(name: string, options?: VirtualTypeOptions): VirtualType; /** Object of currently defined virtuals on this schema */ virtuals: any; @@ -1161,11 +1399,33 @@ declare module 'mongoose' { virtualpath(name: string): VirtualType | null; } - type SchemaDefinitionProperty = SchemaTypeOptions | Function | string | Schema | Schema[] | Array> | Function[] | SchemaDefinition | SchemaDefinition[]; + type SchemaDefinitionWithBuiltInClass = T extends number + ? (typeof Number | 'number' | 'Number' | typeof Schema.Types.Number) + : T extends string + ? (typeof String | 'string' | 'String' | typeof Schema.Types.String) + : T extends boolean + ? (typeof Boolean | 'boolean' | 'Boolean' | typeof Schema.Types.Boolean) + : T extends NativeDate + ? (typeof NativeDate | 'date' | 'Date' | typeof Schema.Types.Date) + : (Function | string); + + type SchemaDefinitionProperty = T extends string | number | boolean | NativeDate | Function + ? (SchemaDefinitionWithBuiltInClass | SchemaTypeOptions) : + SchemaTypeOptions | + typeof SchemaType | + Schema | + Schema[] | + ReadonlyArray> | + SchemaTypeOptions[] | + ReadonlyArray> | + Function[] | + SchemaDefinition | + SchemaDefinition[] | + ReadonlyArray>; type SchemaDefinition = T extends undefined ? { [path: string]: SchemaDefinitionProperty; } - : { [path in keyof T]-?: SchemaDefinitionProperty; }; + : { [path in keyof T]?: SchemaDefinitionProperty; }; interface SchemaOptions { /** @@ -1326,17 +1586,28 @@ declare module 'mongoose' { interface SchemaTimestampsConfig { createdAt?: boolean | string; updatedAt?: boolean | string; - currentTime?: () => (Date | number); + currentTime?: () => (NativeDate | number); } - interface SchemaTypeOptions { - type?: T; + type AnyArray = T[] | ReadonlyArray; + + type Unpacked = T extends (infer U)[] ? U : T; + + export class SchemaTypeOptions { + type?: + T extends string | number | boolean | NativeDate | Function ? SchemaDefinitionWithBuiltInClass : + T extends Schema ? T : + T extends object[] ? (AnyArray>> | AnyArray>> | AnyArray>>) : + T extends string[] ? (SchemaDefinitionWithBuiltInClass[] | ReadonlyArray>) : + T extends number[] ? (SchemaDefinitionWithBuiltInClass[] | ReadonlyArray>) : + T extends boolean[] ? (SchemaDefinitionWithBuiltInClass[] | ReadonlyArray>) : + T extends Function[] ? (SchemaDefinitionWithBuiltInClass[] | ReadonlyArray>) : + T | typeof SchemaType | Schema; /** Defines a virtual with the given name that gets/sets this path. */ alias?: string; /** Function or object describing how to validate this schematype. See [validation docs](https://mongoosejs.com/docs/validation.html). */ - // eslint-disable-next-line @typescript-eslint/ban-types validate?: RegExp | [RegExp, string] | Function | [Function, string] | ValidateOpts | ValidateOpts[]; /** Allows overriding casting logic for this individual path. If a string, the given string overwrites Mongoose's default cast error message. */ @@ -1347,7 +1618,7 @@ declare module 'mongoose' { * path cannot be set to a nullish value. If a function, Mongoose calls the * function and only checks for nullish values if the function returns a truthy value. */ - required?: boolean | (() => boolean) | [boolean, string]; + required?: boolean | (() => boolean) | [boolean, string] | [() => boolean, string]; /** * The default value for this path. If a function, Mongoose executes the function @@ -1370,7 +1641,7 @@ declare module 'mongoose' { * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose will * build an index on this path when the model is compiled. */ - index?: boolean | number | IndexOptions; + index?: boolean | number | IndexOptions | '2d' | '2dsphere' | 'hashed' | 'text'; /** * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose @@ -1411,19 +1682,19 @@ declare module 'mongoose' { set?: (value: T, schematype?: this) => any; /** array of allowed values for this path. Allowed for strings, numbers, and arrays of strings */ - enum?: Array + enum?: Array | ReadonlyArray | { values: Array | ReadonlyArray, message?: string } | { [path: string]: string | number | null }; /** The default [subtype](http://bsonspec.org/spec.html) associated with this buffer when it is stored in MongoDB. Only allowed for buffer paths */ subtype?: number /** The minimum value allowed for this path. Only allowed for numbers and dates. */ - min?: number | Date | [number, string] | [Date, string]; + min?: number | NativeDate | [number, string] | [NativeDate, string] | readonly [number, string] | readonly [NativeDate, string]; /** The maximum value allowed for this path. Only allowed for numbers and dates. */ - max?: number | Date | [number, string] | [Date, string]; + max?: number | NativeDate | [number, string] | [NativeDate, string] | readonly [number, string] | readonly [NativeDate, string]; /** Defines a TTL index on this path. Only allowed for dates. */ - expires?: number | Date; + expires?: string | number; /** If `true`, Mongoose will skip gathering indexes on subpaths. Only allowed for subdocuments and subdocument arrays. */ excludeIndexes?: boolean; @@ -1432,8 +1703,7 @@ declare module 'mongoose' { _id?: boolean; /** If set, specifies the type of this map's values. Mongoose will cast this map's values to the given type. */ - // eslint-disable-next-line @typescript-eslint/ban-types - of?: Function | SchemaTypeOptions; + of?: Function | SchemaDefinitionProperty; /** If true, uses Mongoose's default `_id` settings. Only allowed for ObjectIds */ auto?: boolean; @@ -1451,20 +1721,37 @@ declare module 'mongoose' { uppercase?: boolean; /** If set, Mongoose will add a custom validator that ensures the given string's `length` is at least the given number. */ - minlength?: number | [number, string]; + minlength?: number | [number, string] | readonly [number, string]; /** If set, Mongoose will add a custom validator that ensures the given string's `length` is at most the given number. */ - maxlength?: number | [number, string]; + maxlength?: number | [number, string] | readonly [number, string]; [other: string]: any; } - interface IndexOptions { - background?: boolean, - expires?: number | string - sparse?: boolean, - type?: string, - unique?: boolean + export type RefType = + | number + | string + | Buffer + | undefined + | mongoose.Types.ObjectId + | mongoose.Types.Buffer + | typeof mongoose.Schema.Types.Number + | typeof mongoose.Schema.Types.String + | typeof mongoose.Schema.Types.Buffer + | typeof mongoose.Schema.Types.ObjectId; + + /** + * Reference another Model + */ + export type PopulatedDoc< + PopulatedType, + RawId extends RefType = (PopulatedType extends { _id?: RefType; } ? NonNullable : mongoose.Types.ObjectId) | undefined + > = PopulatedType | RawId; + + interface IndexOptions extends mongodb.IndexOptions { + expires?: number | string; + weights?: AnyObject; } interface ValidatorProps { @@ -1495,29 +1782,80 @@ declare module 'mongoose' { validator: ValidateFn | LegacyAsyncValidateFn | AsyncValidateFn; } + interface VirtualTypeOptions { + /** If `ref` is not nullish, this becomes a populated virtual. */ + ref?: string | Function; + + /** The local field to populate on if this is a populated virtual. */ + localField?: string | Function; + + /** The foreign field to populate on if this is a populated virtual. */ + foreignField?: string | Function; + + /** + * By default, a populated virtual is an array. If you set `justOne`, + * the populated virtual will be a single doc or `null`. + */ + justOne?: boolean; + + /** If you set this to `true`, Mongoose will call any custom getters you defined on this virtual. */ + getters?: boolean; + + /** + * 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()`. + */ + count?: boolean; + + /** Add an extra match condition to `populate()`. */ + match?: FilterQuery | Function; + + /** Add a default `limit` to the `populate()` query. */ + limit?: number; + + /** Add a default `skip` to the `populate()` query. */ + skip?: number; + + /** + * For legacy reasons, `limit` with `populate()` may give incorrect results because it only + * executes a single query for every document being populated. If you set `perDocumentLimit`, + * Mongoose will ensure correct `limit` per document by executing a separate query for each + * document to `populate()`. For example, `.find().populate({ path: 'test', perDocumentLimit: 2 })` + * will execute 2 additional queries if `.find()` returns 2 documents. + */ + perDocumentLimit?: number; + + /** Additional options like `limit` and `lean`. */ + options?: QueryOptions & { match?: AnyObject }; + + /** Additional options for plugins */ + [extra: string]: any; + } + class VirtualType { /** Applies getters to `value`. */ applyGetters(value: any, doc: Document): any; + /** Applies setters to `value`. */ applySetters(value: any, doc: Document): any; /** Adds a custom getter to this virtual. */ - // eslint-disable-next-line @typescript-eslint/ban-types get(fn: Function): this; + /** Adds a custom setter to this virtual. */ - // eslint-disable-next-line @typescript-eslint/ban-types set(fn: Function): this; } namespace Schema { namespace Types { - class Array extends SchemaType { + class Array extends SchemaType implements AcceptsDiscriminator { /** This schema type's name, to defend against minifiers that mangle function names. */ static schemaName: string; static options: { castNonArrays: boolean; }; - discriminator(name: string, schema: Schema, tag?: string): any; + discriminator(name: string | number, schema: Schema, value?: string): Model; + discriminator(name: string | number, schema: Schema, value?: string): U; /** * Adds an enum validator if this is an array of strings or numbers. Equivalent to @@ -1556,10 +1894,10 @@ declare module 'mongoose' { expires(when: number | string): this; /** Sets a maximum date validator. */ - max(value: Date, message: string): this; + max(value: NativeDate, message: string): this; /** Sets a minimum date validator. */ - min(value: Date, message: string): this; + min(value: NativeDate, message: string): this; } class Decimal128 extends SchemaType { @@ -1567,13 +1905,14 @@ declare module 'mongoose' { static schemaName: string; } - class DocumentArray extends SchemaType { + class DocumentArray extends SchemaType implements AcceptsDiscriminator { /** This schema type's name, to defend against minifiers that mangle function names. */ static schemaName: string; static options: { castNonArrays: boolean; }; - discriminator(name: string, schema: Schema, tag?: string): any; + discriminator(name: string | number, schema: Schema, value?: string): Model; + discriminator(name: string | number, schema: Schema, value?: string): U; /** The schema used for documents in this array */ schema: Schema; @@ -1611,12 +1950,15 @@ declare module 'mongoose' { auto(turnOn: boolean): this; } - class Embedded extends SchemaType { + class Embedded extends SchemaType implements AcceptsDiscriminator { /** This schema type's name, to defend against minifiers that mangle function names. */ static schemaName: string; /** The document's schema */ schema: Schema; + + discriminator(name: string | number, schema: Schema, value?: string): Model; + discriminator(name: string | number, schema: Schema, value?: string): U; } class String extends SchemaType { @@ -1686,6 +2028,7 @@ declare module 'mongoose' { /** Returns a native js Array. */ toObject(options?: ToObjectOptions): any; + toObject(options?: ToObjectOptions): T; /** Wraps [`Array#unshift`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/unshift) with proper change tracking. */ unshift(...args: any[]): number; @@ -1712,6 +2055,8 @@ declare module 'mongoose' { /** Searches array items for the first document with a matching _id. */ id(id: any): T | null; + + push(...args: (AnyKeys & AnyObject)[]): number; } class EmbeddedDocument extends Document { @@ -1721,6 +2066,9 @@ declare module 'mongoose' { /** Returns this sub-documents parent document. */ parent(): Document; + /** Returns this sub-documents parent document. */ + $parent(): Document; + /** Returns this sub-documents parent array. */ parentArray(): DocumentArray; } @@ -1754,27 +2102,40 @@ declare module 'mongoose' { /** Returns this sub-documents parent document. */ parent(): Document; + + /** Returns this sub-documents parent document. */ + $parent(): Document; } } - interface Query { + type ReturnsNewDoc = { new: true } | { returnOriginal: false } | {returnDocument: 'after'}; + + type QueryWithHelpers = Query & THelpers; + + class Query { _mongooseOptions: MongooseQueryOptions; + /** + * Returns a wrapper around a [mongodb driver cursor](http://mongodb.github.io/node-mongodb-native/2.1/api/Cursor.html). + * A QueryCursor exposes a Streams3 interface, as well as a `.next()` function. + * This is equivalent to calling `.cursor()` with no arguments. + */ + [Symbol.asyncIterator](): AsyncIterableIterator; + /** Executes the query */ exec(): Promise; - exec(callback?: (err: CallbackError, res: ResultType) => void): void; + exec(callback?: Callback): void; // @todo: this doesn't seem right - exec(callback?: (err: any, result: ResultType) => void): Promise | any; + exec(callback?: Callback): Promise | any; - // eslint-disable-next-line @typescript-eslint/ban-types - $where(argument: string | Function): Query, DocType>; + $where(argument: string | Function): QueryWithHelpers; /** Specifies an `$all` query condition. When called with one argument, the most recent path passed to `where()` is used. */ all(val: Array): this; all(path: string, val: Array): this; /** Specifies arguments for an `$and` condition. */ - and(array: Array>): this; + and(array: FilterQuery[]): this; /** Specifies the batchSize option. */ batchSize(val: number): this; @@ -1783,7 +2144,14 @@ declare module 'mongoose' { box(val: any): this; box(lower: number[], upper: number[]): this; - cast(model: Model | null, obj: any): UpdateQuery; + /** + * Casts this query to the schema of `model`. + * + * @param {Model} [model] the model to cast to. If not set, defaults to `this.model` + * @param {Object} [obj] If not set, defaults to this query's conditions + * @return {Object} the casted `obj` + */ + cast(model?: Model | null, obj?: any): any; /** * Executes the query returning a `Promise` which will be @@ -1803,12 +2171,12 @@ declare module 'mongoose' { comment(val: string): this; /** Specifies this query as a `count` query. */ - count(callback?: (err: any, count: number) => void): Query; - count(criteria: FilterQuery, callback?: (err: any, count: number) => void): Query; + count(callback?: Callback): QueryWithHelpers; + count(criteria: FilterQuery, callback?: Callback): QueryWithHelpers; /** Specifies this query as a `countDocuments` query. */ - countDocuments(callback?: (err: any, count: number) => void): Query; - countDocuments(criteria: FilterQuery, callback?: (err: any, count: number) => void): Query; + countDocuments(callback?: Callback): QueryWithHelpers; + countDocuments(criteria: FilterQuery, callback?: Callback): QueryWithHelpers; /** * Returns a wrapper around a [mongodb driver cursor](http://mongodb.github.io/node-mongodb-native/2.1/api/Cursor.html). @@ -1821,22 +2189,24 @@ declare module 'mongoose' { * remove, except it deletes _every_ document that matches `filter` in the * collection, regardless of the value of `single`. */ - deleteMany(filter?: FilterQuery, options?: QueryOptions, callback?: (err: CallbackError, res: any) => void): Query; + deleteMany(filter?: FilterQuery, options?: QueryOptions, callback?: Callback): QueryWithHelpers; + deleteMany(filter: FilterQuery, callback: Callback): QueryWithHelpers; + deleteMany(callback: Callback): QueryWithHelpers; /** * Declare and/or execute this query as a `deleteOne()` operation. Works like * remove, except it deletes at most one document regardless of the `single` * option. */ - deleteOne(filter?: FilterQuery, options?: QueryOptions, callback?: (err: CallbackError, res: any) => void): Query; + deleteOne(filter?: FilterQuery, options?: QueryOptions, callback?: Callback): QueryWithHelpers; + deleteOne(filter: FilterQuery, callback: Callback): QueryWithHelpers; + deleteOne(callback: Callback): QueryWithHelpers; /** Creates a `distinct` query: returns the distinct values of the given `field` that match `filter`. */ - distinct(field: string, filter?: FilterQuery, callback?: (err: any, count: number) => void): Query, DocType>; + distinct(field: string, filter?: FilterQuery, callback?: Callback): QueryWithHelpers, DocType, THelpers, RawDocType>; /** Specifies a `$elemMatch` query condition. When called with one argument, the most recent path passed to `where()` is used. */ - // eslint-disable-next-line @typescript-eslint/ban-types elemMatch(val: Function | any): this; - // eslint-disable-next-line @typescript-eslint/ban-types elemMatch(path: string, val: Function | any): this; /** @@ -1850,7 +2220,7 @@ declare module 'mongoose' { equals(val: any): this; /** Creates a `estimatedDocumentCount` query: counts the number of documents in the collection. */ - estimatedDocumentCount(options?: QueryOptions, callback?: (err: any, count: number) => void): Query; + estimatedDocumentCount(options?: QueryOptions, callback?: Callback): QueryWithHelpers; /** Specifies a `$exists` query condition. When called with one argument, the most recent path passed to `where()` is used. */ exists(val: boolean): this; @@ -1865,29 +2235,31 @@ declare module 'mongoose' { explain(verbose?: string): this; /** Creates a `find` query: gets a list of documents that match `filter`. */ - find(callback?: (err: any, docs: DocType[]) => void): Query, DocType>; - find(filter: FilterQuery, callback?: (err: any, docs: DocType[]) => void): Query, DocType>; - find(filter: FilterQuery, projection?: any | null, options?: QueryOptions | null, callback?: (err: CallbackError, docs: DocType[]) => void): Query, DocType>; + find(callback?: Callback): QueryWithHelpers, DocType, THelpers, RawDocType>; + find(filter: FilterQuery, callback?: Callback): QueryWithHelpers, DocType, THelpers, RawDocType>; + find(filter: FilterQuery, projection?: any | null, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers, DocType, THelpers, RawDocType>; /** Declares the query a findOne operation. When executed, the first found document is passed to the callback. */ - findOne(filter?: FilterQuery, projection?: any | null, options?: QueryOptions | null, callback?: (err: CallbackError, doc: DocType | null) => void): Query; + findOne(filter?: FilterQuery, projection?: any | null, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers; /** Creates a `findOneAndDelete` query: atomically finds the given document, deletes it, and returns the document as it was before deletion. */ - findOneAndDelete(filter?: FilterQuery, options?: QueryOptions | null, callback?: (err: any, doc: DocType | null, res: any) => void): Query; + findOneAndDelete(filter?: FilterQuery, options?: QueryOptions | null, callback?: (err: CallbackError, doc: DocType | null, res: any) => void): QueryWithHelpers; /** Creates a `findOneAndRemove` query: atomically finds the given document and deletes it. */ - findOneAndRemove(filter?: FilterQuery, options?: QueryOptions | null, callback?: (err: any, doc: DocType | null, res: any) => void): Query; + findOneAndRemove(filter?: FilterQuery, options?: QueryOptions | null, callback?: (err: CallbackError, doc: DocType | null, res: any) => void): QueryWithHelpers; /** Creates a `findOneAndUpdate` query: atomically find the first document that matches `filter` and apply `update`. */ - findOneAndUpdate(filter: FilterQuery, update: UpdateQuery, options: QueryOptions & { upsert: true }, callback?: (err: any, doc: DocType, res: any) => void): Query; - findOneAndUpdate(filter?: FilterQuery, update?: UpdateQuery, options?: QueryOptions | null, callback?: (err: any, doc: DocType | null, res: any) => void): Query; + findOneAndUpdate(filter: FilterQuery, update: UpdateQuery | UpdateWithAggregationPipeline, options: QueryOptions & { rawResult: true }, callback?: (err: CallbackError, doc: mongodb.FindAndModifyWriteOpResultObject, res: any) => void): QueryWithHelpers, DocType, THelpers, RawDocType>; + findOneAndUpdate(filter: FilterQuery, update: UpdateQuery | UpdateWithAggregationPipeline, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: DocType, res: any) => void): QueryWithHelpers; + findOneAndUpdate(filter?: FilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: (err: CallbackError, doc: DocType | null, res: any) => void): QueryWithHelpers; /** Creates a `findByIdAndDelete` query, filtering by the given `_id`. */ - findByIdAndDelete(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: any, doc: DocType | null, res: any) => void): Query; + findByIdAndDelete(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: CallbackError, doc: DocType | null, res: any) => void): QueryWithHelpers; /** Creates a `findOneAndUpdate` query, filtering by the given `_id`. */ - findByIdAndUpdate(id: mongodb.ObjectId | any, update: UpdateQuery, options: QueryOptions & { upsert: true }, callback?: (err: any, doc: DocType, res: any) => void): Query; - findByIdAndUpdate(id?: mongodb.ObjectId | any, update?: UpdateQuery, options?: QueryOptions | null, callback?: (err: any, doc: DocType | null, res: any) => void): Query; + findByIdAndUpdate(id: mongodb.ObjectId | any, update: UpdateQuery | UpdateWithAggregationPipeline, options: QueryOptions & { rawResult: true }, callback?: (err: CallbackError, doc: mongodb.FindAndModifyWriteOpResultObject, res: any) => void): QueryWithHelpers, DocType, THelpers, RawDocType>; + findByIdAndUpdate(id: mongodb.ObjectId | any, update: UpdateQuery | UpdateWithAggregationPipeline, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: DocType, res: any) => void): QueryWithHelpers; + findByIdAndUpdate(id?: mongodb.ObjectId | any, update?: UpdateQuery | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: (err: CallbackError, doc: DocType | null, res: any) => void): QueryWithHelpers; /** Specifies a `$geometry` condition */ geometry(object: { type: string, coordinates: any[] }): this; @@ -1912,7 +2284,7 @@ declare module 'mongoose' { getQuery(): FilterQuery; /** Returns the current update operations as a JSON object. */ - getUpdate(): UpdateQuery | null; + getUpdate(): UpdateQuery | UpdateWithAggregationPipeline | null; /** Specifies a `$gt` query condition. When called with one argument, the most recent path passed to `where()` is used. */ gt(val: number): this; @@ -1936,7 +2308,7 @@ declare module 'mongoose' { j(val: boolean | null): this; /** Sets the lean option. */ - lean>(val?: boolean | any): Query; + lean : LeanDocumentOrArrayWithRawType>(val?: boolean | any): QueryWithHelpers; /** Specifies the maximum number of documents the query will return. */ limit(val: number): this; @@ -1953,7 +2325,7 @@ declare module 'mongoose' { * Runs a function `fn` and treats the return value of `fn` as the new value * for the query to resolve to. */ - map(fn: (doc: DocType) => MappedType): Query; + map(fn: (doc: ResultType) => MappedType): QueryWithHelpers; /** Specifies an `$maxDistance` query condition. When called with one argument, the most recent path passed to `where()` is used. */ maxDistance(val: number): this; @@ -1970,12 +2342,15 @@ declare module 'mongoose' { maxTimeMS(ms: number): this; /** Merges another Query or conditions object into this one. */ - merge(source: Query): this; + merge(source: Query | FilterQuery): this; /** Specifies a `$mod` condition, filters documents for documents whose `path` property is a number that is equal to `remainder` modulo `divisor`. */ mod(val: Array): this; mod(path: string, val: Array): this; + /** The model this query was created from */ + model: typeof Model; + /** * Getter/setter around the current mongoose-specific options for this query * Below are the current Mongoose-specific options. @@ -2005,14 +2380,14 @@ declare module 'mongoose' { * This is handy for integrating with async/await, because `orFail()` saves you * an extra `if` statement to check if no document was found. */ - orFail(err?: NativeError | (() => NativeError)): Query, DocType>; + orFail(err?: NativeError | (() => NativeError)): QueryWithHelpers, DocType, THelpers, RawDocType>; /** Specifies a `$polygon` condition */ polygon(...coordinatePairs: number[][]): this; polygon(path: string, ...coordinatePairs: number[][]): this; /** Specifies paths which should be populated with other documents. */ - populate(path: string | any, select?: string | any, model?: string | Model, match?: any): this; + populate(path: string | any, select?: string | any, model?: string | Model, match?: any): this; populate(options: PopulateOptions | Array): this; /** Get/set the current projection (AKA fields). Pass `null` to remove the current projection. */ @@ -2033,14 +2408,15 @@ declare module 'mongoose' { * deprecated, you should use [`deleteOne()`](#query_Query-deleteOne) * or [`deleteMany()`](#query_Query-deleteMany) instead. */ - remove(filter?: FilterQuery, callback?: (err: CallbackError, res: mongodb.WriteOpResult['result']) => void): Query; + remove(filter?: FilterQuery, callback?: Callback): QueryWithHelpers; /** * Declare and/or execute this query as a replaceOne() operation. Same as * `update()`, except MongoDB will replace the existing document and will * not accept any [atomic](https://docs.mongodb.com/manual/tutorial/model-data-for-atomic-operations/#pattern) operators (`$set`, etc.) */ - replaceOne(filter?: FilterQuery, replacement?: DocumentDefinition, options?: QueryOptions | null, callback?: (err: any, res: any) => void): Query; + replaceOne(filter?: FilterQuery, replacement?: DocumentDefinition, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers; + replaceOne(filter?: FilterQuery, replacement?: Object, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers; /** Specifies which document fields to include or exclude (also known as the query "projection") */ select(arg: string | any): this; @@ -2066,15 +2442,15 @@ declare module 'mongoose' { * This is useful for query middleware so you can add an update regardless * of whether you use `updateOne()`, `updateMany()`, `findOneAndUpdate()`, etc. */ - set(path: string, value: any): this; + set(path: string | Record, value?: any): this; /** Sets query options. Some options only make sense for certain operations. */ - setOptions(options: QueryOptions, overwrite: boolean): this; + setOptions(options: QueryOptions, overwrite?: boolean): this; /** Sets the query conditions to the provided JSON object. */ setQuery(val: FilterQuery | null): void; - setUpdate(update: UpdateQuery): void; + setUpdate(update: UpdateQuery | UpdateWithAggregationPipeline): void; /** Specifies an `$size` query condition. When called with one argument, the most recent path passed to `where()` is used. */ size(val: number): this; @@ -2106,10 +2482,10 @@ declare module 'mongoose' { then: Promise['then']; /** Converts this query to a customized, reusable query constructor with all arguments and options retained. */ - toConstructor(): new (...args: any[]) => Query; + toConstructor(): new (...args: any[]) => QueryWithHelpers; /** Declare and/or execute this query as an update() operation. */ - update(filter?: FilterQuery, update?: UpdateQuery, options?: QueryOptions | null, callback?: (err: CallbackError, res: any) => void): Query; + update(filter?: FilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers; /** * Declare and/or execute this query as an updateMany() operation. Same as @@ -2117,13 +2493,13 @@ declare module 'mongoose' { * `filter` (as opposed to just the first one) regardless of the value of * the `multi` option. */ - updateMany(filter?: FilterQuery, update?: UpdateQuery, options?: QueryOptions | null, callback?: (err: CallbackError, res: any) => void): Query; + updateMany(filter?: FilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers; /** * Declare and/or execute this query as an updateOne() operation. Same as * `update()`, except it does not support the `multi` or `overwrite` options. */ - updateOne(filter?: FilterQuery, update?: UpdateQuery, options?: QueryOptions | null, callback?: (err: CallbackError, res: any) => void): Query; + updateOne(filter?: FilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers; /** * Sets the specified number of `mongod` servers, or tag set of `mongod` servers, @@ -2133,6 +2509,8 @@ declare module 'mongoose' { /** Specifies a path for use with chaining. */ where(path: string, val?: any): this; + where(obj: object): this; + where(): this; /** Defines a `$within` or `$geoWithin` argument for geo-spatial queries. */ within(val?: any): this; @@ -2146,57 +2524,113 @@ declare module 'mongoose' { } type _FilterQuery = { - [P in keyof T]?: P extends '_id' - ? [Extract] extends [never] - ? mongodb.Condition - : mongodb.Condition - : [Extract] extends [never] - ? mongodb.Condition - : mongodb.Condition; + [P in keyof T]?: mongodb.Condition<[Extract] extends [never] ? T[P] : T[P] | string>; } & mongodb.RootQuerySelector; - export type FilterQuery = _FilterQuery>; + export type FilterQuery = _FilterQuery; - export type UpdateQuery = mongodb.UpdateQuery> & mongodb.MatchKeysAndValues>; + type NumericTypes = number | mongodb.Decimal128 | mongodb.Double | mongodb.Int32 | mongodb.Long; - type _AllowStringsForIds = { - [K in keyof T]: [Extract] extends [never] ? T[K] : T[K] | string; + type OnlyFieldsOfType = { + [key in keyof TSchema]?: [Extract] extends [never] ? never : AssignableType; }; - export type DocumentDefinition = _AllowStringsForIds>; - type FunctionPropertyNames = { - // The 1 & T[K] check comes from: https://stackoverflow.com/questions/55541275/typescript-check-for-the-any-type - // eslint-disable-next-line @typescript-eslint/ban-types - [K in keyof T]: 0 extends (1 & T[K]) ? never : (T[K] extends Function ? K : never) - }[keyof T]; + /** @see https://docs.mongodb.com/manual/reference/operator/update */ + type _UpdateQuery = { + /** @see https://docs.mongodb.com/manual/reference/operator/update-field/ */ + $currentDate?: OnlyFieldsOfType & AnyObject; + $inc?: OnlyFieldsOfType & AnyObject; + $min?: OnlyFieldsOfType & AnyObject; + $max?: OnlyFieldsOfType & AnyObject; + $mul?: OnlyFieldsOfType & AnyObject; + $rename?: { [key: string]: string }; + $set?: OnlyFieldsOfType & AnyObject; + $setOnInsert?: OnlyFieldsOfType & AnyObject; + $unset?: OnlyFieldsOfType & AnyObject; + + /** @see https://docs.mongodb.com/manual/reference/operator/update-array/ */ + $addToSet?: OnlyFieldsOfType & AnyObject; + $pop?: OnlyFieldsOfType, 1 | -1> & AnyObject; + $pull?: OnlyFieldsOfType, any> & AnyObject; + $push?: OnlyFieldsOfType, any> & AnyObject; + $pullAll?: OnlyFieldsOfType, any> & AnyObject; + + /** @see https://docs.mongodb.com/manual/reference/operator/update-bitwise/ */ + $bit?: { + [key: string]: { [key in 'and' | 'or' | 'xor']?: number }; + }; + }; + + type UpdateWithAggregationPipeline = UpdateAggregationStage[]; + type UpdateAggregationStage = { $addFields: any } | + { $set: any } | + { $project: any } | + { $unset: any } | + { $replaceRoot: any } | + { $replaceWith: any }; + + type __UpdateDefProperty = + 0 extends (1 & T) ? T : // any + T extends unknown[] ? LeanArray : // Array + T extends Document ? LeanDocument : // Subdocument + [Extract] extends [never] ? T : + T | string; + type __UpdateQueryDef = { + [K in keyof T]: __UpdateDefProperty; + }; + type _UpdateQueryDef = __UpdateQueryDef; + + export type UpdateQuery = (_UpdateQuery<_UpdateQueryDef> & mongodb.MatchKeysAndValues<_UpdateQueryDef>>); + + export type DocumentDefinition = { + [K in keyof Omit>]: + [Extract] extends [never] + ? T[K] extends TreatAsPrimitives + ? T[K] + : LeanDocumentElement + : T[K] | string; + }; type actualPrimitives = string | boolean | number | bigint | symbol | null | undefined; type TreatAsPrimitives = actualPrimitives | - // eslint-disable-next-line no-undef - Date | RegExp | symbol | Error | BigInt | Types.ObjectId; + NativeDate | RegExp | symbol | Error | BigInt | Types.ObjectId; type LeanType = 0 extends (1 & T) ? T : // any T extends TreatAsPrimitives ? T : // primitives LeanDocument; // Documents and everything else + type LeanArray = T extends unknown[][] ? LeanArray[] : LeanType[]; + export type _LeanDocument = { - [K in keyof T]: - 0 extends (1 & T[K]) ? T[K] : // any - T[K] extends unknown[] ? LeanType[] : // Array - T[K] extends Document ? LeanDocument : // Subdocument - T[K]; + [K in keyof T]: LeanDocumentElement; }; - export type LeanDocument = Omit, Exclude | '$isSingleNested'>, FunctionPropertyNames>; + // Keep this a separate type, to ensure that T is a naked type. + // This way, the conditional type is distributive over union types. + // This is required for PopulatedDoc. + type LeanDocumentElement = + 0 extends (1 & T) ? T : // any + T extends unknown[] ? LeanArray : // Array + T extends Document ? LeanDocument : // Subdocument + T; + + export type LeanDocument = Omit<_LeanDocument, Exclude | '$isSingleNested'>; export type LeanDocumentOrArray = 0 extends (1 & T) ? T : T extends unknown[] ? LeanDocument[] : T extends Document ? LeanDocument : T; - class QueryCursor extends stream.Readable { + export type LeanDocumentOrArrayWithRawType = 0 extends (1 & T) ? T : + T extends unknown[] ? RawDocType[] : + T extends Document ? RawDocType : + T; + + class QueryCursor extends stream.Readable { + [Symbol.asyncIterator](): AsyncIterableIterator; + /** * Adds a [cursor flag](http://mongodb.github.io/node-mongodb-native/2.2/api/Cursor.html#addCursorFlag). * Useful for setting the `noCursorTimeout` and `tailable` flags. @@ -2208,28 +2642,31 @@ declare module 'mongoose' { * `next()` will error. */ close(): Promise; - close(callback: (err: CallbackError) => void): void; + close(callback: CallbackWithoutResult): void; /** - * Execute `fn` for every document in the cursor. If `fn` returns a promise, + * Execute `fn` for every document(s) in the cursor. If batchSize is provided + * `fn` will be executed for each batch of documents. If `fn` returns a promise, * will wait for the promise to resolve before iterating on to the next one. * Returns a promise that resolves when done. */ eachAsync(fn: (doc: DocType) => any, options?: { parallel?: number }): Promise; - eachAsync(fn: (doc: DocType) => any, options?: { parallel?: number }, cb?: (err: CallbackError) => void): void; + eachAsync(fn: (doc: DocType[]) => any, options: { parallel?: number, batchSize: number }): Promise; + eachAsync(fn: (doc: DocType) => any, options?: { parallel?: number, batchSize?: number }, cb?: CallbackWithoutResult): void; + eachAsync(fn: (doc: DocType[]) => any, options: { parallel?: number, batchSize: number }, cb?: CallbackWithoutResult): void; /** * Registers a transform function which subsequently maps documents retrieved * via the streams interface or `.next()` */ - map(fn: (res: DocType) => ResultType): QueryCursor; + map(fn: (res: DocType) => ResultType): QueryCursor; /** * Get the next document from this cursor. Will return `null` when there are * no documents left. */ next(): Promise; - next(callback: (err: CallbackError, doc: DocType | null) => void): void; + next(callback: Callback): void; options: any; } @@ -2266,15 +2703,15 @@ declare module 'mongoose' { count(countName: string): this; /** - * Sets the cursor option option for the aggregation query (ignored for < 2.6.0). + * Sets the cursor option for the aggregation query (ignored for < 2.6.0). */ cursor(options?: Record): this; /** Executes the aggregate pipeline on the currently bound Model. If cursor option is set, returns a cursor */ - exec(callback?: (err: any, result: R) => void): Promise | any; + exec(callback?: Callback): Promise | any; /** Execute the aggregation with explain */ - explain(callback?: (err: CallbackError, result: any) => void): Promise; + explain(callback?: Callback): Promise; /** Combines multiple aggregation pipelines. */ facet(options: any): this; @@ -2309,9 +2746,18 @@ declare module 'mongoose' { */ model(model: any): this; + /** + * Append a new $near operator to this aggregation pipeline + * @param arg $near operator contents + */ + near(arg: { near?: number[]; distanceField: string; maxDistance?: number; query?: Record; includeLocs?: string; num?: number; uniqueDocs?: boolean }): this; + /** Returns the current pipeline */ pipeline(): any[]; + /** Appends a new $project operator to this aggregate pipeline. */ + project(arg: string | Object): this; + /** Sets the readPreference option for the aggregation query. */ read(pref: string | mongodb.ReadPreferenceMode, tags?: any[]): this; @@ -2321,6 +2767,9 @@ declare module 'mongoose' { /** Appends a new $redact operator to this aggregate pipeline. */ redact(expression: any, thenExpr: string | any, elseExpr: string | any): this; + /** Appends a new $replaceRoot operator to this aggregate pipeline. */ + replaceRoot(newRoot: object | string): this; + /** * Helper for [Atlas Text Search](https://docs.atlas.mongodb.com/reference/atlas-search/tutorial/)'s * `$search` stage. @@ -2356,9 +2805,6 @@ declare module 'mongoose' { /** Appends new custom $unwind operator(s) to this aggregate pipeline. */ unwind(...args: any[]): this; - - /** Appends new custom $project operator to this aggregate pipeline. */ - project(arg: any): this } class AggregationCursor extends stream.Readable { @@ -2373,15 +2819,16 @@ declare module 'mongoose' { * `next()` will error. */ close(): Promise; - close(callback: (err: CallbackError) => void): void; + close(callback: CallbackWithoutResult): void; /** - * Execute `fn` for every document in the cursor. If `fn` returns a promise, + * Execute `fn` for every document(s) in the cursor. If batchSize is provided + * `fn` will be executed for each batch of documents. If `fn` returns a promise, * will wait for the promise to resolve before iterating on to the next one. * Returns a promise that resolves when done. */ - eachAsync(fn: (doc: any) => any, options?: { parallel?: number }): Promise; - eachAsync(fn: (doc: any) => any, options?: { parallel?: number }, cb?: (err: CallbackError) => void): void; + eachAsync(fn: (doc: any) => any, options?: { parallel?: number, batchSize?: number }): Promise; + eachAsync(fn: (doc: any) => any, options?: { parallel?: number, batchSize?: number }, cb?: CallbackWithoutResult): void; /** * Registers a transform function which subsequently maps documents retrieved @@ -2394,7 +2841,7 @@ declare module 'mongoose' { * no documents left. */ next(): Promise; - next(callback: (err: CallbackError, doc: any) => void): void; + next(callback: Callback): void; } class SchemaType { @@ -2402,7 +2849,6 @@ declare module 'mongoose' { constructor(path: string, options?: any, instance?: string); /** Get/set the function used to cast arbitrary values to this type. */ - // eslint-disable-next-line @typescript-eslint/ban-types static cast(caster?: Function | boolean): Function; static checkRequired(checkRequired?: (v: any) => boolean): (v: any) => boolean; @@ -2413,14 +2859,16 @@ declare module 'mongoose' { /** Attaches a getter for all instances of this schema type. */ static get(getter: (value: any) => any): void; - /** Get/set the function used to cast arbitrary values to this type. */ - cast(caster: (v: any) => any): (v: any) => any; + /** The class that Mongoose uses internally to instantiate this SchemaType's `options` property. */ + OptionsConstructor: typeof SchemaTypeOptions; + + /** Cast `val` to this schema type. Each class that inherits from schema type should implement this function. */ + cast(val: any, doc: Document, init: boolean, prev?: any, options?: any): any; /** Sets a default value for this SchemaType. */ default(val: any): any; /** Adds a getter to this schematype. */ - // eslint-disable-next-line @typescript-eslint/ban-types get(fn: Function): this; /** @@ -2448,7 +2896,6 @@ declare module 'mongoose' { select(val: boolean): this; /** Adds a setter to this schematype. */ - // eslint-disable-next-line @typescript-eslint/ban-types set(fn: Function): this; /** Declares a sparse index. */ @@ -2464,11 +2911,14 @@ declare module 'mongoose' { unique(bool: boolean): this; /** Adds validator(s) for this document path. */ - // eslint-disable-next-line @typescript-eslint/ban-types validate(obj: RegExp | Function | any, errorMsg?: string, type?: string): this; } + type Callback = (error: CallbackError, result: T) => void; + + type CallbackWithoutResult = (error: CallbackError) => void; + class NativeError extends global.Error { } type CallbackError = NativeError | null; @@ -2492,6 +2942,8 @@ declare module 'mongoose' { path: string; reason?: NativeError | null; model?: any; + + constructor(type: string, value: any, path: string, reason?: NativeError, schemaType?: SchemaType); } export class DisconnectedError extends Error { @@ -2548,7 +3000,7 @@ declare module 'mongoose' { export class ValidationError extends Error { name: 'ValidationError'; - errors: { [path: string]: ValidatorError | CastError }; + errors: { [path: string]: ValidatorError | CastError | ValidationError }; } export class ValidatorError extends Error { @@ -2575,9 +3027,11 @@ declare module 'mongoose' { /** Deprecated types for backwards compatibility. */ - /** Alias for QueryOptions for backwards compatability. */ + /** Alias for QueryOptions for backwards compatibility. */ type ModelUpdateOptions = QueryOptions; + type DocumentQuery = Query; + /** Backwards support for DefinitelyTyped */ interface HookSyncCallback { (this: T, next: HookNextFunction, docs: any[]): Promise | void; diff --git a/index.html b/index.html deleted file mode 100644 index 2846a178710..00000000000 --- a/index.html +++ /dev/null @@ -1,166 +0,0 @@ -Mongoose ODM v5.6.0 Fork me on GitHub

    Elegant MongoDB object modeling for -Node.js

    -
    • Version 5.6.0

    Let's face it, writing MongoDB validation, casting and business logic boilerplate is a drag. That's why we wrote Mongoose.

    const mongoose = require('mongoose');
    -mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true});
    -
    -const Cat = mongoose.model('Cat', { name: String });
    -
    -const kitty = new Cat({ name: 'Zildjian' });
    -kitty.save().then(() => console.log('meow'));
    -

    Mongoose provides a straight-forward, schema-based solution to model -your application data. It includes built-in type casting, validation, -query building, business logic hooks and more, out of the box.

    -

    Getting Started

    - -

    Support

    - -

    News

    - -

    Changelog

    - -

    Sponsors

    - - - -
    \ No newline at end of file diff --git a/index.pug b/index.pug index 5d6ac1b4ede..a41036fa2a2 100644 --- a/index.pug +++ b/index.pug @@ -85,7 +85,7 @@ html(lang='en') } body - a#forkbanner(href="http://github.com/learnboost/mongoose") + a#forkbanner(href="http://github.com/Automattic/mongoose") img(style="position: absolute; top: 0; right: 0; border: 0;", src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png", alt="Fork me on GitHub") #wrap.homepage #header @@ -169,12 +169,6 @@ html(lang='en') - - - - - - @@ -226,8 +220,8 @@ html(lang='en') - - + + @@ -259,9 +253,6 @@ html(lang='en') - - - @@ -289,9 +280,6 @@ html(lang='en') - - - @@ -331,15 +319,9 @@ html(lang='en') - - - - - - @@ -349,6 +331,45 @@ html(lang='en') + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/lib/aggregate.js b/lib/aggregate.js index 53131bff7f0..451b820126e 100644 --- a/lib/aggregate.js +++ b/lib/aggregate.js @@ -7,8 +7,9 @@ const AggregationCursor = require('./cursor/AggregationCursor'); const Query = require('./query'); const applyGlobalMaxTimeMS = require('./helpers/query/applyGlobalMaxTimeMS'); +const getConstructorName = require('./helpers/getConstructorName'); const promiseOrCallback = require('./helpers/promiseOrCallback'); -const stringifyAccumulatorOptions = require('./helpers/aggregate/stringifyAccumulatorOptions'); +const stringifyFunctionOperators = require('./helpers/aggregate/stringifyFunctionOperators'); const util = require('util'); const utils = require('./utils'); const read = Query.prototype.read; @@ -562,7 +563,7 @@ Aggregate.prototype.sort = function(arg) { const sort = {}; - if (arg.constructor.name === 'Object') { + if (getConstructorName(arg) === 'Object') { const desc = ['desc', 'descending', -1]; Object.keys(arg).forEach(function(field) { // If sorting by text score, skip coercing into 1/-1 @@ -983,7 +984,7 @@ Aggregate.prototype.exec = function(callback) { return promiseOrCallback(callback, cb => { prepareDiscriminatorPipeline(this); - stringifyAccumulatorOptions(this._pipeline); + stringifyFunctionOperators(this._pipeline); model.hooks.execPre('aggregate', this, error => { if (error) { diff --git a/lib/browser.js b/lib/browser.js index f716f2aee71..f71a4793cb4 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -68,11 +68,14 @@ exports.Schema = require('./schema'); * * ####Types: * - * - [ObjectId](#types-objectid-js) - * - [Buffer](#types-buffer-js) - * - [SubDocument](#types-embedded-js) - * - [Array](#types-array-js) - * - [DocumentArray](#types-documentarray-js) + * - [Array](/docs/schematypes.html#arrays) + * - [Buffer](/docs/schematypes.html#buffers) + * - [Embedded](/docs/schematypes.html#schemas) + * - [DocumentArray](/docs/api/documentarraypath.html) + * - [Decimal128](/docs/api.html#mongoose_Mongoose-Decimal128) + * - [ObjectId](/docs/schematypes.html#objectids) + * - [Map](/docs/schematypes.html#maps) + * - [Subdocument](/docs/schematypes.html#schemas) * * Using this exposed access to the `ObjectId` type, we can construct ids on demand. * diff --git a/lib/cast.js b/lib/cast.js index 1c24bc7fe65..94543f4468c 100644 --- a/lib/cast.js +++ b/lib/cast.js @@ -9,6 +9,7 @@ const StrictModeError = require('./error/strict'); const Types = require('./schema/index'); const castTextSearch = require('./schema/operators/text'); const get = require('./helpers/get'); +const getConstructorName = require('./helpers/getConstructorName'); const getSchemaDiscriminatorByValue = require('./helpers/discriminator/getSchemaDiscriminatorByValue'); const isOperator = require('./helpers/query/isOperator'); const util = require('util'); @@ -267,7 +268,7 @@ module.exports = function cast(schema, obj, options, context) { } } else if (val == null) { continue; - } else if (val.constructor.name === 'Object') { + } else if (getConstructorName(val) === 'Object') { any$conditionals = Object.keys(val).some(isOperator); if (!any$conditionals) { diff --git a/lib/collection.js b/lib/collection.js index 043e8c8a21b..df16146094d 100644 --- a/lib/collection.js +++ b/lib/collection.js @@ -141,7 +141,7 @@ Collection.prototype.doQueue = function() { } this.queue = []; const _this = this; - process.nextTick(function() { + immediate(function() { _this.emitter.emit('queue'); }); return this; diff --git a/lib/connection.js b/lib/connection.js index 65bd6159798..89fdc9077d7 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -25,8 +25,6 @@ const parseConnectionString = require('mongodb/lib/core').parseConnectionString; const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol; const sessionNewDocuments = require('./helpers/symbols').sessionNewDocuments; -let id = 0; - /*! * A list of authentication mechanisms that don't require a password for authentication. * This is used by the authMechanismDoesNotRequirePassword method. @@ -60,7 +58,7 @@ function Connection(base) { this.base = base; this.collections = {}; this.models = {}; - this.config = { autoIndex: true }; + this.config = {}; this.replica = false; this.options = null; this.otherDbs = []; // FIXME: To be replaced with relatedDbs @@ -70,7 +68,12 @@ function Connection(base) { this._closeCalled = false; this._hasOpened = false; this.plugins = []; - this.id = id++; + if (typeof base === 'undefined' || !base.connections.length) { + this.id = 0; + } else { + this.id = base.connections.length; + } + this._queue = []; } /*! @@ -116,11 +119,6 @@ Object.defineProperty(Connection.prototype, 'readyState', { db.readyState = val; } - // loop over relatedDbs on this connection and change their state - for (const k in this.relatedDbs) { - this.relatedDbs[k].readyState = val; - } - if (STATES.connected === val) { this._hasOpened = true; } @@ -361,6 +359,18 @@ Object.defineProperty(Connection.prototype, 'pass', { Connection.prototype.db; +/** + * The MongoClient instance this connection uses to talk to MongoDB. Mongoose automatically sets this property + * when the connection is opened. + * + * @property client + * @memberOf Connection + * @instance + * @api public + */ + +Connection.prototype.client; + /** * A hash of the global options that are associated with this connection * @@ -448,7 +458,7 @@ Connection.prototype.startSession = _wrapConnHelper(function startSession(option * * // Throw an error to abort the transaction * throw new Error('Oops!'); - * }).catch(() => {}); + * },{ readPreference: 'primary' }).catch(() => {}); * * // true, `transaction()` reset the document's state because the * // transaction was aborted. @@ -456,14 +466,15 @@ Connection.prototype.startSession = _wrapConnHelper(function startSession(option * * @method transaction * @param {Function} fn Function to execute in a transaction - * @return {Promise} promise that resolves to the returned value of `fn` + * @param {mongodb.TransactionOptions} [options] Optional settings for the transaction + * @return {Promise} promise that is fulfilled if Mongoose successfully committed the transaction, or rejects if the transaction was aborted or if Mongoose failed to commit the transaction. If fulfilled, the promise resolves to a MongoDB command result. * @api public */ -Connection.prototype.transaction = function transaction(fn) { +Connection.prototype.transaction = function transaction(fn, options) { return this.startSession().then(session => { session[sessionNewDocuments] = new Map(); - return session.withTransaction(() => fn(session)). + return session.withTransaction(() => fn(session), options). then(res => { delete session[sessionNewDocuments]; return res; @@ -559,9 +570,7 @@ function _wrapConnHelper(fn) { // Re: gh-8534 immediate(() => { if (this.readyState === STATES.connecting && this._shouldBufferCommands()) { - this.once('open', function() { - fn.apply(this, argsWithoutCb.concat([cb])); - }); + this._queue.push({ fn: fn, ctx: this, args: argsWithoutCb.concat([cb]) }); } else if (this.readyState === STATES.disconnected && this.db == null) { cb(disconnectedError); } else { @@ -621,6 +630,11 @@ Connection.prototype.error = function(err, callback) { Connection.prototype.onOpen = function() { this.readyState = STATES.connected; + for (const d of this._queue) { + d.fn.apply(d.ctx, d.args); + } + this._queue = []; + // avoid having the collection subscribe to our event emitter // to prevent 0.3 warning for (const i in this.collections) { @@ -638,7 +652,7 @@ Connection.prototype.onOpen = function() { * @param {String} uri The URI to connect with. * @param {Object} [options] Passed on to http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html#connect * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](http://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection. - * @param {Number} [options.bufferTimeoutMS=true] Mongoose specific option. If `bufferCommands` is true, Mongoose will throw an error after `bufferTimeoutMS` if the operation is still buffered. + * @param {Number} [options.bufferTimeoutMS=10000] Mongoose specific option. If `bufferCommands` is true, Mongoose will throw an error after `bufferTimeoutMS` if the operation is still buffered. * @param {String} [options.dbName] The name of the database we want to use. If not provided, use database name from connection string. * @param {String} [options.user] username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility. * @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility. @@ -648,7 +662,7 @@ Connection.prototype.onOpen = function() { * @param {Number} [options.heartbeatFrequencyMS] If `useUnifiedTopology = true`, the MongoDB driver sends a heartbeat every `heartbeatFrequencyMS` to check on the status of the connection. A heartbeat is subject to `serverSelectionTimeoutMS`, so the MongoDB driver will retry failed heartbeats for up to 30 seconds by default. Mongoose only emits a `'disconnected'` event after a heartbeat has failed, so you may want to decrease this setting to reduce the time between when your server goes down and when Mongoose emits `'disconnected'`. We recommend you do **not** set this setting below 1000, too many heartbeats can lead to performance degradation. * @param {Boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection. * @param {Boolean} [options.useNewUrlParser=false] False by default. Set to `true` to opt in to the MongoDB driver's new URL parser logic. - * @param {Boolean} [options.useCreateIndex=true] Mongoose-specific option. If `true`, this connection will use [`createIndex()` instead of `ensureIndex()`](/docs/deprecations.html#ensureindex) for automatic index builds via [`Model.init()`](/docs/api.html#model_Model.init). + * @param {Boolean} [options.useCreateIndex=false] Mongoose-specific option. If `true`, this connection will use [`createIndex()` instead of `ensureIndex()`](/docs/deprecations.html#ensureindex) for automatic index builds via [`Model.init()`](/docs/api.html#model_Model.init). * @param {Boolean} [options.useFindAndModify=true] True by default. Set to `false` to make `findOneAndUpdate()` and `findOneAndRemove()` use native `findOneAndUpdate()` rather than `findAndModify()`. * @param {Number} [options.reconnectTries=30] If you're connected to a single server or mongos proxy (as opposed to a replica set), the MongoDB driver will try to reconnect every `reconnectInterval` milliseconds for `reconnectTries` times, and give up afterward. When the driver gives up, the mongoose connection emits a `reconnectFailed` event. This option does nothing for replica set connections. * @param {Number} [options.reconnectInterval=1000] See `reconnectTries` option above. @@ -713,6 +727,7 @@ Connection.prototype.openUri = function(uri, options, callback) { if (options) { options = utils.clone(options); + const autoIndex = options.config && options.config.autoIndex != null ? options.config.autoIndex : options.autoIndex; @@ -817,9 +832,9 @@ Connection.prototype.openUri = function(uri, options, callback) { const promise = new Promise((resolve, reject) => { const client = new mongodb.MongoClient(uri, options); _this.client = client; + client.setMaxListeners(0); client.connect((error) => { if (error) { - _this.readyState = STATES.disconnected; return reject(error); } @@ -833,19 +848,20 @@ Connection.prototype.openUri = function(uri, options, callback) { this.$initialConnection = Promise.all([promise, parsePromise]). then(res => res[0]). catch(err => { + this.readyState = STATES.disconnected; if (err != null && err.name === 'MongoServerSelectionError') { err = serverSelectionError.assimilateError(err); } if (this.listeners('error').length > 0) { - process.nextTick(() => this.emit('error', err)); + immediate(() => this.emit('error', err)); } throw err; }); this.then = function(resolve, reject) { return this.$initialConnection.then(() => { if (typeof resolve === 'function') { - resolve(_this); + return resolve(_this); } }, reject); }; @@ -867,6 +883,7 @@ function _setClient(conn, client, options, dbName) { const db = dbName != null ? client.db(dbName) : client.db(); conn.db = db; conn.client = client; + conn._closeCalled = client._closeCalled; const _handleReconnect = () => { // If we aren't disconnected, we assume this reconnect is due to a @@ -916,7 +933,7 @@ function _setClient(conn, client, options, dbName) { } }); - db.on('close', function() { + client.on('close', function() { const type = get(db, 's.topology.s.description.type', ''); if (type !== 'ReplicaSetWithPrimary') { // Implicitly emits 'disconnected' @@ -927,14 +944,15 @@ function _setClient(conn, client, options, dbName) { } // Backwards compat for mongoose 4.x - db.on('reconnect', function() { - _handleReconnect(); - }); db.s.topology.on('reconnectFailed', function() { conn.emit('reconnectFailed'); }); if (!options.useUnifiedTopology) { + client.on('reconnect', function() { + _handleReconnect(); + }); + db.s.topology.on('left', function(data) { conn.emit('left', data); }); @@ -950,8 +968,13 @@ function _setClient(conn, client, options, dbName) { conn.emit('attemptReconnect'); }); } - if (!options.useUnifiedTopology || !type.startsWith('ReplicaSet')) { - db.on('close', function() { + if (!options.useUnifiedTopology) { + client.on('close', function() { + // Implicitly emits 'disconnected' + conn.readyState = STATES.disconnected; + }); + } else if (!type.startsWith('ReplicaSet')) { + client.on('close', function() { // Implicitly emits 'disconnected' conn.readyState = STATES.disconnected; }); @@ -964,23 +987,16 @@ function _setClient(conn, client, options, dbName) { conn.readyState = STATES.disconnected; } }); - } - db.on('timeout', function() { - conn.emit('timeout'); - }); + client.on('timeout', function() { + conn.emit('timeout'); + }); + } delete conn.then; delete conn.catch; - conn.readyState = STATES.connected; - - for (const i in conn.collections) { - if (utils.object.hasOwnProperty(conn.collections, i)) { - conn.collections[i].onOpen(); - } - } - conn.emit('open'); + conn.onOpen(); } /*! @@ -1028,6 +1044,9 @@ Connection.prototype._close = function(force, callback) { const _this = this; const closeCalled = this._closeCalled; this._closeCalled = true; + if (this.client != null) { + this.client._closeCalled = true; + } switch (this.readyState) { case STATES.disconnected: @@ -1103,7 +1122,11 @@ Connection.prototype.onClose = function(force) { */ Connection.prototype.collection = function(name, options) { - options = options ? utils.clone(options) : {}; + const defaultOptions = { + autoIndex: this.config.autoIndex != null ? this.config.autoIndex : this.base.options.autoIndex, + autoCreate: this.config.autoCreate != null ? this.config.autoCreate : this.base.options.autoCreate + }; + options = Object.assign({}, defaultOptions, options ? utils.clone(options) : {}); options.$wasForceClosed = this.$wasForceClosed; if (!(name in this.collections)) { this.collections[name] = new Collection(name, this, options); @@ -1290,6 +1313,8 @@ Connection.prototype.deleteModel = function(name) { delete this.models[name]; delete this.collections[collectionName]; delete this.base.modelSchemas[name]; + + this.emit('deleteModel', model); } else if (name instanceof RegExp) { const pattern = name; const names = this.modelNames(); @@ -1472,6 +1497,7 @@ Connection.prototype.setClient = function setClient(client) { * @param {String} name The database name * @param {Object} [options] * @param {Boolean} [options.useCache=false] If true, cache results so calling `useDb()` multiple times with the same name only creates 1 connection object. + * @param {Boolean} [options.noListener=false] If true, the connection object will not make the db listen to events on the original connection. See [issue #9961](https://github.com/Automattic/mongoose/issues/9961). * @return {Connection} New Connection Object * @api public */ diff --git a/lib/cursor/AggregationCursor.js b/lib/cursor/AggregationCursor.js index 5023b1c7f8f..cec302a9f11 100644 --- a/lib/cursor/AggregationCursor.js +++ b/lib/cursor/AggregationCursor.js @@ -8,7 +8,9 @@ const MongooseError = require('../error/mongooseError'); const Readable = require('stream').Readable; const promiseOrCallback = require('../helpers/promiseOrCallback'); const eachAsync = require('../helpers/cursor/eachAsync'); +const immediate = require('../helpers/immediate'); const util = require('util'); +const utils = require('../../lib/utils'); /** * An AggregationCursor is a concurrency primitive for processing aggregation @@ -35,7 +37,14 @@ const util = require('util'); */ function AggregationCursor(agg) { - Readable.call(this, { objectMode: true }); + const streamOpts = { objectMode: true }; + // for node < 12 we will emit 'close' event after 'end' + if (utils.nodeMajorVersion >= 12) { + // set autoDestroy=true because on node 12 it's by default false + // gh-10902 need autoDestroy to destroy correctly and emit 'close' event for node >= 12 + streamOpts.autoDestroy = true; + } + Readable.call(this, streamOpts); this.cursor = null; this.agg = agg; @@ -85,13 +94,10 @@ AggregationCursor.prototype._read = function() { if (error) { return _this.emit('error', error); } - setTimeout(function() { - // on node >= 14 streams close automatically (gh-8834) - const isNotClosedAutomatically = !_this.destroyed; - if (isNotClosedAutomatically) { - _this.emit('close'); - } - }, 0); + // for node >= 12 the autoDestroy will emit the 'close' event + if (utils.nodeMajorVersion < 12) { + _this.on('end', () => _this.emit('close')); + } }); return; } @@ -342,7 +348,7 @@ function _next(ctx, cb) { } if (ctx._error) { - return process.nextTick(function() { + return immediate(function() { callback(ctx._error); }); } diff --git a/lib/cursor/QueryCursor.js b/lib/cursor/QueryCursor.js index 32a130f7d7d..9eec7b074b0 100644 --- a/lib/cursor/QueryCursor.js +++ b/lib/cursor/QueryCursor.js @@ -8,7 +8,9 @@ const Readable = require('stream').Readable; const promiseOrCallback = require('../helpers/promiseOrCallback'); const eachAsync = require('../helpers/cursor/eachAsync'); const helpers = require('../queryhelpers'); +const immediate = require('../helpers/immediate'); const util = require('util'); +const utils = require('../../lib/utils'); /** * A QueryCursor is a concurrency primitive for processing query results @@ -33,7 +35,14 @@ const util = require('util'); */ function QueryCursor(query, options) { - Readable.call(this, { objectMode: true }); + const streamOpts = { objectMode: true }; + // for node < 12 we will emit 'close' event after 'end' + if (utils.nodeMajorVersion >= 12) { + // set autoDestroy=true because on node 12 it's by default false + // gh-10902 need autoDestroy to destroy correctly and emit 'close' event for node >= 12 + streamOpts.autoDestroy = true; + } + Readable.call(this, streamOpts); this.cursor = null; this.query = query; @@ -54,11 +63,18 @@ function QueryCursor(query, options) { if (this.options.batchSize) { this.options.cursor = options.cursor || {}; this.options.cursor.batchSize = options.batchSize; + + // Max out the number of documents we'll populate in parallel at 5000. + this.options._populateBatchSize = Math.min(this.options.batchSize, 5000); } model.collection.find(query._conditions, this.options, function(err, cursor) { if (_this._error) { - cursor.close(function() {}); + if (cursor != null) { + cursor.close(function() {}); + } + _this.emit('cursor', null); _this.listeners('error').length > 0 && _this.emit('error', _this._error); + return; } if (err) { return _this.emit('error', err); @@ -87,13 +103,10 @@ QueryCursor.prototype._read = function() { if (error) { return _this.emit('error', error); } - setTimeout(function() { - // on node >= 14 streams close automatically (gh-8834) - const isNotClosedAutomatically = !_this.destroyed; - if (isNotClosedAutomatically) { - _this.emit('close'); - } - }, 0); + // for node >= 12 the autoDestroy will emit the 'close' event + if (utils.nodeMajorVersion < 12) { + _this.on('end', () => _this.emit('close')); + } }); return; } @@ -344,7 +357,7 @@ function _next(ctx, cb) { } if (ctx._error) { - return process.nextTick(function() { + return immediate(function() { callback(ctx._error); }); } @@ -355,7 +368,7 @@ function _next(ctx, cb) { ctx.query._mongooseOptions); ctx._pop.__noPromise = true; } - if (ctx.query._mongooseOptions.populate && ctx.options.batchSize > 1) { + if (ctx.query._mongooseOptions.populate && ctx.options._populateBatchSize > 1) { if (ctx._batchDocs && ctx._batchDocs.length) { // Return a cached populated doc return _nextDoc(ctx, ctx._batchDocs.shift(), ctx._pop, callback); @@ -389,7 +402,10 @@ function _next(ctx, cb) { }); } } else { - ctx.once('cursor', function() { + ctx.once('cursor', function(cursor) { + if (cursor == null) { + return; + } _next(ctx, cb); }); } @@ -410,7 +426,12 @@ function _onNext(error, doc) { this.ctx._batchDocs.push(doc); - if (this.ctx._batchDocs.length < this.ctx.options.batchSize) { + if (this.ctx._batchDocs.length < this.ctx.options._populateBatchSize) { + // If both `batchSize` and `_populateBatchSize` are huge, calling `next()` repeatedly may + // cause a stack overflow. So make sure we clear the stack regularly. + if (this.ctx._batchDocs.length > 0 && this.ctx._batchDocs.length % 1000 === 0) { + return immediate(() => this.ctx.cursor.next(_onNext.bind(this))); + } this.ctx.cursor.next(_onNext.bind(this)); } else { _populateBatch.call(this); @@ -441,7 +462,7 @@ function _populateBatch() { function _nextDoc(ctx, doc, pop, callback) { if (ctx.query._mongooseOptions.lean) { - return ctx.model.hooks.execPost('find', ctx.query, [doc], err => { + return ctx.model.hooks.execPost('find', ctx.query, [[doc]], err => { if (err != null) { return callback(err); } @@ -453,7 +474,7 @@ function _nextDoc(ctx, doc, pop, callback) { if (err != null) { return callback(err); } - ctx.model.hooks.execPost('find', ctx.query, [doc], err => { + ctx.model.hooks.execPost('find', ctx.query, [[doc]], err => { if (err != null) { return callback(err); } @@ -470,7 +491,10 @@ function _waitForCursor(ctx, cb) { if (ctx.cursor) { return cb(); } - ctx.once('cursor', function() { + ctx.once('cursor', function(cursor) { + if (cursor == null) { + return; + } cb(); }); } diff --git a/lib/document.js b/lib/document.js index b127476a24a..7add115faf5 100644 --- a/lib/document.js +++ b/lib/document.js @@ -25,6 +25,7 @@ const get = require('./helpers/get'); const getEmbeddedDiscriminatorPath = require('./helpers/document/getEmbeddedDiscriminatorPath'); const handleSpreadDoc = require('./helpers/document/handleSpreadDoc'); const idGetter = require('./plugins/idGetter'); +const immediate = require('./helpers/immediate'); const isDefiningProjection = require('./helpers/projection/isDefiningProjection'); const isExclusive = require('./helpers/projection/isExclusive'); const inspect = require('util').inspect; @@ -41,13 +42,13 @@ const isMongooseObject = utils.isMongooseObject; const arrayAtomicsBackupSymbol = Symbol('mongoose.Array#atomicsBackup'); const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol; const documentArrayParent = require('./helpers/symbols').documentArrayParent; -const documentIsSelected = require('./helpers/symbols').documentIsSelected; const documentIsModified = require('./helpers/symbols').documentIsModified; const documentModifiedPaths = require('./helpers/symbols').documentModifiedPaths; const documentSchemaSymbol = require('./helpers/symbols').documentSchemaSymbol; const getSymbol = require('./helpers/symbols').getSymbol; const populateModelSymbol = require('./helpers/symbols').populateModelSymbol; const scopeSymbol = require('./helpers/symbols').scopeSymbol; +const schemaMixedSymbol = require('./schema/symbols').schemaMixedSymbol; let DocumentArray; let MongooseArray; @@ -77,9 +78,8 @@ function Document(obj, fields, skipId, options) { options = Object.assign({}, options); const defaults = get(options, 'defaults', true); options.defaults = defaults; - // Support `browserDocument.js` syntax - if (this.schema == null) { + if (this.$__schema == null) { const _schema = utils.isObject(fields) && !fields.instanceOfSchema ? new Schema(fields) : fields; @@ -96,12 +96,11 @@ function Document(obj, fields, skipId, options) { this.$__.$options = options || {}; this.$locals = {}; this.$op = null; - if (obj != null && typeof obj !== 'object') { throw new ObjectParameterError(obj, 'obj', 'Document'); } - const schema = this.schema; + const schema = this.$__schema; if (typeof fields === 'boolean' || fields === 'throw') { this.$__.strictMode = fields; @@ -141,7 +140,6 @@ function Document(obj, fields, skipId, options) { }); } } - if (obj) { // Skip set hooks if (this.$__original_set) { @@ -205,6 +203,17 @@ for (const i in EventEmitter.prototype) { Document[i] = EventEmitter.prototype[i]; } +/** + * The document's internal schema. + * + * @api private + * @property schema + * @memberOf Document + * @instance + */ + +Document.prototype.$__schema; + /** * The document's schema. * @@ -256,6 +265,28 @@ Object.defineProperty(Document.prototype, '$locals', { Document.prototype.isNew; +/** + * Set this property to add additional query filters when Mongoose saves this document and `isNew` is false. + * + * ####Example: + * + * // Make sure `save()` never updates a soft deleted document. + * schema.pre('save', function() { + * this.$where = { isDeleted: false }; + * }); + * + * @api public + * @property $where + * @memberOf Document + * @instance + */ + +Object.defineProperty(Document.prototype, '$where', { + configurable: false, + enumerable: false, + writable: true +}); + /** * The string version of this documents _id. * @@ -334,7 +365,7 @@ function $__hasIncludedChildren(fields) { */ function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren, isBeforeSetters, pathsToSkip) { - const paths = Object.keys(doc.schema.paths); + const paths = Object.keys(doc.$__schema.paths); const plen = paths.length; for (let i = 0; i < plen; ++i) { @@ -346,12 +377,11 @@ function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren, isB continue; } - const type = doc.schema.paths[p]; - const path = p.indexOf('.') === -1 ? [p] : p.split('.'); + const type = doc.$__schema.paths[p]; + const path = type.splitPath(); const len = path.length; let included = false; let doc_ = doc._doc; - for (let j = 0; j < len; ++j) { if (doc_ == null) { break; @@ -460,7 +490,7 @@ function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren, isB Document.prototype.$__buildDoc = function(obj, fields, skipId, exclude, hasIncludedChildren) { const doc = {}; - const paths = Object.keys(this.schema.paths). + const paths = Object.keys(this.$__schema.paths). // Don't build up any paths that are underneath a map, we don't know // what the keys will be filter(p => !p.includes('$*')); @@ -479,7 +509,7 @@ Document.prototype.$__buildDoc = function(obj, fields, skipId, exclude, hasInclu } } - const path = p.split('.'); + const path = this.$__schema.paths[p].splitPath(); const len = path.length; const last = len - 1; let curPath = ''; @@ -580,6 +610,7 @@ Document.prototype.$__init = function(doc, opts) { } child.$__.parent = this; } + item._childDocs = []; } } @@ -591,7 +622,6 @@ Document.prototype.$__init = function(doc, opts) { this.constructor.emit('init', this); this.$__._id = this._id; - return this; }; @@ -660,12 +690,12 @@ function init(self, obj, doc, opts, prefix) { function _init(index) { i = keys[index]; path = prefix + i; - schema = self.schema.path(path); + schema = self.$__schema.path(path); // Should still work if not a model-level discriminator, but should not be // necessary. This is *only* to catch the case where we queried using the // base model and the discriminated model has a projection - if (self.schema.$isRootDiscriminator && !self.isSelected(path)) { + if (self.$__schema.$isRootDiscriminator && !self.$__isSelected(path)) { return; } @@ -770,10 +800,10 @@ Document.prototype.update = function update() { Document.prototype.updateOne = function updateOne(doc, options, callback) { const query = this.constructor.updateOne({ _id: this._id }, doc, options); - query._pre(cb => { + query.pre(cb => { this.constructor._middleware.execPre('updateOne', this, [this], cb); }); - query._post(cb => { + query.post(cb => { this.constructor._middleware.execPost('updateOne', this, [this], {}, cb); }); @@ -795,7 +825,7 @@ Document.prototype.updateOne = function updateOne(doc, options, callback) { * * ####Valid options: * - * - same as in [Model.replaceOne](#model_Model.replaceOne) + * - same as in [Model.replaceOne](https://mongoosejs.com/docs/api/model.html#model_Model.replaceOne) * * @see Model.replaceOne #model_Model.replaceOne * @param {Object} doc @@ -838,12 +868,22 @@ Document.prototype.replaceOne = function replaceOne() { Document.prototype.$session = function $session(session) { if (arguments.length === 0) { + if (this.$__.session != null && this.$__.session.hasEnded) { + this.$__.session = null; + return null; + } return this.$__.session; } + + if (session != null && session.hasEnded) { + throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + + 'called `endSession()` on the session you are passing to `$session()`.'); + } + this.$__.session = session; if (!this.ownerDocument) { - const subdocs = this.$__getAllSubdocs(); + const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } @@ -873,10 +913,10 @@ Document.prototype.overwrite = function overwrite(obj) { continue; } // Explicitly skip version key - if (this.schema.options.versionKey && key === this.schema.options.versionKey) { + if (this.$__schema.options.versionKey && key === this.$__schema.options.versionKey) { continue; } - if (this.schema.options.discriminatorKey && key === this.schema.options.discriminatorKey) { + if (this.$__schema.options.discriminatorKey && key === this.$__schema.options.discriminatorKey) { continue; } this.$set(key, obj[key]); @@ -909,6 +949,7 @@ Document.prototype.$set = function $set(path, val, type, options) { const merge = options.merge; const adhoc = type && type !== true; const constructing = type === true; + const typeKey = this.$__schema.options.typeKey; let adhocs; let keys; let i = 0; @@ -922,7 +963,7 @@ Document.prototype.$set = function $set(path, val, type, options) { if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); - adhocs[path] = this.schema.interpretAsType(path, type, this.schema.options); + adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { @@ -938,9 +979,13 @@ Document.prototype.$set = function $set(path, val, type, options) { path = path._doc; } } + if (path == null) { + const _ = path; + path = val; + val = _; + } prefix = val ? val + '.' : ''; - keys = Object.keys(path); const len = keys.length; @@ -958,7 +1003,7 @@ Document.prototype.$set = function $set(path, val, type, options) { for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix + key; - pathtype = this.schema.pathType(pathName); + pathtype = this.$__schema.pathType(pathName); // On initial set, delete any nested keys if we're going to overwrite // them to ensure we keep the user's key order. @@ -971,6 +1016,9 @@ Document.prototype.$set = function $set(path, val, type, options) { delete this._doc[key]; // Make sure we set `{}` back even if we minimize re: gh-8565 options = Object.assign({}, options, { _skipMinimizeTopLevel: true }); + } else { + // Make sure we set `{_skipMinimizeTopLevel: false}` if don't have overwrite: gh-10441 + options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); } const someCondition = typeof path[key] === 'object' && @@ -981,9 +1029,9 @@ Document.prototype.$set = function $set(path, val, type, options) { pathtype !== 'real' && pathtype !== 'adhocOrUndefined' && !(this.$__path(pathName) instanceof MixedSchema) && - !(this.schema.paths[pathName] && - this.schema.paths[pathName].options && - this.schema.paths[pathName].options.ref); + !(this.$__schema.paths[pathName] && + this.$__schema.paths[pathName].options && + this.$__schema.paths[pathName].options.ref); if (someCondition) { this.$__.$setCalled.add(prefix + key); @@ -1002,8 +1050,8 @@ Document.prototype.$set = function $set(path, val, type, options) { if (pathtype === 'real' || pathtype === 'virtual') { // Check for setting single embedded schema to document (gh-3535) let p = path[key]; - if (this.schema.paths[pathName] && - this.schema.paths[pathName].$isSingleNested && + if (this.$__schema.paths[pathName] && + this.$__schema.paths[pathName].$isSingleNested && path[key] instanceof Document) { p = p.toObject({ virtuals: false, transform: false }); } @@ -1028,7 +1076,7 @@ Document.prototype.$set = function $set(path, val, type, options) { this.$__.$setCalled.add(path); } - let pathType = this.schema.pathType(path); + let pathType = this.$__schema.pathType(path); if (pathType === 'adhocOrUndefined') { pathType = getEmbeddedDiscriminatorPath(this, path, { typeOnly: true }); } @@ -1079,8 +1127,8 @@ Document.prototype.$set = function $set(path, val, type, options) { const parts = path.indexOf('.') === -1 ? [path] : path.split('.'); // Might need to change path for top-level alias - if (typeof this.schema.aliases[parts[0]] == 'string') { - parts[0] = this.schema.aliases[parts[0]]; + if (typeof this.$__schema.aliases[parts[0]] == 'string') { + parts[0] = this.$__schema.aliases[parts[0]]; } if (pathType === 'adhocOrUndefined' && strict) { @@ -1091,12 +1139,12 @@ Document.prototype.$set = function $set(path, val, type, options) { const subpath = parts.slice(0, i + 1).join('.'); // If path is underneath a virtual, bypass everything and just set it. - if (i + 1 < parts.length && this.schema.pathType(subpath) === 'virtual') { + if (i + 1 < parts.length && this.$__schema.pathType(subpath) === 'virtual') { mpath.set(path, val, this); return this; } - schema = this.schema.path(subpath); + schema = this.$__schema.path(subpath); if (schema == null) { continue; } @@ -1120,7 +1168,7 @@ Document.prototype.$set = function $set(path, val, type, options) { return this; } } else if (pathType === 'virtual') { - schema = this.schema.virtualpath(path); + schema = this.$__schema.virtualpath(path); schema.applySetters(val, this); return this; } else { @@ -1140,7 +1188,7 @@ Document.prototype.$set = function $set(path, val, type, options) { // traverse the element ({nested: null})` is not likely. If user gets // that error, its their fault for now. We should reconsider disallowing // modifying not selected paths for 6.x - if (!this.isSelected(curPath)) { + if (!this.$__isSelected(curPath)) { this.unmarkModified(curPath); } cur = this.$__getValue(curPath); @@ -1234,27 +1282,26 @@ Document.prototype.$set = function $set(path, val, type, options) { let didPopulate = false; if (refMatches && val instanceof Document) { this.populated(path, val._id, { [populateModelSymbol]: val.constructor }); + val.$__.wasPopulated = true; didPopulate = true; } let popOpts; if (schema.options && - Array.isArray(schema.options[this.schema.options.typeKey]) && - schema.options[this.schema.options.typeKey].length && - schema.options[this.schema.options.typeKey][0].ref && - _isManuallyPopulatedArray(val, schema.options[this.schema.options.typeKey][0].ref)) { - if (this.ownerDocument) { - popOpts = { [populateModelSymbol]: val[0].constructor }; - this.ownerDocument().populated(this.$__fullPath(path), - val.map(function(v) { return v._id; }), popOpts); - } else { - popOpts = { [populateModelSymbol]: val[0].constructor }; - this.populated(path, val.map(function(v) { return v._id; }), popOpts); + Array.isArray(schema.options[typeKey]) && + schema.options[typeKey].length && + schema.options[typeKey][0].ref && + _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { + popOpts = { [populateModelSymbol]: val[0].constructor }; + this.populated(path, val.map(function(v) { return v._id; }), popOpts); + + for (const doc of val) { + doc.$__.wasPopulated = true; } didPopulate = true; } - if (this.schema.singleNestedPaths[path] == null) { + if (this.$__schema.singleNestedPaths[path] == null) { // If this path is underneath a single nested schema, we'll call the setter // later in `$__set()` because we don't take `_doc` when we iterate through // a single nested doc. That's to make sure we get the correct context. @@ -1413,11 +1460,11 @@ Document.prototype.$__shouldModify = function(pathToMark, path, constructing, pa // Re: the note about gh-7196, `val` is the raw value without casting or // setters if the full path is under a single nested subdoc because we don't // want to double run setters. So don't set it as modified. See gh-7264. - if (this.schema.singleNestedPaths[path] != null) { + if (this.$__schema.singleNestedPaths[path] != null) { return false; } - if (val === void 0 && !this.isSelected(path)) { + if (val === void 0 && !this.$__isSelected(path)) { // when a path is not selected in a query, its initial // value will be undefined. return true; @@ -1576,20 +1623,20 @@ Document.prototype.get = function(path, type, options) { let adhoc; options = options || {}; if (type) { - adhoc = this.schema.interpretAsType(path, type, this.schema.options); + adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } let schema = this.$__path(path); if (schema == null) { - schema = this.schema.virtualpath(path); + schema = this.$__schema.virtualpath(path); } if (schema instanceof MixedSchema) { - const virtual = this.schema.virtualpath(path); + const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } - const pieces = path.split('.'); + const pieces = path.indexOf('.') === -1 ? [path] : path.split('.'); let obj = this._doc; if (schema instanceof VirtualType) { @@ -1597,8 +1644,8 @@ Document.prototype.get = function(path, type, options) { } // Might need to change path for top-level alias - if (typeof this.schema.aliases[pieces[0]] == 'string') { - pieces[0] = this.schema.aliases[pieces[0]]; + if (typeof this.$__schema.aliases[pieces[0]] == 'string') { + pieces[0] = this.$__schema.aliases[pieces[0]]; } for (let i = 0, l = pieces.length; i < l; i++) { @@ -1623,7 +1670,7 @@ Document.prototype.get = function(path, type, options) { if (schema != null && options.getters !== false) { obj = schema.applyGetters(obj, this); - } else if (this.schema.nested[path] && options.virtuals) { + } else if (this.$__schema.nested[path] && options.virtuals) { // Might need to apply virtuals if this is a nested path return applyVirtuals(this, utils.clone(obj) || {}, { path: path }); } @@ -1654,7 +1701,7 @@ Document.prototype.$__path = function(path) { if (adhocType) { return adhocType; } - return this.schema.path(path); + return this.$__schema.path(path); }; /** @@ -2058,7 +2105,7 @@ Document.prototype.isSelected = function isSelected(path) { path = path.split(' '); } if (Array.isArray(path)) { - return path.some(p => this.isSelected(p)); + return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); @@ -2107,7 +2154,7 @@ Document.prototype.isSelected = function isSelected(path) { return !inclusive; }; -Document.prototype[documentIsSelected] = Document.prototype.isSelected; +Document.prototype.$__isSelected = Document.prototype.isSelected; /** * Checks if `path` was explicitly selected. If no projection, always returns @@ -2189,6 +2236,7 @@ Document.prototype.isDirectSelected = function isDirectSelected(path) { * @param {Array|String} [pathsToValidate] list of paths to validate. If set, Mongoose will validate only the modified paths that are in the given list. * @param {Object} [options] internal options * @param {Boolean} [options.validateModifiedOnly=false] if `true` mongoose validates only modified paths. + * @param {Array|string} [options.pathsToSkip] list of paths to skip. If set, Mongoose will validate every modified path that is not in this list. * @param {Function} [callback] optional callback called after validation completes, passing an error if one occurred * @return {Promise} Promise * @api public @@ -2209,7 +2257,17 @@ Document.prototype.validate = function(pathsToValidate, options, callback) { this.$__.validating = new ParallelValidateError(this, { parentStack: options && options.parentStack }); } - if (typeof pathsToValidate === 'function') { + if (arguments.length === 1) { + if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { + options = arguments[0]; + callback = null; + pathsToValidate = null; + } else if (typeof arguments[0] === 'function') { + callback = arguments[0]; + options = null; + pathsToValidate = null; + } + } else if (typeof pathsToValidate === 'function') { callback = pathsToValidate; options = null; pathsToValidate = null; @@ -2218,6 +2276,10 @@ Document.prototype.validate = function(pathsToValidate, options, callback) { options = pathsToValidate; pathsToValidate = null; } + if (options && typeof options.pathsToSkip === 'string') { + const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; + options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); + } return promiseOrCallback(callback, cb => { if (parallelValidate != null) { @@ -2237,7 +2299,7 @@ Document.prototype.validate = function(pathsToValidate, options, callback) { function _evaluateRequiredFunctions(doc) { Object.keys(doc.$__.activePaths.states.require).forEach(path => { - const p = doc.schema.path(path); + const p = doc.$__schema.path(path); if (p != null && typeof p.originalRequiredValue === 'function') { doc.$__.cachedRequired[path] = p.originalRequiredValue.call(doc, doc); @@ -2253,10 +2315,9 @@ function _getPathsToValidate(doc) { const skipSchemaValidators = {}; _evaluateRequiredFunctions(doc); - // only validate required fields when necessary let paths = new Set(Object.keys(doc.$__.activePaths.states.require).filter(function(path) { - if (!doc.isSelected(path) && !doc.isModified(path)) { + if (!doc.$__isSelected(path) && !doc.isModified(path)) { return false; } if (path in doc.$__.cachedRequired) { @@ -2271,7 +2332,7 @@ function _getPathsToValidate(doc) { Object.keys(doc.$__.activePaths.states.default).forEach(addToPaths); function addToPaths(p) { paths.add(p); } - const subdocs = doc.$__getAllSubdocs(); + const subdocs = doc.$getAllSubdocs(); const modifiedPaths = doc.modifiedPaths(); for (const subdoc of subdocs) { if (subdoc.$basePath) { @@ -2298,7 +2359,7 @@ function _getPathsToValidate(doc) { // gh-661: if a whole array is modified, make sure to run validation on all // the children as well for (const path of paths) { - const _pathType = doc.schema.path(path); + const _pathType = doc.$__schema.path(path); if (!_pathType || !_pathType.$isMongooseArray || // To avoid potential performance issues, skip doc arrays whose children @@ -2327,26 +2388,25 @@ function _getPathsToValidate(doc) { const flattenOptions = { skipArrays: true }; for (const pathToCheck of paths) { - if (doc.schema.nested[pathToCheck]) { + if (doc.$__schema.nested[pathToCheck]) { let _v = doc.$__getValue(pathToCheck); if (isMongooseObject(_v)) { _v = _v.toObject({ transform: false }); } - const flat = flatten(_v, pathToCheck, flattenOptions, doc.schema); + const flat = flatten(_v, pathToCheck, flattenOptions, doc.$__schema); Object.keys(flat).forEach(addToPaths); } } - for (const path of paths) { // Single nested paths (paths embedded under single nested subdocs) will // be validated on their own when we call `validate()` on the subdoc itself. // Re: gh-8468 - if (doc.schema.singleNestedPaths.hasOwnProperty(path)) { + if (doc.$__schema.singleNestedPaths.hasOwnProperty(path)) { paths.delete(path); continue; } - const _pathType = doc.schema.path(path); + const _pathType = doc.$__schema.path(path); if (!_pathType || !_pathType.$isSchemaMap) { continue; } @@ -2382,11 +2442,13 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) { (typeof options === 'object') && ('validateModifiedOnly' in options); + const pathsToSkip = get(options, 'pathsToSkip', null); + let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { - shouldValidateModifiedOnly = this.schema.options.validateModifiedOnly; + shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const _this = this; @@ -2431,16 +2493,19 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) { pathDetails[0].filter((path) => this.isModified(path)) : pathDetails[0]; const skipSchemaValidators = pathDetails[1]; - + if (typeof pathsToValidate === 'string') { + pathsToValidate = pathsToValidate.split(' '); + } if (Array.isArray(pathsToValidate)) { paths = _handlePathsToValidate(paths, pathsToValidate); + } else if (pathsToSkip) { + paths = _handlePathsToSkip(paths, pathsToSkip); } - if (paths.length === 0) { - return process.nextTick(function() { + return immediate(function() { const error = _complete(); if (error) { - return _this.schema.s.hooks.execPost('validate:error', _this, [_this], { error: error }, function(error) { + return _this.$__schema.s.hooks.execPost('validate:error', _this, [_this], { error: error }, function(error) { callback(error); }); } @@ -2451,17 +2516,11 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) { const validated = {}; let total = 0; - const complete = function() { - const error = _complete(); - if (error) { - return _this.schema.s.hooks.execPost('validate:error', _this, [_this], { error: error }, function(error) { - callback(error); - }); - } - callback(null, _this); - }; + for (const path of paths) { + validatePath(path); + } - const validatePath = function(path) { + function validatePath(path) { if (path == null || validated[path]) { return; } @@ -2469,8 +2528,8 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) { validated[path] = true; total++; - process.nextTick(function() { - const schemaType = _this.schema.path(path); + immediate(function() { + const schemaType = _this.$__schema.path(path); if (!schemaType) { return --total || complete(); @@ -2482,6 +2541,11 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) { return; } + // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) + if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { + return --total || complete(); + } + let val = _this.$__getValue(path); // If you `populate()` and get back a null value, required validators @@ -2497,7 +2561,8 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) { const doValidateOptions = { skipSchemaValidators: skipSchemaValidators[path], - path: path + path: path, + validateModifiedOnly: shouldValidateModifiedOnly }; schemaType.doValidate(val, function(err) { if (err && (!schemaType.$isMongooseDocumentArray || err.$isArrayValidatorError)) { @@ -2511,12 +2576,18 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) { --total || complete(); }, scope, doValidateOptions); }); - }; + } - const numPaths = paths.length; - for (let i = 0; i < numPaths; ++i) { - validatePath(paths[i]); + function complete() { + const error = _complete(); + if (error) { + return _this.$__schema.s.hooks.execPost('validate:error', _this, [_this], { error: error }, function(error) { + callback(error); + }); + } + callback(null, _this); } + }; /*! @@ -2552,6 +2623,15 @@ function _handlePathsToValidate(paths, pathsToValidate) { return ret; } +/*! + * ignore + */ +function _handlePathsToSkip(paths, pathsToSkip) { + pathsToSkip = new Set(pathsToSkip); + paths = paths.filter(p => !pathsToSkip.has(p)); + return paths; +} + /** * Executes registered validation rules (skipping asynchronous validators) for this document. * @@ -2571,6 +2651,7 @@ function _handlePathsToValidate(paths, pathsToValidate) { * @param {Array|string} pathsToValidate only validate the given paths * @param {Object} [options] options for validation * @param {Boolean} [options.validateModifiedOnly=false] If `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths. + * @param {Array|string} [options.pathsToSkip] list of paths to skip. If set, Mongoose will validate every modified path that is not in this list. * @return {ValidationError|undefined} ValidationError if there are errors during validation, or undefined if there is no error. * @api public */ @@ -2578,6 +2659,11 @@ function _handlePathsToValidate(paths, pathsToValidate) { Document.prototype.validateSync = function(pathsToValidate, options) { const _this = this; + if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { + options = arguments[0]; + pathsToValidate = null; + } + const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); @@ -2586,11 +2672,16 @@ Document.prototype.validateSync = function(pathsToValidate, options) { if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { - shouldValidateModifiedOnly = this.schema.options.validateModifiedOnly; + shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } + let pathsToSkip = options && options.pathsToSkip; + if (typeof pathsToValidate === 'string') { - pathsToValidate = pathsToValidate.split(' '); + const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; + pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); + } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { + pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary @@ -2602,8 +2693,9 @@ Document.prototype.validateSync = function(pathsToValidate, options) { if (Array.isArray(pathsToValidate)) { paths = _handlePathsToValidate(paths, pathsToValidate); + } else if (Array.isArray(pathsToSkip)) { + paths = _handlePathsToSkip(paths, pathsToSkip); } - const validating = {}; paths.forEach(function(path) { @@ -2613,7 +2705,7 @@ Document.prototype.validateSync = function(pathsToValidate, options) { validating[path] = true; - const p = _this.schema.path(path); + const p = _this.$__schema.path(path); if (!p) { return; } @@ -2624,7 +2716,8 @@ Document.prototype.validateSync = function(pathsToValidate, options) { const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], - path: path + path: path, + validateModifiedOnly: shouldValidateModifiedOnly }); if (err && (!p.$isMongooseDocumentArray || err.$isArrayValidatorError)) { if (p.$isSingleNested && @@ -2885,7 +2978,13 @@ Document.prototype.$__reset = function reset() { }). forEach(function(doc) { doc.$__reset(); - _this.$__.activePaths.init(doc.$basePath); + if (doc.$__parent === _this) { + _this.$__.activePaths.init(doc.$basePath); + } else if (doc.$__parent != null && doc.$__parent.ownerDocument) { + // If map path underneath subdocument, may end up with a case where + // map path is modified but parent still needs to be reset. See gh-10295 + doc.$__parent.$__reset(); + } }); // clear atomics @@ -2912,7 +3011,7 @@ Document.prototype.$__reset = function reset() { this.$__.validationError = undefined; this.errors = undefined; _this = this; - this.schema.requiredPaths().forEach(function(path) { + this.$__schema.requiredPaths().forEach(function(path) { _this.$__.activePaths.require(path); }); @@ -2942,7 +3041,7 @@ Document.prototype.$__undoReset = function $__undoReset() { } } - for (const subdoc of this.$__getAllSubdocs()) { + for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } }; @@ -2966,7 +3065,6 @@ Document.prototype.$__dirty = function() { schema: _this.$__path(path) }; }); - // gh-2558: if we had to set a default and the value is not undefined, // we have to save as well all = all.concat(this.$__.activePaths.map('default', function(path) { @@ -3010,7 +3108,6 @@ Document.prototype.$__dirty = function() { top.value[arrayAtomicsSymbol].$set = top.value; } }); - top = lastPath = null; return minimal; }; @@ -3033,8 +3130,10 @@ Document.prototype.$__setSchema = function(schema) { for (const key of Object.keys(schema.virtuals)) { schema.virtuals[key]._applyDefaultGetters(); } - - this.schema = schema; + if (schema.path('schema') == null) { + this.schema = schema; + } + this.$__schema = schema; this[documentSchemaSymbol] = schema; }; @@ -3070,21 +3169,25 @@ Document.prototype.$__getArrayPathsToValidate = function() { /** * Get all subdocs (by bfs) * - * @api private - * @method $__getAllSubdocs + * @api public + * @method $getAllSubdocs * @memberOf Document * @instance */ -Document.prototype.$__getAllSubdocs = function() { +Document.prototype.$getAllSubdocs = function $getAllSubdocs() { DocumentArray || (DocumentArray = require('./types/documentarray')); Embedded = Embedded || require('./types/embedded'); function docReducer(doc, seed, path) { let val = doc; + let isNested = false; if (path) { if (doc instanceof Document && doc[documentSchemaSymbol].paths[path]) { val = doc._doc[path]; + } else if (doc instanceof Document && doc[documentSchemaSymbol].nested[path]) { + val = doc._doc[path]; + isNested = true; } else { val = doc[path]; } @@ -3112,18 +3215,18 @@ Document.prototype.$__getAllSubdocs = function() { seed.push(doc); } }); - } else if (val instanceof Document && val.$__isNested) { - seed = Object.keys(val).reduce(function(seed, path) { - return docReducer(val, seed, path); - }, seed); + } else if (isNested && val != null) { + for (const path of Object.keys(val)) { + docReducer(val, seed, path); + } } return seed; } - const _this = this; - const subDocs = Object.keys(this._doc).reduce(function(seed, path) { - return docReducer(_this, seed, path); - }, []); + const subDocs = []; + for (const path of Object.keys(this._doc)) { + docReducer(this, subDocs, path); + } return subDocs; }; @@ -3133,7 +3236,7 @@ Document.prototype.$__getAllSubdocs = function() { */ function applyQueue(doc) { - const q = doc.schema && doc.schema.callQueue; + const q = doc.$__schema && doc.$__schema.callQueue; if (!q.length) { return; } @@ -3175,7 +3278,7 @@ Document.prototype.$toObject = function(options, json) { const path = json ? 'toJSON' : 'toObject'; const baseOptions = get(this, 'constructor.base.options.' + path, {}); - const schemaOptions = get(this, 'schema.options', {}); + const schemaOptions = get(this, '$__schema.options', {}); // merge base default options with Schema's set default options if available. // `clone` is necessary here because `utils.options` directly modifies the second input. defaultOptions = utils.options(defaultOptions, clone(baseOptions)); @@ -3185,10 +3288,6 @@ Document.prototype.$toObject = function(options, json) { options = utils.isPOJO(options) ? clone(options) : {}; options._calledWithOptions = options._calledWithOptions || clone(options); - if (!('flattenMaps' in options)) { - options.flattenMaps = defaultOptions.flattenMaps; - } - let _minimize; if (options._calledWithOptions.minimize != null) { _minimize = options.minimize; @@ -3198,6 +3297,15 @@ Document.prototype.$toObject = function(options, json) { _minimize = schemaOptions.minimize; } + let flattenMaps; + if (options._calledWithOptions.flattenMaps != null) { + flattenMaps = options.flattenMaps; + } else if (defaultOptions.flattenMaps != null) { + flattenMaps = defaultOptions.flattenMaps; + } else { + flattenMaps = schemaOptions.flattenMaps; + } + // The original options that will be passed to `clone()`. Important because // `clone()` will recursively call `$toObject()` on embedded docs, so we // need the original options the user passed in, plus `_isNested` and @@ -3205,7 +3313,8 @@ Document.prototype.$toObject = function(options, json) { const cloneOptions = Object.assign(utils.clone(options), { _isNested: true, json: json, - minimize: _minimize + minimize: _minimize, + flattenMaps: flattenMaps }); if (utils.hasUserDefinedProperty(options, 'getters')) { @@ -3254,8 +3363,8 @@ Document.prototype.$toObject = function(options, json) { applyVirtuals(this, ret, gettersOptions, options); } - if (options.versionKey === false && this.schema.options.versionKey) { - delete ret[this.schema.options.versionKey]; + if (options.versionKey === false && this.$__schema.options.versionKey) { + delete ret[this.$__schema.options.versionKey]; } let transform = options.transform; @@ -3485,7 +3594,7 @@ function minimize(obj) { */ function applyVirtuals(self, json, options, toObjectOptions) { - const schema = self.schema; + const schema = self.$__schema; const paths = Object.keys(schema.virtuals); let i = paths.length; const numPaths = i; @@ -3495,6 +3604,19 @@ function applyVirtuals(self, json, options, toObjectOptions) { let v; const aliases = get(toObjectOptions, 'aliases', true); + let virtualsToApply = null; + if (Array.isArray(options.virtuals)) { + virtualsToApply = new Set(options.virtuals); + } + else if (options.virtuals && options.virtuals.pathsToSkip) { + virtualsToApply = new Set(paths); + for (let i = 0; i < options.virtuals.pathsToSkip.length; i++) { + if (virtualsToApply.has(options.virtuals.pathsToSkip[i])) { + virtualsToApply.delete(options.virtuals.pathsToSkip[i]); + } + } + } + if (!cur) { return json; } @@ -3503,6 +3625,10 @@ function applyVirtuals(self, json, options, toObjectOptions) { for (i = 0; i < numPaths; ++i) { path = paths[i]; + if (virtualsToApply != null && !virtualsToApply.has(path)) { + continue; + } + // Allow skipping aliases with `toObject({ virtuals: true, aliases: false })` if (!aliases && schema.aliases.hasOwnProperty(path)) { continue; @@ -3535,6 +3661,7 @@ function applyVirtuals(self, json, options, toObjectOptions) { return json; } + /*! * Applies virtuals properties to `json`. * @@ -3544,7 +3671,7 @@ function applyVirtuals(self, json, options, toObjectOptions) { */ function applyGetters(self, json, options) { - const schema = self.schema; + const schema = self.$__schema; const paths = Object.keys(schema.paths); let i = paths.length; let path; @@ -3565,7 +3692,7 @@ function applyGetters(self, json, options) { let part; cur = self._doc; - if (!self.isSelected(path)) { + if (!self.$__isSelected(path)) { continue; } @@ -3599,7 +3726,7 @@ function applyGetters(self, json, options) { */ function applySchemaTypeTransforms(self, json) { - const schema = self.schema; + const schema = self.$__schema; const paths = Object.keys(schema.paths || {}); const cur = self._doc; @@ -3642,7 +3769,7 @@ function throwErrorIfPromise(path, transformedValue) { */ function omitDeselectedFields(self, json) { - const schema = self.schema; + const schema = self.$__schema; const paths = Object.keys(schema.paths || {}); const cur = self._doc; @@ -3897,6 +4024,34 @@ Document.prototype.populate = function populate() { return this; }; +/** + * Gets all populated documents associated with this document. + * + * @api public + * @return {Array} array of populated documents. Empty array if there are no populated documents associated with this document. + * @memberOf Document + * @instance + */ +Document.prototype.$getPopulatedDocs = function $getPopulatedDocs() { + let keys = []; + if (this.$__.populated != null) { + keys = keys.concat(Object.keys(this.$__.populated)); + } + if (this.$$populatedVirtuals != null) { + keys = keys.concat(Object.keys(this.$$populatedVirtuals)); + } + let result = []; + for (const key of keys) { + const value = this.get(key); + if (Array.isArray(value)) { + result = result.concat(value); + } else if (value instanceof Document) { + result.push(value); + } + } + return result; +}; + /** * Explicitly executes population and returns a promise. Useful for promises integration. * @@ -3966,25 +4121,21 @@ Document.prototype.execPopulate = function(callback) { Document.prototype.populated = function(path, val, options) { // val and options are internal - if (val === null || val === void 0) { + if (val == null || val === true) { if (!this.$__.populated) { return undefined; } - const v = this.$__.populated[path]; + + // Map paths can be populated with either `path.$*` or just `path` + const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; + + const v = this.$__.populated[_path]; if (v) { - return v.value; + return val === true ? v : v.value; } return undefined; } - // internal - if (val === true) { - if (!this.$__.populated) { - return undefined; - } - return this.$__.populated[path]; - } - this.$__.populated || (this.$__.populated = {}); this.$__.populated[path] = { value: val, options: options }; @@ -4017,7 +4168,7 @@ Document.prototype.populated = function(path, val, options) { * console.log(doc.author); // '5144cf8050f071d979c118a7' * }) * - * If the path was not populated, this is a no-op. + * If the path was not provided, then all populated fields are returned to their unpopulated state. * * @param {String} path * @return {Document} this @@ -4052,7 +4203,7 @@ Document.prototype.depopulate = function(path) { continue; } delete populated[key]; - this.$set(key, populatedIds); + utils.setValue(key, populatedIds, this._doc); } return this; } @@ -4065,7 +4216,7 @@ Document.prototype.depopulate = function(path) { delete this.$$populatedVirtuals[singlePath]; delete this._doc[singlePath]; } else if (populatedIds) { - this.$set(singlePath, populatedIds); + utils.setValue(singlePath, populatedIds, this._doc); } } return this; @@ -4131,7 +4282,6 @@ Document.prototype.$__fullPath = function(path) { Document.prototype.getChanges = function() { const delta = this.$__delta(); - const changes = delta ? delta[1] : {}; return changes; }; diff --git a/lib/drivers/browser/index.js b/lib/drivers/browser/index.js index 56d0b8a75c9..bc4ac5f6b28 100644 --- a/lib/drivers/browser/index.js +++ b/lib/drivers/browser/index.js @@ -8,6 +8,9 @@ exports.Binary = require('./binary'); exports.Collection = function() { throw new Error('Cannot create a collection from browser library'); }; +exports.getConnection = () => function() { + throw new Error('Cannot create a connection from browser library'); +}; exports.Decimal128 = require('./decimal128'); exports.ObjectId = require('./objectid'); exports.ReadPreference = require('./ReadPreference'); diff --git a/lib/drivers/node-mongodb-native/collection.js b/lib/drivers/node-mongodb-native/collection.js index fdb5aae0161..34241f2d2eb 100644 --- a/lib/drivers/node-mongodb-native/collection.js +++ b/lib/drivers/node-mongodb-native/collection.js @@ -7,7 +7,9 @@ const MongooseCollection = require('../../collection'); const MongooseError = require('../../error/mongooseError'); const Collection = require('mongodb').Collection; +const ObjectId = require('./objectid'); const get = require('../../helpers/get'); +const getConstructorName = require('../../helpers/getConstructorName'); const sliced = require('sliced'); const stream = require('stream'); const util = require('util'); @@ -21,9 +23,11 @@ const util = require('util'); * @api private */ -function NativeCollection(name, options) { +function NativeCollection(name, conn, options) { this.collection = null; this.Promise = options.Promise || Promise; + this.modelName = options.modelName; + delete options.modelName; this._closed = false; MongooseCollection.apply(this, arguments); } @@ -54,6 +58,7 @@ NativeCollection.prototype.onOpen = function() { if (_this.opts.autoCreate === false) { _this.collection = _this.conn.db.collection(_this.name); + MongooseCollection.prototype.onOpen.call(_this); return _this.collection; } @@ -127,6 +132,7 @@ function iter(i) { const _this = this; const debug = get(_this, 'conn.base.options.debug'); const lastArg = arguments[arguments.length - 1]; + const opId = new ObjectId(); // If user force closed, queueing will hang forever. See #5664 if (this.conn.$wasForceClosed) { @@ -140,12 +146,20 @@ function iter(i) { } } + let _args = args; + let callback = null; if (this._shouldBufferCommands() && this.buffer) { if (syncCollectionMethods[i]) { throw new Error('Collection method ' + i + ' is synchronous'); } - this.conn.emit('buffer', { method: i, args: args }); + this.conn.emit('buffer', { + _id: opId, + modelName: _this.modelName, + collectionName: _this.name, + method: i, + args: args + }); let callback; let _args; @@ -181,7 +195,9 @@ function iter(i) { if (removed) { const message = 'Operation `' + this.name + '.' + i + '()` buffering timed out after ' + bufferTimeoutMS + 'ms'; - callback(new MongooseError(message)); + const err = new MongooseError(message); + this.conn.emit('buffer-end', { _id: opId, modelName: _this.modelName, collectionName: _this.name, method: i, error: err }); + callback(err); } }, bufferTimeoutMS); @@ -191,6 +207,16 @@ function iter(i) { } return promise; + } else if (!syncCollectionMethods[i] && typeof lastArg === 'function') { + callback = function collectionOperationCallback(err, res) { + if (err != null) { + _this.conn.emit('operation-end', { _id: opId, modelName: _this.modelName, collectionName: _this.name, method: i, error: err }); + } else { + _this.conn.emit('operation-end', { _id: opId, modelName: _this.modelName, collectionName: _this.name, method: i, result: res }); + } + return lastArg.apply(this, arguments); + }; + _args = args.slice(0, args.length - 1).concat([callback]); } if (debug) { @@ -206,6 +232,8 @@ function iter(i) { } } + this.conn.emit('operation-start', { _id: opId, modelName: _this.modelName, collectionName: this.name, method: i, params: _args }); + try { if (collection == null) { const message = 'Cannot call `' + this.name + '.' + i + '()` before initial connection ' + @@ -213,14 +241,30 @@ function iter(i) { 'you have `bufferCommands = false`.'; throw new MongooseError(message); } - - return collection[i].apply(collection, args); + const ret = collection[i].apply(collection, _args); + if (ret != null && typeof ret.then === 'function') { + return ret.then( + res => { + this.conn.emit('operation-end', { _id: opId, modelName: this.modelName, collectionName: this.name, method: i, result: res }); + return res; + }, + err => { + this.conn.emit('operation-end', { _id: opId, modelName: this.modelName, collectionName: this.name, method: i, error: err }); + throw err; + } + ); + } + return ret; } catch (error) { // Collection operation may throw because of max bson size, catch it here // See gh-3906 - if (args.length > 0 && - typeof args[args.length - 1] === 'function') { - args[args.length - 1](error); + if (typeof callback === 'function') { + callback(error); + } else { + this.conn.emit('operation-end', { _id: opId, modelName: _this.modelName, collectionName: this.name, method: i, error: error }); + } + if (typeof lastArg === 'function') { + lastArg(error); } else { throw error; } @@ -333,14 +377,15 @@ function format(obj, sub, color, shell) { const clone = require('../../helpers/clone'); let x = clone(obj, { transform: false }); + const constructorName = getConstructorName(x); - if (x.constructor.name === 'Binary') { + if (constructorName === 'Binary') { x = 'BinData(' + x.sub_type + ', "' + x.toString('base64') + '")'; - } else if (x.constructor.name === 'ObjectID') { + } else if (constructorName === 'ObjectID') { x = inspectable('ObjectId("' + x.toHexString() + '")'); - } else if (x.constructor.name === 'Date') { + } else if (constructorName === 'Date') { x = inspectable('new Date("' + x.toUTCString() + '")'); - } else if (x.constructor.name === 'Object') { + } else if (constructorName === 'Object') { const keys = Object.keys(x); const numKeys = keys.length; let key; @@ -359,16 +404,17 @@ function format(obj, sub, color, shell) { error = _error; } } - if (x[key].constructor.name === 'Binary') { + const _constructorName = getConstructorName(x[key]); + if (_constructorName === 'Binary') { x[key] = 'BinData(' + x[key].sub_type + ', "' + x[key].buffer.toString('base64') + '")'; - } else if (x[key].constructor.name === 'Object') { + } else if (_constructorName === 'Object') { x[key] = format(x[key], true); - } else if (x[key].constructor.name === 'ObjectID') { + } else if (_constructorName === 'ObjectID') { formatObjectId(x, key); - } else if (x[key].constructor.name === 'Date') { + } else if (_constructorName === 'Date') { formatDate(x, key, shell); - } else if (x[key].constructor.name === 'ClientSession') { + } else if (_constructorName === 'ClientSession') { x[key] = inspectable('ClientSession("' + get(x[key], 'id.id.buffer', '').toString('hex') + '")'); } else if (Array.isArray(x[key])) { diff --git a/lib/drivers/node-mongodb-native/connection.js b/lib/drivers/node-mongodb-native/connection.js index 57e7195448a..9eb9fe812ed 100644 --- a/lib/drivers/node-mongodb-native/connection.js +++ b/lib/drivers/node-mongodb-native/connection.js @@ -6,6 +6,8 @@ const MongooseConnection = require('../../connection'); const STATES = require('../../connectionstate'); +const immediate = require('../../helpers/immediate'); +const setTimeout = require('../../helpers/timers').setTimeout; /** * A [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) connection implementation. @@ -38,16 +40,20 @@ NativeConnection.prototype.__proto__ = MongooseConnection.prototype; * Returns a new connection object, with the new db. If you set the `useCache` * option, `useDb()` will cache connections by `name`. * + * **Note:** Calling `close()` on a `useDb()` connection will close the base connection as well. + * * @param {String} name The database name * @param {Object} [options] * @param {Boolean} [options.useCache=false] If true, cache results so calling `useDb()` multiple times with the same name only creates 1 connection object. + * @param {Boolean} [options.noListener=false] If true, the new connection object won't listen to any events on the base connection. This is better for memory usage in cases where you're calling `useDb()` for every request. * @return {Connection} New Connection Object * @api public */ NativeConnection.prototype.useDb = function(name, options) { // Return immediately if cached - if (options && options.useCache && this.relatedDbs[name]) { + options = options || {}; + if (options.useCache && this.relatedDbs[name]) { return this.relatedDbs[name]; } @@ -90,16 +96,24 @@ NativeConnection.prototype.useDb = function(name, options) { function wireup() { newConn.client = _this.client; - newConn.db = _this.client.db(name); + const _opts = {}; + if (options.hasOwnProperty('noListener')) { + _opts.noListener = options.noListener; + } + newConn.db = _this.client.db(name, _opts); newConn.onOpen(); // setup the events appropriately - listen(newConn); + if (options.noListener !== true) { + listen(newConn); + } } newConn.name = name; // push onto the otherDbs stack, this is used when state changes - this.otherDbs.push(newConn); + if (options.noListener !== true) { + this.otherDbs.push(newConn); + } newConn.otherDbs.push(this); // push onto the relatedDbs cache, this is used when state changes @@ -116,13 +130,16 @@ NativeConnection.prototype.useDb = function(name, options) { */ function listen(conn) { - if (conn.db._listening) { + if (conn._listening) { return; } - conn.db._listening = true; + conn._listening = true; - conn.db.on('close', function(force) { - if (conn._closeCalled) return; + conn.client.on('close', function(force) { + if (conn._closeCalled) { + return; + } + conn._closeCalled = conn.client._closeCalled; // the driver never emits an `open` event. auto_reconnect still // emits a `close` event but since we never get another @@ -134,26 +151,30 @@ function listen(conn) { } conn.onClose(force); }); - conn.db.on('error', function(err) { + conn.client.on('error', function(err) { conn.emit('error', err); }); - conn.db.on('reconnect', function() { - conn.readyState = STATES.connected; - conn.emit('reconnect'); - conn.emit('reconnected'); - conn.onOpen(); - }); - conn.db.on('timeout', function(err) { - conn.emit('timeout', err); - }); - conn.db.on('open', function(err, db) { - if (STATES.disconnected === conn.readyState && db && db.databaseName) { + + if (!conn.client.s.options.useUnifiedTopology) { + conn.db.on('reconnect', function() { conn.readyState = STATES.connected; conn.emit('reconnect'); conn.emit('reconnected'); - } + conn.onOpen(); + }); + conn.db.on('open', function(err, db) { + if (STATES.disconnected === conn.readyState && db && db.databaseName) { + conn.readyState = STATES.connected; + conn.emit('reconnect'); + conn.emit('reconnected'); + } + }); + } + + conn.client.on('timeout', function(err) { + conn.emit('timeout', err); }); - conn.db.on('parseError', function(err) { + conn.client.on('parseError', function(err) { conn.emit('parseError', err); }); } @@ -169,7 +190,7 @@ function listen(conn) { NativeConnection.prototype.doClose = function(force, fn) { if (this.client == null) { - process.nextTick(() => fn()); + immediate(() => fn()); return this; } diff --git a/lib/drivers/node-mongodb-native/index.js b/lib/drivers/node-mongodb-native/index.js index 2cd749e274d..2ed9eb0a5bc 100644 --- a/lib/drivers/node-mongodb-native/index.js +++ b/lib/drivers/node-mongodb-native/index.js @@ -8,4 +8,5 @@ exports.Binary = require('./binary'); exports.Collection = require('./collection'); exports.Decimal128 = require('./decimal128'); exports.ObjectId = require('./objectid'); -exports.ReadPreference = require('./ReadPreference'); \ No newline at end of file +exports.ReadPreference = require('./ReadPreference'); +exports.getConnection = () => require('./connection'); \ No newline at end of file diff --git a/lib/error/cast.js b/lib/error/cast.js index c6ff277086c..dc604b58211 100644 --- a/lib/error/cast.js +++ b/lib/error/cast.js @@ -22,8 +22,9 @@ class CastError extends MongooseError { // If no args, assume we'll `init()` later. if (arguments.length > 0) { const stringValue = getStringValue(value); + const valueType = getValueType(value); const messageFormat = getMessageFormat(schemaType); - const msg = formatMessage(null, type, stringValue, path, messageFormat); + const msg = formatMessage(null, type, stringValue, path, messageFormat, valueType); super(msg); this.init(type, value, path, reason, schemaType); } else { @@ -31,6 +32,18 @@ class CastError extends MongooseError { } } + toJSON() { + return { + stringValue: this.stringValue, + valueType: this.valueType, + kind: this.kind, + value: this.value, + path: this.path, + reason: this.reason, + name: this.name, + message: this.message + }; + } /*! * ignore */ @@ -41,6 +54,7 @@ class CastError extends MongooseError { this.value = value; this.path = path; this.reason = reason; + this.valueType = getValueType(value); } /*! @@ -55,6 +69,7 @@ class CastError extends MongooseError { this.path = other.path; this.reason = other.reason; this.message = other.message; + this.valueType = other.valueType; } /*! @@ -63,7 +78,7 @@ class CastError extends MongooseError { setModel(model) { this.model = model; this.message = formatMessage(model, this.kind, this.stringValue, this.path, - this.messageFormat); + this.messageFormat, this.valueType); } } @@ -80,6 +95,21 @@ function getStringValue(value) { return stringValue; } +function getValueType(value) { + if (value == null) { + return '' + value; + } + + const t = typeof value; + if (t !== 'object') { + return t; + } + if (typeof value.constructor !== 'function') { + return t; + } + return value.constructor.name; +} + function getMessageFormat(schemaType) { const messageFormat = get(schemaType, 'options.cast', null); if (typeof messageFormat === 'string') { @@ -91,7 +121,7 @@ function getMessageFormat(schemaType) { * ignore */ -function formatMessage(model, kind, stringValue, path, messageFormat) { +function formatMessage(model, kind, stringValue, path, messageFormat, valueType) { if (messageFormat != null) { let ret = messageFormat. replace('{KIND}', kind). @@ -103,12 +133,12 @@ function formatMessage(model, kind, stringValue, path, messageFormat) { return ret; } else { + const valueTypeMsg = valueType ? ' (type ' + valueType + ')' : ''; let ret = 'Cast to ' + kind + ' failed for value ' + - stringValue + ' at path "' + path + '"'; + stringValue + valueTypeMsg + ' at path "' + path + '"'; if (model != null) { ret += ' for model "' + model.modelName + '"'; } - return ret; } } diff --git a/lib/error/validation.js b/lib/error/validation.js index ccae07adff1..783554aaa1c 100644 --- a/lib/error/validation.js +++ b/lib/error/validation.js @@ -5,6 +5,7 @@ 'use strict'; const MongooseError = require('./mongooseError'); +const getConstructorName = require('../helpers/getConstructorName'); const util = require('util'); class ValidationError extends MongooseError { @@ -17,7 +18,7 @@ class ValidationError extends MongooseError { */ constructor(instance) { let _message; - if (instance && instance.constructor.name === 'model') { + if (getConstructorName(instance) === 'model') { _message = instance.constructor.modelName + ' validation failed'; } else { _message = 'Validation failed'; @@ -67,13 +68,14 @@ if (util.inspect.custom) { /*! * Helper for JSON.stringify + * Ensure `name` and `message` show up in toJSON output re: gh-9847 */ Object.defineProperty(ValidationError.prototype, 'toJSON', { enumerable: false, writable: false, configurable: true, value: function() { - return Object.assign({}, this, { message: this.message }); + return Object.assign({}, this, { name: this.name, message: this.message }); } }); diff --git a/lib/helpers/aggregate/stringifyAccumulatorOptions.js b/lib/helpers/aggregate/stringifyFunctionOperators.js similarity index 57% rename from lib/helpers/aggregate/stringifyAccumulatorOptions.js rename to lib/helpers/aggregate/stringifyFunctionOperators.js index 7362514d8f5..124418efd68 100644 --- a/lib/helpers/aggregate/stringifyAccumulatorOptions.js +++ b/lib/helpers/aggregate/stringifyFunctionOperators.js @@ -1,6 +1,6 @@ 'use strict'; -module.exports = function stringifyAccumulatorOptions(pipeline) { +module.exports = function stringifyFunctionOperators(pipeline) { if (!Array.isArray(pipeline)) { return; } @@ -17,9 +17,21 @@ module.exports = function stringifyAccumulatorOptions(pipeline) { } } + const stageType = Object.keys(stage)[0]; + if (stageType && typeof stage[stageType] === 'object') { + const stageOptions = stage[stageType]; + for (const key of Object.keys(stageOptions)) { + if (stageOptions[key] != null && + stageOptions[key].$function != null && + typeof stageOptions[key].$function.body === 'function') { + stageOptions[key].$function.body = stageOptions[key].$function.body.toString(); + } + } + } + if (stage.$facet != null) { for (const key of Object.keys(stage.$facet)) { - stringifyAccumulatorOptions(stage.$facet[key]); + stringifyFunctionOperators(stage.$facet[key]); } } } diff --git a/lib/helpers/arrayDepth.js b/lib/helpers/arrayDepth.js index 2c6f2e58629..e55de7ffbea 100644 --- a/lib/helpers/arrayDepth.js +++ b/lib/helpers/arrayDepth.js @@ -9,6 +9,9 @@ function arrayDepth(arr) { if (arr.length === 0) { return { min: 1, max: 1, containsNonArrayItem: false }; } + if (arr.length === 1 && !Array.isArray(arr[0])) { + return { min: 1, max: 1, containsNonArrayItem: false }; + } const res = arrayDepth(arr[0]); diff --git a/lib/helpers/clone.js b/lib/helpers/clone.js index 5f0b2c92960..1056b02e2a1 100644 --- a/lib/helpers/clone.js +++ b/lib/helpers/clone.js @@ -111,7 +111,7 @@ function cloneObject(obj, options, isArrayChild) { const ret = {}; let hasKeys; - for (const k in obj) { + for (const k of Object.keys(obj)) { if (specialProperties.has(k)) { continue; } diff --git a/lib/helpers/cursor/eachAsync.js b/lib/helpers/cursor/eachAsync.js index 4afff032fe8..2ef0346e0d4 100644 --- a/lib/helpers/cursor/eachAsync.js +++ b/lib/helpers/cursor/eachAsync.js @@ -4,6 +4,7 @@ * Module dependencies. */ +const immediate = require('../immediate'); const promiseOrCallback = require('../promiseOrCallback'); /** @@ -22,9 +23,22 @@ const promiseOrCallback = require('../promiseOrCallback'); module.exports = function eachAsync(next, fn, options, callback) { const parallel = options.parallel || 1; + const batchSize = options.batchSize; const enqueue = asyncQueue(); return promiseOrCallback(callback, cb => { + if (batchSize != null) { + if (typeof batchSize !== 'number') { + throw new TypeError('batchSize must be a number'); + } + if (batchSize < 1) { + throw new TypeError('batchSize must be at least 1'); + } + if (batchSize !== Math.floor(batchSize)) { + throw new TypeError('batchSize must be a positive integer'); + } + } + iterate(cb); }); @@ -32,6 +46,7 @@ module.exports = function eachAsync(next, fn, options, callback) { let drained = false; let handleResultsInProgress = 0; let currentDocumentIndex = 0; + let documentsBatch = []; let error = null; for (let i = 0; i < parallel; ++i) { @@ -56,6 +71,8 @@ module.exports = function eachAsync(next, fn, options, callback) { drained = true; if (handleResultsInProgress <= 0) { finalCallback(null); + } else if (batchSize != null && documentsBatch.length) { + handleNextResult(documentsBatch, currentDocumentIndex++, handleNextResultCallBack); } return done(); } @@ -64,10 +81,27 @@ module.exports = function eachAsync(next, fn, options, callback) { // Kick off the subsequent `next()` before handling the result, but // make sure we know that we still have a result to handle re: #8422 - process.nextTick(() => done()); + immediate(() => done()); + + if (batchSize != null) { + documentsBatch.push(doc); + } - handleNextResult(doc, currentDocumentIndex++, function(err) { - --handleResultsInProgress; + // If the current documents size is less than the provided patch size don't process the documents yet + if (batchSize != null && documentsBatch.length !== batchSize) { + setTimeout(() => enqueue(fetch), 0); + return; + } + + const docsToProcess = batchSize != null ? documentsBatch : doc; + + function handleNextResultCallBack(err) { + if (batchSize != null) { + handleResultsInProgress -= documentsBatch.length; + documentsBatch = []; + } else { + --handleResultsInProgress; + } if (err != null) { error = err; return finalCallback(err); @@ -77,7 +111,9 @@ module.exports = function eachAsync(next, fn, options, callback) { } setTimeout(() => enqueue(fetch), 0); - }); + } + + handleNextResult(docsToProcess, currentDocumentIndex++, handleNextResultCallBack); }); } } diff --git a/lib/helpers/discriminator/areDiscriminatorValuesEqual.js b/lib/helpers/discriminator/areDiscriminatorValuesEqual.js new file mode 100644 index 00000000000..87b2408e6db --- /dev/null +++ b/lib/helpers/discriminator/areDiscriminatorValuesEqual.js @@ -0,0 +1,16 @@ +'use strict'; + +const ObjectId = require('../../types/objectid'); + +module.exports = function areDiscriminatorValuesEqual(a, b) { + if (typeof a === 'string' && typeof b === 'string') { + return a === b; + } + if (typeof a === 'number' && typeof b === 'number') { + return a === b; + } + if (a instanceof ObjectId && b instanceof ObjectId) { + return a.toString() === b.toString(); + } + return false; +}; \ No newline at end of file diff --git a/lib/helpers/discriminator/getConstructor.js b/lib/helpers/discriminator/getConstructor.js index 04a3dedd83a..728da3b25ff 100644 --- a/lib/helpers/discriminator/getConstructor.js +++ b/lib/helpers/discriminator/getConstructor.js @@ -14,7 +14,7 @@ module.exports = function getConstructor(Constructor, value) { if (Constructor.discriminators[value[discriminatorKey]]) { Constructor = Constructor.discriminators[value[discriminatorKey]]; } else { - const constructorByValue = getDiscriminatorByValue(Constructor, value[discriminatorKey]); + const constructorByValue = getDiscriminatorByValue(Constructor.discriminators, value[discriminatorKey]); if (constructorByValue) { Constructor = constructorByValue; } diff --git a/lib/helpers/discriminator/getDiscriminatorByValue.js b/lib/helpers/discriminator/getDiscriminatorByValue.js index a107a910492..099aaefddfd 100644 --- a/lib/helpers/discriminator/getDiscriminatorByValue.js +++ b/lib/helpers/discriminator/getDiscriminatorByValue.js @@ -1,5 +1,7 @@ 'use strict'; +const areDiscriminatorValuesEqual = require('./areDiscriminatorValuesEqual'); + /*! * returns discriminator by discriminatorMapping.value * @@ -7,21 +9,19 @@ * @param {string} value */ -module.exports = function getDiscriminatorByValue(model, value) { - let discriminator = null; - if (!model.discriminators) { - return discriminator; +module.exports = function getDiscriminatorByValue(discriminators, value) { + if (discriminators == null) { + return null; } - for (const name in model.discriminators) { - const it = model.discriminators[name]; + for (const name of Object.keys(discriminators)) { + const it = discriminators[name]; if ( it.schema && it.schema.discriminatorMapping && - it.schema.discriminatorMapping.value == value + areDiscriminatorValuesEqual(it.schema.discriminatorMapping.value, value) ) { - discriminator = it; - break; + return it; } } - return discriminator; + return null; }; \ No newline at end of file diff --git a/lib/helpers/discriminator/getSchemaDiscriminatorByValue.js b/lib/helpers/discriminator/getSchemaDiscriminatorByValue.js index f3e71a093a9..b29fb6521e1 100644 --- a/lib/helpers/discriminator/getSchemaDiscriminatorByValue.js +++ b/lib/helpers/discriminator/getSchemaDiscriminatorByValue.js @@ -1,5 +1,7 @@ 'use strict'; +const areDiscriminatorValuesEqual = require('./areDiscriminatorValuesEqual'); + /*! * returns discriminator by discriminatorMapping.value * @@ -16,7 +18,7 @@ module.exports = function getSchemaDiscriminatorByValue(schema, value) { if (discriminatorSchema.discriminatorMapping == null) { continue; } - if (discriminatorSchema.discriminatorMapping.value === value) { + if (areDiscriminatorValuesEqual(discriminatorSchema.discriminatorMapping.value, value)) { return discriminatorSchema; } } diff --git a/lib/helpers/document/cleanModifiedSubpaths.js b/lib/helpers/document/cleanModifiedSubpaths.js index 252d34824df..98de475364f 100644 --- a/lib/helpers/document/cleanModifiedSubpaths.js +++ b/lib/helpers/document/cleanModifiedSubpaths.js @@ -14,7 +14,7 @@ module.exports = function cleanModifiedSubpaths(doc, path, options) { } for (const modifiedPath of Object.keys(doc.$__.activePaths.states.modify)) { if (skipDocArrays) { - const schemaType = doc.schema.path(modifiedPath); + const schemaType = doc.$__schema.path(modifiedPath); if (schemaType && schemaType.$isMongooseDocumentArray) { continue; } diff --git a/lib/helpers/document/compile.js b/lib/helpers/document/compile.js index def45e67b23..caa9c203b33 100644 --- a/lib/helpers/document/compile.js +++ b/lib/helpers/document/compile.js @@ -74,6 +74,13 @@ function defineKey(prop, subprops, prototype, prefix, keys, options) { value: prototype.schema }); + Object.defineProperty(nested, '$__schema', { + enumerable: false, + configurable: true, + writable: false, + value: prototype.schema + }); + Object.defineProperty(nested, documentSchemaSymbol, { enumerable: false, configurable: true, @@ -181,13 +188,7 @@ function getOwnPropertyDescriptors(object) { const result = {}; Object.getOwnPropertyNames(object).forEach(function(key) { - result[key] = Object.getOwnPropertyDescriptor(object, key); - // Assume these are schema paths, ignore them re: #5470 - if (result[key].get) { - delete result[key]; - return; - } - result[key].enumerable = [ + const skip = [ 'isNew', '$__', 'errors', @@ -198,6 +199,12 @@ function getOwnPropertyDescriptors(object) { '__index', '$isDocumentArrayElement' ].indexOf(key) === -1; + if (skip) { + return; + } + + result[key] = Object.getOwnPropertyDescriptor(object, key); + result[key].enumerable = false; }); return result; diff --git a/lib/helpers/get.js b/lib/helpers/get.js index dcb3881f713..dbab30601dc 100644 --- a/lib/helpers/get.js +++ b/lib/helpers/get.js @@ -6,7 +6,30 @@ */ module.exports = function get(obj, path, def) { - const parts = path.split('.'); + let parts; + let isPathArray = false; + if (typeof path === 'string') { + if (path.indexOf('.') === -1) { + const _v = getProperty(obj, path); + if (_v == null) { + return def; + } + return _v; + } + + parts = path.split('.'); + } else { + isPathArray = true; + parts = path; + + if (parts.length === 1) { + const _v = getProperty(obj, parts[0]); + if (_v == null) { + return def; + } + return _v; + } + } let rest = path; let cur = obj; for (const part of parts) { @@ -16,13 +39,15 @@ module.exports = function get(obj, path, def) { // `lib/cast.js` depends on being able to get dotted paths in updates, // like `{ $set: { 'a.b': 42 } }` - if (cur[rest] != null) { + if (!isPathArray && cur[rest] != null) { return cur[rest]; } cur = getProperty(cur, part); - rest = rest.substr(part.length + 1); + if (!isPathArray) { + rest = rest.substr(part.length + 1); + } } return cur == null ? def : cur; diff --git a/lib/helpers/getConstructorName.js b/lib/helpers/getConstructorName.js new file mode 100644 index 00000000000..a4e19249318 --- /dev/null +++ b/lib/helpers/getConstructorName.js @@ -0,0 +1,15 @@ +'use strict'; + +/*! + * If `val` is an object, returns constructor name, if possible. Otherwise returns undefined. + */ + +module.exports = function getConstructorName(val) { + if (val == null) { + return void 0; + } + if (typeof val.constructor !== 'function') { + return void 0; + } + return val.constructor.name; +}; \ No newline at end of file diff --git a/lib/helpers/immediate.js b/lib/helpers/immediate.js index ddb70607a1f..0a1c26c32f6 100644 --- a/lib/helpers/immediate.js +++ b/lib/helpers/immediate.js @@ -7,6 +7,8 @@ 'use strict'; +const nextTick = process.nextTick.bind(process); + module.exports = function immediate(cb) { - return process.nextTick(cb); + return nextTick(cb); }; diff --git a/lib/helpers/model/castBulkWrite.js b/lib/helpers/model/castBulkWrite.js index 6e7a8300754..54559b5686a 100644 --- a/lib/helpers/model/castBulkWrite.js +++ b/lib/helpers/model/castBulkWrite.js @@ -20,7 +20,7 @@ module.exports = function castBulkWrite(originalModel, op, options) { const model = decideModelByObject(originalModel, op['insertOne']['document']); const doc = new model(op['insertOne']['document']); - if (model.schema.options.timestamps != null) { + if (model.schema.options.timestamps) { doc.initializeTimestamps(); } if (options.session != null) { @@ -147,7 +147,7 @@ module.exports = function castBulkWrite(originalModel, op, options) { // set `skipId`, otherwise we get "_id field cannot be changed" const doc = new model(op['replaceOne']['replacement'], strict, true); - if (model.schema.options.timestamps != null) { + if (model.schema.options.timestamps) { doc.initializeTimestamps(); } if (options.session != null) { @@ -218,7 +218,7 @@ function _addDiscriminatorToObject(schema, obj) { function decideModelByObject(model, object) { const discriminatorKey = model.schema.options.discriminatorKey; if (object != null && object.hasOwnProperty(discriminatorKey)) { - model = getDiscriminatorByValue(model, object[discriminatorKey]) || model; + model = getDiscriminatorByValue(model.discriminators, object[discriminatorKey]) || model; } return model; -} \ No newline at end of file +} diff --git a/lib/helpers/model/discriminator.js b/lib/helpers/model/discriminator.js index 2ad4c5e3754..24a569ed298 100644 --- a/lib/helpers/model/discriminator.js +++ b/lib/helpers/model/discriminator.js @@ -63,7 +63,7 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu } let value = name; - if (typeof tiedValue == 'string' && tiedValue.length) { + if ((typeof tiedValue === 'string' && tiedValue.length) || tiedValue != null) { value = tiedValue; } @@ -126,17 +126,17 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu default: value, select: true, set: function(newName) { - if (newName === value) { + if (newName === value || (Array.isArray(value) && utils.deepEqual(newName, value))) { return value; } throw new Error('Can\'t set discriminator key "' + key + '"'); }, $skipDiscriminatorCheck: true }; - obj[key][schema.options.typeKey] = existingPath ? - existingPath.instance : - String; + obj[key][schema.options.typeKey] = existingPath ? existingPath.options[schema.options.typeKey] : String; schema.add(obj); + + schema.discriminatorMapping = { key: key, value: value, isRoot: false }; if (baseSchema.options.collection) { diff --git a/lib/helpers/path/parentPaths.js b/lib/helpers/path/parentPaths.js new file mode 100644 index 00000000000..77d00ae89bb --- /dev/null +++ b/lib/helpers/path/parentPaths.js @@ -0,0 +1,13 @@ +'use strict'; + +module.exports = function parentPaths(path) { + const pieces = path.split('.'); + let cur = ''; + const ret = []; + for (let i = 0; i < pieces.length; ++i) { + cur += (cur.length > 0 ? '.' : '') + pieces[i]; + ret.push(cur); + } + + return ret; +}; \ No newline at end of file diff --git a/lib/helpers/path/setDottedPath.js b/lib/helpers/path/setDottedPath.js new file mode 100644 index 00000000000..fe6b9b5afea --- /dev/null +++ b/lib/helpers/path/setDottedPath.js @@ -0,0 +1,16 @@ +'use strict'; + +module.exports = function setDottedPath(obj, path, val) { + const parts = path.split('.'); + let cur = obj; + for (const part of parts.slice(0, -1)) { + if (cur[part] == null) { + cur[part] = {}; + } + + cur = cur[part]; + } + + const last = parts[parts.length - 1]; + cur[last] = val; +}; \ No newline at end of file diff --git a/lib/helpers/populate/assignRawDocsToIdStructure.js b/lib/helpers/populate/assignRawDocsToIdStructure.js index 843c148373e..5682143a84e 100644 --- a/lib/helpers/populate/assignRawDocsToIdStructure.js +++ b/lib/helpers/populate/assignRawDocsToIdStructure.js @@ -69,7 +69,12 @@ function assignRawDocsToIdStructure(rawIds, resultDocs, resultOrder, options, re if (recursed) { if (doc) { if (sorting) { - newOrder[resultOrder[sid]] = doc; + const _resultOrder = resultOrder[sid]; + if (Array.isArray(_resultOrder) && Array.isArray(doc) && _resultOrder.length === doc.length) { + newOrder.push(doc); + } else { + newOrder[_resultOrder] = doc; + } } else { newOrder.push(doc); } diff --git a/lib/helpers/populate/assignVals.js b/lib/helpers/populate/assignVals.js index 9fd51d80967..81ccf61bc88 100644 --- a/lib/helpers/populate/assignVals.js +++ b/lib/helpers/populate/assignVals.js @@ -12,7 +12,7 @@ const utils = require('../../utils'); module.exports = function assignVals(o) { // Options that aren't explicitly listed in `populateOptions` - const userOptions = get(o, 'allOptions.options.options'); + const userOptions = Object.assign({}, get(o, 'allOptions.options.options'), get(o, 'allOptions.options')); // `o.options` contains options explicitly listed in `populateOptions`, like // `match` and `limit`. const populateOptions = Object.assign({}, o.options, userOptions, { @@ -25,6 +25,7 @@ module.exports = function assignVals(o) { // replace the original ids in our intermediate _ids structure // with the documents found by query + o.allIds = [].concat(o.allIds); assignRawDocsToIdStructure(o.rawIds, o.rawDocs, o.rawOrder, populateOptions); // now update the original documents being populated using the @@ -33,6 +34,7 @@ module.exports = function assignVals(o) { const rawIds = o.rawIds; const options = o.options; const count = o.count && o.isVirtual; + let i; function setValue(val) { if (count) { @@ -41,6 +43,11 @@ module.exports = function assignVals(o) { if (val instanceof SkipPopulateValue) { return val.val; } + if (val === void 0) { + return val; + } + + const _allIds = o.allIds[i]; if (o.justOne === true && Array.isArray(val)) { // Might be an embedded discriminator (re: gh-9244) with multiple models, so make sure to pick the right @@ -61,16 +68,17 @@ module.exports = function assignVals(o) { val[i] = ret[i]; } - return valueFilter(val[0], options, populateOptions, populatedModel); + return valueFilter(val[0], options, populateOptions, _allIds); } else if (o.justOne === false && !Array.isArray(val)) { - return valueFilter([val], options, populateOptions, populatedModel); + return valueFilter([val], options, populateOptions, _allIds); } - return valueFilter(val, options, populateOptions, populatedModel); + return valueFilter(val, options, populateOptions, _allIds); } - for (let i = 0; i < docs.length; ++i) { - const existingVal = mpath.get(o.path, docs[i], lookupLocalFields); - if (existingVal == null && !getVirtual(o.originalModel.schema, o.path)) { + for (i = 0; i < docs.length; ++i) { + const _path = o.path.endsWith('.$*') ? o.path.slice(0, -3) : o.path; + const existingVal = mpath.get(_path, docs[i], lookupLocalFields); + if (existingVal == null && !getVirtual(o.originalModel.schema, _path)) { continue; } @@ -79,8 +87,8 @@ module.exports = function assignVals(o) { valueToSet = numDocs(rawIds[i]); } else if (Array.isArray(o.match)) { valueToSet = Array.isArray(rawIds[i]) ? - sift(o.match[i], rawIds[i]) : - sift(o.match[i], [rawIds[i]])[0]; + rawIds[i].filter(sift(o.match[i])) : + [rawIds[i]].filter(sift(o.match[i]))[0]; } else { valueToSet = rawIds[i]; } @@ -94,7 +102,7 @@ module.exports = function assignVals(o) { utils.isPOJO(existingVal); // If we pass the first check, also make sure the local field's schematype // is map (re: gh-6460) - isMap = isMap && get(originalSchema._getSchema(o.path), '$isSchemaMap'); + isMap = isMap && get(originalSchema._getSchema(_path), '$isSchemaMap'); if (!o.isVirtual && isMap) { const _keys = existingVal instanceof Map ? Array.from(existingVal.keys()) : @@ -116,14 +124,14 @@ module.exports = function assignVals(o) { } if (o.isVirtual && isDoc) { - docs[i].populated(o.path, o.justOne ? originalIds[0] : originalIds, o.allOptions); + docs[i].populated(_path, o.justOne ? originalIds[0] : originalIds, o.allOptions); // If virtual populate and doc is already init-ed, need to walk through // the actual doc to set rather than setting `_doc` directly - mpath.set(o.path, valueToSet, docs[i], setValue); + mpath.set(_path, valueToSet, docs[i], setValue); continue; } - const parts = o.path.split('.'); + const parts = _path.split('.'); let cur = docs[i]; const curPath = parts[0]; for (let j = 0; j < parts.length - 1; ++j) { @@ -143,7 +151,7 @@ module.exports = function assignVals(o) { // See gh-8342, gh-8455 const schematype = originalSchema._getSchema(curPath); if (valueToSet == null && schematype != null && schematype.$isMongooseArray) { - return; + break; } cur[parts[j]] = {}; } @@ -151,17 +159,17 @@ module.exports = function assignVals(o) { // If the property in MongoDB is a primitive, we won't be able to populate // the nested path, so skip it. See gh-7545 if (typeof cur !== 'object') { - return; + break; } } if (docs[i].$__) { - docs[i].populated(o.path, o.allIds[i], o.allOptions); + docs[i].populated(_path, o.allIds[i], o.allOptions); } // If lean, need to check that each individual virtual respects // `justOne`, because you may have a populated virtual with `justOne` // underneath an array. See gh-6867 - mpath.set(o.path, valueToSet, docs[i], lookupLocalFields, setValue, false); + mpath.set(_path, valueToSet, docs[i], lookupLocalFields, setValue, false); } }; @@ -195,15 +203,20 @@ function numDocs(v) { * that population mapping can occur. */ -function valueFilter(val, assignmentOpts, populateOptions) { +function valueFilter(val, assignmentOpts, populateOptions, allIds) { + const userSpecifiedTransform = typeof populateOptions.transform === 'function'; + const transform = userSpecifiedTransform ? populateOptions.transform : noop; if (Array.isArray(val)) { // find logic const ret = []; const numValues = val.length; for (let i = 0; i < numValues; ++i) { - const subdoc = val[i]; - if (!isPopulatedObject(subdoc) && (!populateOptions.retainNullValues || subdoc != null)) { + let subdoc = val[i]; + const _allIds = Array.isArray(allIds) ? allIds[i] : allIds; + if (!isPopulatedObject(subdoc) && (!populateOptions.retainNullValues || subdoc != null) && !userSpecifiedTransform) { continue; + } else if (userSpecifiedTransform) { + subdoc = transform(isPopulatedObject(subdoc) ? subdoc : null, _allIds); } maybeRemoveId(subdoc, assignmentOpts); ret.push(subdoc); @@ -225,22 +238,19 @@ function valueFilter(val, assignmentOpts, populateOptions) { } // findOne - if (isPopulatedObject(val)) { + if (isPopulatedObject(val) || utils.isPOJO(val)) { maybeRemoveId(val, assignmentOpts); - return val; + return transform(val, allIds); } - if (val instanceof Map) { return val; } - if (populateOptions.justOne === true) { - return (val == null ? val : null); - } if (populateOptions.justOne === false) { return []; } - return val; + + return val == null ? transform(val, allIds) : transform(null, allIds); } /*! @@ -271,4 +281,8 @@ function isPopulatedObject(obj) { obj.$isMongooseMap || obj.$__ != null || leanPopulateMap.has(obj); +} + +function noop(v) { + return v; } \ No newline at end of file diff --git a/lib/helpers/populate/createPopulateQueryFilter.js b/lib/helpers/populate/createPopulateQueryFilter.js new file mode 100644 index 00000000000..db7f9c69da7 --- /dev/null +++ b/lib/helpers/populate/createPopulateQueryFilter.js @@ -0,0 +1,79 @@ +'use strict'; + +const SkipPopulateValue = require('./SkipPopulateValue'); +const parentPaths = require('../path/parentPaths'); + +module.exports = function createPopulateQueryFilter(ids, _match, _foreignField, model, skipInvalidIds) { + const match = _formatMatch(_match); + + if (_foreignField.size === 1) { + const foreignField = Array.from(_foreignField)[0]; + const foreignSchemaType = model.schema.path(foreignField); + if (foreignField !== '_id' || !match['_id']) { + ids = _filterInvalidIds(ids, foreignSchemaType, skipInvalidIds); + match[foreignField] = { $in: ids }; + } + + const _parentPaths = parentPaths(foreignField); + for (let i = 0; i < _parentPaths.length - 1; ++i) { + const cur = _parentPaths[i]; + if (match[cur] != null && match[cur].$elemMatch != null) { + match[cur].$elemMatch[foreignField.slice(cur.length + 1)] = { $in: ids }; + delete match[foreignField]; + break; + } + } + } else { + const $or = []; + if (Array.isArray(match.$or)) { + match.$and = [{ $or: match.$or }, { $or: $or }]; + delete match.$or; + } else { + match.$or = $or; + } + for (const foreignField of _foreignField) { + if (foreignField !== '_id' || !match['_id']) { + const foreignSchemaType = model.schema.path(foreignField); + ids = _filterInvalidIds(ids, foreignSchemaType, skipInvalidIds); + $or.push({ [foreignField]: { $in: ids } }); + } + } + } + + return match; +}; + +/*! + * Optionally filter out invalid ids that don't conform to foreign field's schema + * to avoid cast errors (gh-7706) + */ + +function _filterInvalidIds(ids, foreignSchemaType, skipInvalidIds) { + ids = ids.filter(v => !(v instanceof SkipPopulateValue)); + if (!skipInvalidIds) { + return ids; + } + return ids.filter(id => { + try { + foreignSchemaType.cast(id); + return true; + } catch (err) { + return false; + } + }); +} + +/*! + * Format `mod.match` given that it may be an array that we need to $or if + * the client has multiple docs with match functions + */ + +function _formatMatch(match) { + if (Array.isArray(match)) { + if (match.length > 1) { + return { $or: [].concat(match.map(m => Object.assign({}, m))) }; + } + return Object.assign({}, match[0]); + } + return Object.assign({}, match); +} \ No newline at end of file diff --git a/lib/helpers/populate/getModelsMapForPopulate.js b/lib/helpers/populate/getModelsMapForPopulate.js index a5bd38821be..5cc6c3f6f88 100644 --- a/lib/helpers/populate/getModelsMapForPopulate.js +++ b/lib/helpers/populate/getModelsMapForPopulate.js @@ -5,6 +5,7 @@ const SkipPopulateValue = require('./SkipPopulateValue'); const get = require('../get'); const getDiscriminatorByValue = require('../discriminator/getDiscriminatorByValue'); const isPathExcluded = require('../projection/isPathExcluded'); +const getConstructorName = require('../getConstructorName'); const getSchemaTypes = require('./getSchemaTypes'); const getVirtual = require('./getVirtual'); const lookupLocalFields = require('./lookupLocalFields'); @@ -30,7 +31,6 @@ module.exports = function getModelsMapForPopulate(model, docs, options) { let currentOptions; let modelNames; let modelName; - let modelForFindSchema; const originalModel = options.model; let isVirtual = false; @@ -43,7 +43,6 @@ module.exports = function getModelsMapForPopulate(model, docs, options) { for (i = 0; i < len; i++) { doc = docs[i]; let justOne = null; - schema = getSchemaTypes(modelSchema, doc, options.path); // Special case: populating a path that's a DocumentArray unless // there's an explicit `ref` or `refPath` re: gh-8946 @@ -55,7 +54,7 @@ module.exports = function getModelsMapForPopulate(model, docs, options) { } // Populating a nested path should always be a no-op re: #9073. // People shouldn't do this, but apparently they do. - if (modelSchema.nested[options.path]) { + if (options._localModel != null && options._localModel.schema.nested[options.path]) { continue; } const isUnderneathDocArray = schema && schema.$isUnderneathDocArray; @@ -174,9 +173,11 @@ module.exports = function getModelsMapForPopulate(model, docs, options) { } } else if (schema && !schema[schemaMixedSymbol]) { // Skip Mixed types because we explicitly don't do casting on those. - justOne = Array.isArray(schema) ? - schema.every(schema => !schema.$isMongooseArray) : - !schema.$isMongooseArray; + if (options.path.endsWith('.' + schema.path)) { + justOne = Array.isArray(schema) ? + schema.every(schema => !schema.$isMongooseArray) : + !schema.$isMongooseArray; + } } if (!modelNames) { @@ -358,7 +359,6 @@ module.exports = function getModelsMapForPopulate(model, docs, options) { } } } - return map; function _getModelNames(doc, schema) { @@ -405,28 +405,26 @@ module.exports = function getModelsMapForPopulate(model, docs, options) { } else { let modelForCurrentDoc = model; let schemaForCurrentDoc; - - if (!schema && discriminatorKey) { - modelForFindSchema = utils.getValue(discriminatorKey, doc); - if (modelForFindSchema) { - // `modelForFindSchema` is the discriminator value, so we might need - // find the discriminated model name - const discriminatorModel = getDiscriminatorByValue(model, modelForFindSchema); - if (discriminatorModel != null) { - modelForCurrentDoc = discriminatorModel; - } else { - try { - modelForCurrentDoc = model.db.model(modelForFindSchema); - } catch (error) { - return error; - } + let discriminatorValue; + + if (!schema && discriminatorKey && (discriminatorValue = utils.getValue(discriminatorKey, doc))) { + // `modelNameForFind` is the discriminator value, so we might need + // find the discriminated model name + const discriminatorModel = getDiscriminatorByValue(model.discriminators, discriminatorValue) || model; + if (discriminatorModel != null) { + modelForCurrentDoc = discriminatorModel; + } else { + try { + modelForCurrentDoc = model.db.model(discriminatorValue); + } catch (error) { + return error; } + } - schemaForCurrentDoc = modelForCurrentDoc.schema._getSchema(options.path); + schemaForCurrentDoc = modelForCurrentDoc.schema._getSchema(options.path); - if (schemaForCurrentDoc && schemaForCurrentDoc.caster) { - schemaForCurrentDoc = schemaForCurrentDoc.caster; - } + if (schemaForCurrentDoc && schemaForCurrentDoc.caster) { + schemaForCurrentDoc = schemaForCurrentDoc.caster; } } else { schemaForCurrentDoc = schema; @@ -501,7 +499,12 @@ function handleRefFunction(ref, doc) { */ function convertTo_id(val, schema) { - if (val != null && val.$__ != null) return val._id; + if (val != null && val.$__ != null) { + return val._id; + } + if (val != null && val._id != null && (schema == null || !schema.$isSchemaMap)) { + return val._id; + } if (Array.isArray(val)) { for (let i = 0; i < val.length; ++i) { @@ -510,7 +513,7 @@ function convertTo_id(val, schema) { } } if (val.isMongooseArray && val.$schema()) { - return val.$schema().cast(val, val.$parent()); + return val.$schema()._castForPopulate(val, val.$parent()); } return [].concat(val); @@ -518,8 +521,7 @@ function convertTo_id(val, schema) { // `populate('map')` may be an object if populating on a doc that hasn't // been hydrated yet - if (val != null && - val.constructor.name === 'Object' && + if (getConstructorName(val) === 'Object' && // The intent here is we should only flatten the object if we expect // to get a Map in the end. Avoid doing this for mixed types. (schema == null || schema[schemaMixedSymbol] == null)) { diff --git a/lib/helpers/populate/getSchemaTypes.js b/lib/helpers/populate/getSchemaTypes.js index 15660df1f78..4c33ebb3486 100644 --- a/lib/helpers/populate/getSchemaTypes.js +++ b/lib/helpers/populate/getSchemaTypes.js @@ -20,7 +20,6 @@ const populateModelSymbol = require('../symbols').populateModelSymbol; module.exports = function getSchemaTypes(schema, doc, path) { const pathschema = schema.path(path); const topLevelDoc = doc; - if (pathschema) { return pathschema; } @@ -33,7 +32,6 @@ module.exports = function getSchemaTypes(schema, doc, path) { while (p--) { trypath = parts.slice(0, p).join('.'); foundschema = schema.path(trypath); - if (foundschema == null) { continue; } @@ -52,7 +50,8 @@ module.exports = function getSchemaTypes(schema, doc, path) { const keys = subdoc ? mpath.get(discriminatorKeyPath, subdoc) || [] : []; schemas = Object.keys(discriminators). reduce(function(cur, discriminator) { - if (doc == null || keys.indexOf(discriminator) !== -1) { + const tiedValue = discriminators[discriminator].discriminatorMapping.value; + if (doc == null || keys.indexOf(discriminator) !== -1 || keys.indexOf(tiedValue) !== -1) { cur.push(discriminators[discriminator]); } return cur; @@ -117,24 +116,40 @@ module.exports = function getSchemaTypes(schema, doc, path) { ret.$isUnderneathDocArray = ret.$isUnderneathDocArray || !foundschema.schema.$isSingleNested; } - return ret; } } else if (p !== parts.length && foundschema.$isMongooseArray && foundschema.casterConstructor.$isMongooseArray) { // Nested arrays. Drill down to the bottom of the nested array. - // Ignore discriminators. let type = foundschema; while (type.$isMongooseArray && !type.$isMongooseDocumentArray) { type = type.casterConstructor; } - return search( + + const ret = search( parts.slice(p), type.schema, null, nestedPath.concat(parts.slice(0, p)) ); + if (ret != null) { + return ret; + } + + if (type.schema.discriminators) { + const discriminatorPaths = []; + for (const discriminatorName of Object.keys(type.schema.discriminators)) { + const _schema = type.schema.discriminators[discriminatorName] || type.schema; + const ret = search(parts.slice(p), _schema, null, nestedPath.concat(parts.slice(0, p))); + if (ret != null) { + discriminatorPaths.push(ret); + } + } + if (discriminatorPaths.length > 0) { + return discriminatorPaths; + } + } } } @@ -153,7 +168,6 @@ module.exports = function getSchemaTypes(schema, doc, path) { ret.$isUnderneathDocArray = ret.$isUnderneathDocArray || !model.schema.$isSingleNested; } - return ret; } } @@ -173,19 +187,16 @@ module.exports = function getSchemaTypes(schema, doc, path) { nestedPath.concat(parts.slice(0, p)) ); - if (ret) { + if (ret != null) { ret.$isUnderneathDocArray = ret.$isUnderneathDocArray || !schema.$isSingleNested; + return ret; } - - return ret; } } - return foundschema; } } - // look for arrays const parts = path.split('.'); for (let i = 0; i < parts.length; ++i) { diff --git a/lib/helpers/populate/lookupLocalFields.js b/lib/helpers/populate/lookupLocalFields.js index 08ed7632015..ea641bf8453 100644 --- a/lib/helpers/populate/lookupLocalFields.js +++ b/lib/helpers/populate/lookupLocalFields.js @@ -10,6 +10,12 @@ module.exports = function lookupLocalFields(cur, path, val) { } if (arguments.length >= 3) { + if (typeof cur !== 'object') { + return void 0; + } + if (val === void 0) { + return void 0; + } cur[path] = val; return val; } @@ -21,6 +27,5 @@ module.exports = function lookupLocalFields(cur, path, val) { Array.from(cur.values()) : Object.keys(cur).map(key => cur[key]); } - return cur[path]; }; \ No newline at end of file diff --git a/lib/helpers/printJestWarning.js b/lib/helpers/printJestWarning.js index eb3a8eb7b1d..9338d42a868 100644 --- a/lib/helpers/printJestWarning.js +++ b/lib/helpers/printJestWarning.js @@ -5,4 +5,11 @@ if (typeof jest !== 'undefined' && typeof window !== 'undefined') { 'with Jest\'s default jsdom test environment. Please make sure you read ' + 'Mongoose\'s docs on configuring Jest to test Node.js apps: ' + 'http://mongoosejs.com/docs/jest.html'); +} + +if (typeof jest !== 'undefined' && process.nextTick.toString().indexOf('nextTick') === -1) { + console.warn('Mongoose: looks like you\'re trying to test a Mongoose app ' + + 'with Jest\'s mock timers enabled. Please make sure you read ' + + 'Mongoose\'s docs on configuring Jest to test Node.js apps: ' + + 'http://mongoosejs.com/docs/jest.html'); } \ No newline at end of file diff --git a/lib/helpers/projection/isExclusive.js b/lib/helpers/projection/isExclusive.js index 8c64bc50fe2..cbf1b78787f 100644 --- a/lib/helpers/projection/isExclusive.js +++ b/lib/helpers/projection/isExclusive.js @@ -7,6 +7,10 @@ const isDefiningProjection = require('./isDefiningProjection'); */ module.exports = function isExclusive(projection) { + if (projection == null) { + return null; + } + const keys = Object.keys(projection); let ki = keys.length; let exclude = null; diff --git a/lib/helpers/promiseOrCallback.js b/lib/helpers/promiseOrCallback.js index db16d01f27c..69ba38530c0 100644 --- a/lib/helpers/promiseOrCallback.js +++ b/lib/helpers/promiseOrCallback.js @@ -1,21 +1,22 @@ 'use strict'; const PromiseProvider = require('../promise_provider'); +const immediate = require('./immediate'); -const emittedSymbol = Symbol.for('mongoose:emitted'); +const emittedSymbol = Symbol('mongoose:emitted'); module.exports = function promiseOrCallback(callback, fn, ee, Promise) { if (typeof callback === 'function') { return fn(function(error) { if (error != null) { - if (ee != null && ee.listeners('error').length > 0 && !error[emittedSymbol]) { + if (ee != null && ee.listeners != null && ee.listeners('error').length > 0 && !error[emittedSymbol]) { error[emittedSymbol] = true; ee.emit('error', error); } try { callback(error); } catch (error) { - return process.nextTick(() => { + return immediate(() => { throw error; }); } @@ -30,7 +31,7 @@ module.exports = function promiseOrCallback(callback, fn, ee, Promise) { return new Promise((resolve, reject) => { fn(function(error, res) { if (error != null) { - if (ee != null && ee.listeners('error').length > 0 && !error[emittedSymbol]) { + if (ee != null && ee.listeners != null && ee.listeners('error').length > 0 && !error[emittedSymbol]) { error[emittedSymbol] = true; ee.emit('error', error); } @@ -42,4 +43,4 @@ module.exports = function promiseOrCallback(callback, fn, ee, Promise) { resolve(res); }); }); -}; \ No newline at end of file +}; diff --git a/lib/helpers/query/castFilterPath.js b/lib/helpers/query/castFilterPath.js index 74ff1c2aaf2..42b8460ceda 100644 --- a/lib/helpers/query/castFilterPath.js +++ b/lib/helpers/query/castFilterPath.js @@ -25,7 +25,7 @@ module.exports = function castFilterPath(query, schematype, val) { if (nested && schematype && !schematype.caster) { const _keys = Object.keys(nested); if (_keys.length && isOperator(_keys[0])) { - for (const key in nested) { + for (const key of Object.keys(nested)) { nested[key] = schematype.castForQueryWrapper({ $conditional: key, val: nested[key], diff --git a/lib/helpers/query/castUpdate.js b/lib/helpers/query/castUpdate.js index bf60e5d11b7..ca9ad4f04a5 100644 --- a/lib/helpers/query/castUpdate.js +++ b/lib/helpers/query/castUpdate.js @@ -6,10 +6,12 @@ const StrictModeError = require('../../error/strict'); const ValidationError = require('../../error/validation'); const castNumber = require('../../cast/number'); const cast = require('../../cast'); +const getConstructorName = require('../getConstructorName'); const getEmbeddedDiscriminatorPath = require('./getEmbeddedDiscriminatorPath'); const handleImmutable = require('./handleImmutable'); const moveImmutableProperties = require('../update/moveImmutableProperties'); const schemaMixedSymbol = require('../../schema/symbols').schemaMixedSymbol; +const setDottedPath = require('../path/setDottedPath'); const utils = require('../../utils'); /*! @@ -199,10 +201,17 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) { } } - if (val && val.constructor.name === 'Object') { + if (getConstructorName(val) === 'Object') { // watch for embedded doc schemas schematype = schema._getSchema(prefix + key); + if (schematype == null) { + const _res = getEmbeddedDiscriminatorPath(schema, obj, filter, prefix + key, options); + if (_res.schematype != null) { + schematype = _res.schematype; + } + } + if (op !== '$setOnInsert' && handleImmutable(schematype, strict, obj, key, prefix + key, context)) { continue; @@ -317,7 +326,7 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) { // If no schema type, check for embedded discriminators because the // filter or update may imply an embedded discriminator type. See #8378 if (schematype == null) { - const _res = getEmbeddedDiscriminatorPath(schema, obj, filter, checkPath); + const _res = getEmbeddedDiscriminatorPath(schema, obj, filter, checkPath, options); if (_res.schematype != null) { schematype = _res.schematype; pathDetails = _res.type; @@ -353,7 +362,15 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) { } try { - obj[key] = castUpdateVal(schematype, val, op, key, context, prefix + key); + if (prefix.length === 0 || key.indexOf('.') === -1) { + obj[key] = castUpdateVal(schematype, val, op, key, context, prefix + key); + } else { + // Setting a nested dotted path that's in the schema. We don't allow paths with '.' in + // a schema, so replace the dotted path with a nested object to avoid ending up with + // dotted properties in the updated object. See (gh-10200) + setDottedPath(obj, key, castUpdateVal(schematype, val, op, key, context, prefix + key)); + delete obj[key]; + } } catch (error) { aggregatedError = _handleCastError(error, context, key, aggregatedError); } diff --git a/lib/helpers/query/completeMany.js b/lib/helpers/query/completeMany.js index aabe59632df..cdc56b72a6e 100644 --- a/lib/helpers/query/completeMany.js +++ b/lib/helpers/query/completeMany.js @@ -1,6 +1,7 @@ 'use strict'; const helpers = require('../../queryhelpers'); +const immediate = require('../immediate'); module.exports = completeMany; @@ -29,10 +30,10 @@ function completeMany(model, docs, fields, userProvidedFields, opts, callback) { error = error || _error; } if (error != null) { - --count || process.nextTick(() => callback(error)); + --count || immediate(() => callback(error)); return; } - --count || process.nextTick(() => callback(error, arr)); + --count || immediate(() => callback(error, arr)); } for (let i = 0; i < len; ++i) { @@ -42,6 +43,9 @@ function completeMany(model, docs, fields, userProvidedFields, opts, callback) { } catch (error) { init(error); } - arr[i].$session(opts.session); + + if (opts.session != null) { + arr[i].$session(opts.session); + } } } diff --git a/lib/helpers/query/getEmbeddedDiscriminatorPath.js b/lib/helpers/query/getEmbeddedDiscriminatorPath.js index 395ea75c4bf..2f3f39d97e0 100644 --- a/lib/helpers/query/getEmbeddedDiscriminatorPath.js +++ b/lib/helpers/query/getEmbeddedDiscriminatorPath.js @@ -3,19 +3,23 @@ const cleanPositionalOperators = require('../schema/cleanPositionalOperators'); const get = require('../get'); const getDiscriminatorByValue = require('../discriminator/getDiscriminatorByValue'); +const updatedPathsByArrayFilter = require('../update/updatedPathsByArrayFilter'); /*! * Like `schema.path()`, except with a document, because impossible to * determine path type without knowing the embedded discriminator key. */ -module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, path) { +module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, path, options) { const parts = path.split('.'); let schematype = null; let type = 'adhocOrUndefined'; filter = filter || {}; update = update || {}; + const arrayFilters = options != null && Array.isArray(options.arrayFilters) ? + options.arrayFilters : []; + const updatedPathsByFilter = updatedPathsByArrayFilter(update); for (let i = 0; i < parts.length; ++i) { const subpath = cleanPositionalOperators(parts.slice(0, i + 1).join('.')); @@ -39,6 +43,7 @@ module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, p if (discriminatorFilterPath in filter) { discriminatorKey = filter[discriminatorFilterPath]; } + const wrapperPath = subpath.replace(/\.\d+$/, ''); if (schematype.$isMongooseDocumentArrayElement && get(filter[wrapperPath], '$elemMatch.' + key) != null) { @@ -49,11 +54,22 @@ module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, p discriminatorKey = update[discriminatorValuePath]; } + for (const filterKey of Object.keys(updatedPathsByFilter)) { + const schemaKey = updatedPathsByFilter[filterKey] + '.' + key; + const arrayFilterKey = filterKey + '.' + key; + if (schemaKey === discriminatorFilterPath) { + const filter = arrayFilters.find(filter => filter.hasOwnProperty(arrayFilterKey)); + if (filter != null) { + discriminatorKey = filter[arrayFilterKey]; + } + } + } + if (discriminatorKey == null) { continue; } - const discriminatorSchema = getDiscriminatorByValue(schematype.caster, discriminatorKey).schema; + const discriminatorSchema = getDiscriminatorByValue(schematype.caster.discriminators, discriminatorKey).schema; const rest = parts.slice(i + 1).join('.'); schematype = discriminatorSchema.path(rest); diff --git a/lib/helpers/query/sanitizeProjection.js b/lib/helpers/query/sanitizeProjection.js new file mode 100644 index 00000000000..dcb61fba910 --- /dev/null +++ b/lib/helpers/query/sanitizeProjection.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = function sanitizeProjection(projection) { + if (projection == null) { + return; + } + + const keys = Object.keys(projection); + for (let i = 0; i < keys.length; ++i) { + if (typeof projection[keys[i]] === 'string') { + projection[keys[i]] = 1; + } + } +}; \ No newline at end of file diff --git a/lib/helpers/query/selectPopulatedFields.js b/lib/helpers/query/selectPopulatedFields.js index 0653f18d71d..92f1d87e04b 100644 --- a/lib/helpers/query/selectPopulatedFields.js +++ b/lib/helpers/query/selectPopulatedFields.js @@ -1,28 +1,31 @@ 'use strict'; +const isExclusive = require('../projection/isExclusive'); +const isInclusive = require('../projection/isInclusive'); + /*! * ignore */ -module.exports = function selectPopulatedFields(query) { - const opts = query._mongooseOptions; +module.exports = function selectPopulatedFields(fields, userProvidedFields, populateOptions) { + if (populateOptions == null) { + return; + } - if (opts.populate != null) { - const paths = Object.keys(opts.populate); - const userProvidedFields = query._userProvidedFields || {}; - if (query.selectedInclusively()) { - for (const path of paths) { - if (!isPathInFields(userProvidedFields, path)) { - query.select(path); - } else if (userProvidedFields[path] === 0) { - delete query._fields[path]; - } + const paths = Object.keys(populateOptions); + userProvidedFields = userProvidedFields || {}; + if (isInclusive(fields)) { + for (const path of paths) { + if (!isPathInFields(userProvidedFields, path)) { + fields[path] = 1; + } else if (userProvidedFields[path] === 0) { + delete fields[path]; } - } else if (query.selectedExclusively()) { - for (const path of paths) { - if (userProvidedFields[path] == null) { - delete query._fields[path]; - } + } + } else if (isExclusive(fields)) { + for (const path of paths) { + if (userProvidedFields[path] == null) { + delete fields[path]; } } } @@ -37,10 +40,10 @@ function isPathInFields(userProvidedFields, path) { const len = pieces.length; let cur = pieces[0]; for (let i = 1; i < len; ++i) { - if (userProvidedFields[cur] != null) { + if (userProvidedFields[cur] != null || userProvidedFields[cur + '.$'] != null) { return true; } cur += '.' + pieces[i]; } - return userProvidedFields[cur] != null; + return userProvidedFields[cur] != null || userProvidedFields[cur + '.$'] != null; } diff --git a/lib/helpers/schema/applyWriteConcern.js b/lib/helpers/schema/applyWriteConcern.js index 168156db117..4095bd94bc7 100644 --- a/lib/helpers/schema/applyWriteConcern.js +++ b/lib/helpers/schema/applyWriteConcern.js @@ -4,13 +4,27 @@ const get = require('../get'); module.exports = function applyWriteConcern(schema, options) { const writeConcern = get(schema, 'options.writeConcern', {}); - if (!('w' in options) && writeConcern.w != null) { - options.w = writeConcern.w; + if (Object.keys(writeConcern).length != 0) { + options.writeConcern = {}; + if (!('w' in options) && writeConcern.w != null) { + options.writeConcern.w = writeConcern.w; + } + if (!('j' in options) && writeConcern.j != null) { + options.writeConcern.j = writeConcern.j; + } + if (!('wtimeout' in options) && writeConcern.wtimeout != null) { + options.writeConcern.wtimeout = writeConcern.wtimeout; + } } - if (!('j' in options) && writeConcern.j != null) { - options.j = writeConcern.j; - } - if (!('wtimeout' in options) && writeConcern.wtimeout != null) { - options.wtimeout = writeConcern.wtimeout; + else { + if (!('w' in options) && writeConcern.w != null) { + options.w = writeConcern.w; + } + if (!('j' in options) && writeConcern.j != null) { + options.j = writeConcern.j; + } + if (!('wtimeout' in options) && writeConcern.wtimeout != null) { + options.wtimeout = writeConcern.wtimeout; + } } }; diff --git a/lib/helpers/schema/cleanPositionalOperators.js b/lib/helpers/schema/cleanPositionalOperators.js index b467be44968..905bb78820b 100644 --- a/lib/helpers/schema/cleanPositionalOperators.js +++ b/lib/helpers/schema/cleanPositionalOperators.js @@ -7,6 +7,6 @@ module.exports = function cleanPositionalOperators(path) { return path. - replace(/\.\$(\[[^\]]*\])?\./g, '.0.'). - replace(/\.(\[[^\]]*\])?\$$/g, '.0'); + replace(/\.\$(\[[^\]]*\])?(?=\.)/g, '.0'). + replace(/\.\$(\[[^\]]*\])?$/g, '.0'); }; \ No newline at end of file diff --git a/lib/helpers/schema/merge.js b/lib/helpers/schema/merge.js index d206500c440..edc5175eedc 100644 --- a/lib/helpers/schema/merge.js +++ b/lib/helpers/schema/merge.js @@ -1,7 +1,15 @@ 'use strict'; -module.exports = function merge(s1, s2) { - s1.add(s2.tree || {}); +module.exports = function merge(s1, s2, skipConflictingPaths) { + const paths = Object.keys(s2.tree); + const pathsToAdd = {}; + for (const key of paths) { + if (skipConflictingPaths && (s1.paths[key] || s1.nested[key] || s1.singleNestedPaths[key])) { + continue; + } + pathsToAdd[key] = s2.tree[key]; + } + s1.add(pathsToAdd); s1.callQueue = s1.callQueue.concat(s2.callQueue); s1.method(s2.methods); diff --git a/lib/helpers/timers.js b/lib/helpers/timers.js new file mode 100644 index 00000000000..7bd09c7418c --- /dev/null +++ b/lib/helpers/timers.js @@ -0,0 +1,3 @@ +'use strict'; + +exports.setTimeout = setTimeout; \ No newline at end of file diff --git a/lib/helpers/timestamps/setupTimestamps.js b/lib/helpers/timestamps/setupTimestamps.js index 752a565738c..6eb4c7955e8 100644 --- a/lib/helpers/timestamps/setupTimestamps.js +++ b/lib/helpers/timestamps/setupTimestamps.js @@ -51,7 +51,7 @@ module.exports = function setupTimestamps(schema, timestamps) { (this.ownerDocument ? this.ownerDocument() : this).constructor.base.now(); const auto_id = this._id && this._id.auto; - if (!skipCreatedAt && createdAt && !this.get(createdAt) && this.isSelected(createdAt)) { + if (!skipCreatedAt && this.isNew && createdAt && !this.get(createdAt) && this.$__isSelected(createdAt)) { this.$set(createdAt, auto_id ? this._id.getTimestamp() : defaultTimestamp); } @@ -86,6 +86,7 @@ module.exports = function setupTimestamps(schema, timestamps) { _setTimestampsOnUpdate[symbols.builtInMiddleware] = true; const opts = { query: true, model: false }; + schema.pre('findOneAndReplace', opts, _setTimestampsOnUpdate); schema.pre('findOneAndUpdate', opts, _setTimestampsOnUpdate); schema.pre('replaceOne', opts, _setTimestampsOnUpdate); schema.pre('update', opts, _setTimestampsOnUpdate); @@ -96,6 +97,12 @@ module.exports = function setupTimestamps(schema, timestamps) { const now = currentTime != null ? currentTime() : this.model.base.now(); + + // Replacing with null update should still trigger timestamps + if (this.op === 'findOneAndReplace' && this.getUpdate() == null) { + this.setUpdate({}); + } + applyTimestampsToUpdate(now, createdAt, updatedAt, this.getUpdate(), this.options, this.schema); applyTimestampsToChildren(now, this.getUpdate(), this.model.schema); diff --git a/lib/helpers/topology/allServersUnknown.js b/lib/helpers/topology/allServersUnknown.js index d3c7b4b07b4..33d23ab3e7e 100644 --- a/lib/helpers/topology/allServersUnknown.js +++ b/lib/helpers/topology/allServersUnknown.js @@ -1,8 +1,9 @@ 'use strict'; +const getConstructorName = require('../getConstructorName'); + module.exports = function allServersUnknown(topologyDescription) { - if (topologyDescription == null || - topologyDescription.constructor.name !== 'TopologyDescription') { + if (getConstructorName(topologyDescription) !== 'TopologyDescription') { return false; } diff --git a/lib/helpers/topology/isAtlas.js b/lib/helpers/topology/isAtlas.js index 3bb8129e3b7..f0638d55136 100644 --- a/lib/helpers/topology/isAtlas.js +++ b/lib/helpers/topology/isAtlas.js @@ -1,8 +1,9 @@ 'use strict'; +const getConstructorName = require('../getConstructorName'); + module.exports = function isAtlas(topologyDescription) { - if (topologyDescription == null || - topologyDescription.constructor.name !== 'TopologyDescription') { + if (getConstructorName(topologyDescription) !== 'TopologyDescription') { return false; } diff --git a/lib/helpers/topology/isSSLError.js b/lib/helpers/topology/isSSLError.js index 61e57d1d286..d910fc8344c 100644 --- a/lib/helpers/topology/isSSLError.js +++ b/lib/helpers/topology/isSSLError.js @@ -1,11 +1,12 @@ 'use strict'; +const getConstructorName = require('../getConstructorName'); + const nonSSLMessage = 'Client network socket disconnected before secure TLS ' + 'connection was established'; module.exports = function isSSLError(topologyDescription) { - if (topologyDescription == null || - topologyDescription.constructor.name !== 'TopologyDescription') { + if (getConstructorName(topologyDescription) !== 'TopologyDescription') { return false; } diff --git a/lib/helpers/update/applyTimestampsToChildren.js b/lib/helpers/update/applyTimestampsToChildren.js index 680d692d02b..6186c3d4d92 100644 --- a/lib/helpers/update/applyTimestampsToChildren.js +++ b/lib/helpers/update/applyTimestampsToChildren.js @@ -19,34 +19,10 @@ function applyTimestampsToChildren(now, update, schema) { if (hasDollarKey) { if (update.$push) { - for (const key of Object.keys(update.$push)) { - const $path = schema.path(key); - if (update.$push[key] && - $path && - $path.$isMongooseDocumentArray && - $path.schema.options.timestamps) { - const timestamps = $path.schema.options.timestamps; - const createdAt = handleTimestampOption(timestamps, 'createdAt'); - const updatedAt = handleTimestampOption(timestamps, 'updatedAt'); - if (update.$push[key].$each) { - update.$push[key].$each.forEach(function(subdoc) { - if (updatedAt != null) { - subdoc[updatedAt] = now; - } - if (createdAt != null) { - subdoc[createdAt] = now; - } - }); - } else { - if (updatedAt != null) { - update.$push[key][updatedAt] = now; - } - if (createdAt != null) { - update.$push[key][createdAt] = now; - } - } - } - } + _applyTimestampToUpdateOperator(update.$push); + } + if (update.$addToSet) { + _applyTimestampToUpdateOperator(update.$addToSet); } if (update.$set != null) { const keys = Object.keys(update.$set); @@ -54,12 +30,49 @@ function applyTimestampsToChildren(now, update, schema) { applyTimestampsToUpdateKey(schema, key, update.$set, now); } } + if (update.$setOnInsert != null) { + const keys = Object.keys(update.$setOnInsert); + for (const key of keys) { + applyTimestampsToUpdateKey(schema, key, update.$setOnInsert, now); + } + } } const updateKeys = Object.keys(update).filter(key => !key.startsWith('$')); for (const key of updateKeys) { applyTimestampsToUpdateKey(schema, key, update, now); } + + function _applyTimestampToUpdateOperator(op) { + for (const key of Object.keys(op)) { + const $path = schema.path(key.replace(/\.\$\./i, '.').replace(/.\$$/, '')); + if (op[key] && + $path && + $path.$isMongooseDocumentArray && + $path.schema.options.timestamps) { + const timestamps = $path.schema.options.timestamps; + const createdAt = handleTimestampOption(timestamps, 'createdAt'); + const updatedAt = handleTimestampOption(timestamps, 'updatedAt'); + if (op[key].$each) { + op[key].$each.forEach(function(subdoc) { + if (updatedAt != null) { + subdoc[updatedAt] = now; + } + if (createdAt != null) { + subdoc[createdAt] = now; + } + }); + } else { + if (updatedAt != null) { + op[key][updatedAt] = now; + } + if (createdAt != null) { + op[key][createdAt] = now; + } + } + } + } + } } function applyTimestampsToDocumentArray(arr, schematype, now) { diff --git a/lib/helpers/update/castArrayFilters.js b/lib/helpers/update/castArrayFilters.js index 57018d9fb10..a9992f2a507 100644 --- a/lib/helpers/update/castArrayFilters.js +++ b/lib/helpers/update/castArrayFilters.js @@ -3,7 +3,7 @@ const castFilterPath = require('../query/castFilterPath'); const cleanPositionalOperators = require('../schema/cleanPositionalOperators'); const getPath = require('../schema/getPath'); -const modifiedPaths = require('./modifiedPaths'); +const updatedPathsByArrayFilter = require('./updatedPathsByArrayFilter'); module.exports = function castArrayFilters(query) { const arrayFilters = query.options.arrayFilters; @@ -15,31 +15,13 @@ module.exports = function castArrayFilters(query) { const schema = query.schema; const strictQuery = schema.options.strictQuery; - const updatedPaths = modifiedPaths(update); - - const updatedPathsByFilter = Object.keys(updatedPaths).reduce((cur, path) => { - const matches = path.match(/\$\[[^\]]+\]/g); - if (matches == null) { - return cur; - } - for (const match of matches) { - const firstMatch = path.indexOf(match); - if (firstMatch !== path.lastIndexOf(match)) { - throw new Error(`Path '${path}' contains the same array filter multiple times`); - } - cur[match.substring(2, match.length - 1)] = path. - substr(0, firstMatch - 1). - replace(/\$\[[^\]]+\]/g, '0'); - } - return cur; - }, {}); + const updatedPathsByFilter = updatedPathsByArrayFilter(update); for (const filter of arrayFilters) { if (filter == null) { throw new Error(`Got null array filter in ${arrayFilters}`); } - for (const key in filter) { - + for (const key of Object.keys(filter)) { if (filter[key] == null) { continue; } diff --git a/lib/helpers/update/updatedPathsByArrayFilter.js b/lib/helpers/update/updatedPathsByArrayFilter.js new file mode 100644 index 00000000000..7cbae298402 --- /dev/null +++ b/lib/helpers/update/updatedPathsByArrayFilter.js @@ -0,0 +1,24 @@ +'use strict'; + +const modifiedPaths = require('./modifiedPaths'); + +module.exports = function updatedPathsByArrayFilter(update) { + const updatedPaths = modifiedPaths(update); + + return Object.keys(updatedPaths).reduce((cur, path) => { + const matches = path.match(/\$\[[^\]]+\]/g); + if (matches == null) { + return cur; + } + for (const match of matches) { + const firstMatch = path.indexOf(match); + if (firstMatch !== path.lastIndexOf(match)) { + throw new Error(`Path '${path}' contains the same array filter multiple times`); + } + cur[match.substring(2, match.length - 1)] = path. + substr(0, firstMatch - 1). + replace(/\$\[[^\]]+\]/g, '0'); + } + return cur; + }, {}); +}; \ No newline at end of file diff --git a/lib/index.js b/lib/index.js index 1425cfac69a..30c74ec3400 100644 --- a/lib/index.js +++ b/lib/index.js @@ -16,6 +16,7 @@ if (global.MONGOOSE_DRIVER_PATH) { } const Document = require('./document'); +const EventEmitter = require('events').EventEmitter; const Schema = require('./schema'); const SchemaType = require('./schematype'); const SchemaTypes = require('./schema/index'); @@ -26,6 +27,7 @@ const Types = require('./types'); const Query = require('./query'); const Model = require('./model'); const applyPlugins = require('./helpers/schema/applyPlugins'); +const driver = require('./driver'); const get = require('./helpers/get'); const promiseOrCallback = require('./helpers/promiseOrCallback'); const legacyPluralize = require('mongoose-legacy-pluralize'); @@ -65,9 +67,12 @@ function Mongoose(options) { this.connections = []; this.models = {}; this.modelSchemas = {}; + this.events = new EventEmitter(); // default global options this.options = Object.assign({ - pluralization: true + pluralization: true, + autoIndex: true, + useCreateIndex: false }, options); const conn = this.createConnection(); // default connection conn.models = this.models; @@ -123,8 +128,8 @@ Mongoose.prototype.cast = cast; Mongoose.prototype.STATES = STATES; /** - * The underlying driver this Mongoose instance uses to communicate with - * the database. A driver is a Mongoose-specific interface that defines functions + * Object with `get()` and `set()` containing the underlying driver this Mongoose instance + * uses to communicate with the database. A driver is a Mongoose-specific interface that defines functions * like `find()`. * * @memberOf Mongoose @@ -132,7 +137,7 @@ Mongoose.prototype.STATES = STATES; * @api public */ -Mongoose.prototype.driver = require('./driver'); +Mongoose.prototype.driver = driver; /** * Sets mongoose options @@ -280,6 +285,7 @@ Mongoose.prototype.createConnection = function(uri, options, callback) { options = null; } _mongoose.connections.push(conn); + _mongoose.events.emit('createConnection', conn); if (arguments.length > 0) { return conn.openUri(uri, options, callback); @@ -311,7 +317,7 @@ Mongoose.prototype.createConnection = function(uri, options, callback) { * @param {String} uri(s) * @param {Object} [options] passed down to the [MongoDB driver's `connect()` function](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html), except for 4 mongoose-specific options explained below. * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](http://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection. - * @param {Number} [options.bufferTimeoutMS=true] Mongoose specific option. If `bufferCommands` is true, Mongoose will throw an error after `bufferTimeoutMS` if the operation is still buffered. + * @param {Number} [options.bufferTimeoutMS=10000] Mongoose specific option. If `bufferCommands` is true, Mongoose will throw an error after `bufferTimeoutMS` if the operation is still buffered. * @param {String} [options.dbName] The name of the database we want to use. If not provided, use database name from connection string. * @param {String} [options.user] username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility. * @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility. @@ -581,6 +587,8 @@ Mongoose.prototype.model = function(name, schema, collection, skipInit) { model.init(function $modelInitNoop() {}); } + connection.emit('model', model); + if (options.cache === false) { return model; } @@ -728,23 +736,17 @@ Mongoose.prototype.__defineSetter__('connection', function(v) { Mongoose.prototype.connections; -/*! - * Driver dependent APIs - */ - -const driver = global.MONGOOSE_DRIVER_PATH || './drivers/node-mongodb-native'; - /*! * Connection */ -const Connection = require(driver + '/connection'); +const Connection = driver.get().getConnection(); /*! * Collection */ -const Collection = require(driver + '/collection'); +const Collection = driver.get().Collection; /** * The Mongoose Aggregate constructor @@ -861,11 +863,14 @@ Mongoose.prototype.VirtualType = VirtualType; * * ####Types: * - * - [ObjectId](#types-objectid-js) - * - [Buffer](#types-buffer-js) - * - [SubDocument](#types-embedded-js) - * - [Array](#types-array-js) - * - [DocumentArray](#types-documentarray-js) + * - [Array](/docs/schematypes.html#arrays) + * - [Buffer](/docs/schematypes.html#buffers) + * - [Embedded](/docs/schematypes.html#schemas) + * - [DocumentArray](/docs/api/documentarraypath.html) + * - [Decimal128](/docs/api.html#mongoose_Mongoose-Decimal128) + * - [ObjectId](/docs/schematypes.html#objectids) + * - [Map](/docs/schematypes.html#maps) + * - [Subdocument](/docs/schematypes.html#schemas) * * Using this exposed access to the `ObjectId` type, we can construct ids on demand. * @@ -989,7 +994,13 @@ Mongoose.prototype.isValidObjectId = function(v) { } if (v._id.toString instanceof Function) { v = v._id.toString(); - return typeof v === 'string' && (v.length === 12 || v.length === 24); + if (typeof v === 'string' && v.length === 12) { + return true; + } + if (typeof v === 'string' && v.length === 24 && /^[a-f0-9]*$/.test(v)) { + return true; + } + return false; } } @@ -997,7 +1008,10 @@ Mongoose.prototype.isValidObjectId = function(v) { v = v.toString(); } - if (typeof v === 'string' && (v.length === 12 || v.length === 24)) { + if (typeof v === 'string' && v.length === 12) { + return true; + } + if (typeof v === 'string' && v.length === 24 && /^[a-f0-9]*$/.test(v)) { return true; } diff --git a/lib/model.js b/lib/model.js index cf8bdfff7fc..412803aca9f 100644 --- a/lib/model.js +++ b/lib/model.js @@ -19,7 +19,6 @@ const RemoveOptions = require('./options/removeOptions'); const SaveOptions = require('./options/saveOptions'); const Schema = require('./schema'); const ServerSelectionError = require('./error/serverSelection'); -const SkipPopulateValue = require('./helpers/populate/SkipPopulateValue'); const ValidationError = require('./error/validation'); const VersionError = require('./error/version'); const ParallelSaveError = require('./error/parallelSave'); @@ -31,10 +30,12 @@ const applyStatics = require('./helpers/model/applyStatics'); const applyWriteConcern = require('./helpers/schema/applyWriteConcern'); const assignVals = require('./helpers/populate/assignVals'); const castBulkWrite = require('./helpers/model/castBulkWrite'); +const createPopulateQueryFilter = require('./helpers/populate/createPopulateQueryFilter'); const getDefaultBulkwriteResult = require('./helpers/getDefaultBulkwriteResult'); const discriminator = require('./helpers/model/discriminator'); const each = require('./helpers/each'); const get = require('./helpers/get'); +const getConstructorName = require('./helpers/getConstructorName'); const getDiscriminatorByValue = require('./helpers/discriminator/getDiscriminatorByValue'); const getModelsMapForPopulate = require('./helpers/populate/getModelsMapForPopulate'); const immediate = require('./helpers/immediate'); @@ -139,6 +140,20 @@ Model.prototype.db; Model.prototype.collection; +/** + * Internal collection the model uses. + * + * This property is read-only. Modifying this property is a no-op. + * + * @api private + * @property collection + * @memberOf Model + * @instance + */ + + +Model.prototype.$__collection; + /** * The name of the model * @@ -212,11 +227,8 @@ function _applyCustomWhere(doc, where) { if (doc.$where == null) { return; } - - const keys = Object.keys(doc.$where); - const len = keys.length; - for (let i = 0; i < len; ++i) { - where[keys[i]] = doc.$where[keys[i]]; + for (const key of Object.keys(doc.$where)) { + where[key] = doc.$where[key]; } } @@ -231,20 +243,33 @@ Model.prototype.$__handleSave = function(options, callback) { if ('safe' in options) { _handleSafe(options); } - applyWriteConcern(this.schema, options); - if ('w' in options) { - saveOptions.w = options.w; - } - if ('j' in options) { - saveOptions.j = options.j; - } - if ('wtimeout' in options) { - saveOptions.wtimeout = options.wtimeout; + + applyWriteConcern(this.$__schema, options); + if (typeof options.writeConcern != 'undefined') { + saveOptions.writeConcern = {}; + if ('w' in options.writeConcern) { + saveOptions.writeConcern.w = options.writeConcern.w; + } + if ('j' in options.writeConcern) { + saveOptions.writeConcern.j = options.writeConcern.j; + } + if ('wtimeout' in options.writeConcern) { + saveOptions.writeConcern.wtimeout = options.writeConcern.wtimeout; + } + } else { + if ('w' in options) { + saveOptions.w = options.w; + } + if ('j' in options) { + saveOptions.j = options.j; + } + if ('wtimeout' in options) { + saveOptions.wtimeout = options.wtimeout; + } } if ('checkKeys' in options) { saveOptions.checkKeys = options.checkKeys; } - const session = this.$session(); if (!saveOptions.hasOwnProperty('session')) { saveOptions.session = session; @@ -253,20 +278,18 @@ Model.prototype.$__handleSave = function(options, callback) { if (Object.keys(saveOptions).length === 0) { saveOptions = null; } - if (this.isNew) { // send entire doc const obj = this.toObject(saveToObjectOptions); - if ((obj || {})._id === void 0) { // documents must have an _id else mongoose won't know // what to update later if more changes are made. the user // wouldn't know what _id was generated by mongodb either // nor would the ObjectId generated by mongodb necessarily // match the schema definition. - setTimeout(function() { + immediate(function() { callback(new MongooseError('document must have an _id before saving')); - }, 0); + }); return; } @@ -281,7 +304,6 @@ Model.prototype.$__handleSave = function(options, callback) { callback(null, ret); }); - this.$__reset(); _setIsNew(this, false); // Make it possible to retry the insert @@ -292,7 +314,6 @@ Model.prototype.$__handleSave = function(options, callback) { this.$__.inserting = false; const delta = this.$__delta(); - if (delta) { if (delta instanceof MongooseError) { callback(delta); @@ -319,7 +340,15 @@ Model.prototype.$__handleSave = function(options, callback) { }); } else { const optionsWithCustomValues = Object.assign({}, options, saveOptions); - this.constructor.exists(this.$__where(), optionsWithCustomValues) + const where = this.$__where(); + if (this.$__schema.options.optimisticConcurrency) { + const key = this.$__schema.options.versionKey; + const val = this.$__getValue(key); + if (val != null) { + where[key] = val; + } + } + this.constructor.exists(where, optionsWithCustomValues) .then((documentExists) => { if (!documentExists) { throw new DocumentNotFoundError(this.$__where(), this.constructor.modelName); @@ -333,7 +362,6 @@ Model.prototype.$__handleSave = function(options, callback) { // store the modified paths before the document is reset this.$__.modifiedPaths = this.modifiedPaths(); - this.$__reset(); _setIsNew(this, false); @@ -346,13 +374,12 @@ Model.prototype.$__handleSave = function(options, callback) { Model.prototype.$__save = function(options, callback) { this.$__handleSave(options, (error, result) => { - const hooks = this.schema.s.hooks; + const hooks = this.$__schema.s.hooks; if (error) { return hooks.execPost('save:error', this, [this], { error: error }, (error) => { callback(error, this); }); } - let numAffected = 0; if (get(options, 'safe.w') !== 0 && get(options, 'w') !== 0) { // Skip checking if write succeeded if writeConcern is set to @@ -368,15 +395,12 @@ Model.prototype.$__save = function(options, callback) { numAffected = result; } } - // was this an update that required a version bump? if (this.$__.version && !this.$__.inserting) { const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; - - const key = this.schema.options.versionKey; + const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; - if (numAffected <= 0) { // the update failed. pass an error back this.$__undoReset(); @@ -390,7 +414,6 @@ Model.prototype.$__save = function(options, callback) { this.$__setValue(key, version + 1); } } - if (result != null && numAffected <= 0) { this.$__undoReset(); error = new DocumentNotFoundError(result.$where, @@ -413,7 +436,7 @@ Model.prototype.$__save = function(options, callback) { */ function generateVersionError(doc, modifiedPaths) { - const key = doc.schema.options.versionKey; + const key = doc.$__schema.options.versionKey; if (!key) { return null; } @@ -474,12 +497,10 @@ Model.prototype.save = function(options, fn) { if (options.hasOwnProperty('session')) { this.$session(options.session); } - this.$__.$versionError = generateVersionError(this, this.modifiedPaths()); fn = this.constructor.$handleCallbackError(fn); - - return this.db.base._promiseOrCallback(fn, cb => { + return this.constructor.db.base._promiseOrCallback(fn, cb => { cb = this.constructor.$wrapCallback(cb); if (parallelSave) { @@ -512,7 +533,7 @@ Model.prototype.save = function(options, fn) { * @return {Boolean} true if versioning should be skipped for the given path */ function shouldSkipVersioning(self, path) { - const skipVersioning = self.schema.options.skipVersioning; + const skipVersioning = self.$__schema.options.skipVersioning; if (!skipVersioning) return false; // Remove any array indexes from the path @@ -538,9 +559,8 @@ function operand(self, where, delta, data, val, op) { op || (op = '$set'); if (!delta[op]) delta[op] = {}; delta[op][data.path] = val; - // disabled versioning? - if (self.schema.options.versionKey === false) return; + if (self.$__schema.options.versionKey === false) return; // path excluded from versioning? if (shouldSkipVersioning(self, data.path)) return; @@ -548,7 +568,7 @@ function operand(self, where, delta, data, val, op) { // already marked for versioning? if (VERSION_ALL === (VERSION_ALL & self.$__.version)) return; - if (self.schema.options.optimisticConcurrency) { + if (self.$__schema.options.optimisticConcurrency) { self.$__.version = VERSION_ALL; return; } @@ -671,7 +691,6 @@ Model.prototype.$__delta = function() { if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } - const where = {}; const delta = {}; const len = dirty.length; @@ -684,7 +703,6 @@ Model.prototype.$__delta = function() { if (get(where, '_id.$__', null) != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } - for (; d < len; ++d) { const data = dirty[d]; let value = data.value; @@ -717,7 +735,6 @@ Model.prototype.$__delta = function() { } if (divergent.length) continue; - if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { @@ -748,7 +765,6 @@ Model.prototype.$__delta = function() { if (this.$__.version) { this.$__version(where, delta); } - return [where, delta]; }; @@ -811,11 +827,13 @@ function checkDivergentArray(doc, path, array) { */ Model.prototype.$__version = function(where, delta) { - const key = this.schema.options.versionKey; + const key = this.$__schema.options.versionKey; if (where === true) { // this is an insert - if (key) this.$__setValue(key, delta[key] = 0); + if (key) { + this.$__setValue(key, delta[key] = 0); + } return; } @@ -825,7 +843,7 @@ Model.prototype.$__version = function(where, delta) { // there is no way to select the correct version. we could fail // fast here and force them to include the versionKey but // thats a bit intrusive. can we do this automatically? - if (!this.isSelected(key)) { + if (!this.$__isSelected(key)) { return; } @@ -934,7 +952,7 @@ Model.prototype.remove = function remove(options, fn) { fn = this.constructor.$handleCallbackError(fn); - return this.db.base._promiseOrCallback(fn, cb => { + return this.constructor.db.base._promiseOrCallback(fn, cb => { cb = this.constructor.$wrapCallback(cb); this.$__remove(options, (err, res) => { this.$op = null; @@ -973,7 +991,7 @@ Model.prototype.deleteOne = function deleteOne(options, fn) { fn = this.constructor.$handleCallbackError(fn); - return this.db.base._promiseOrCallback(fn, cb => { + return this.constructor.db.base._promiseOrCallback(fn, cb => { cb = this.constructor.$wrapCallback(cb); this.$__deleteOne(options, cb); }, this.constructor.events); @@ -1053,13 +1071,13 @@ Model.prototype.model = function model(name) { * - `findOne()` * * @param {Object} filter + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) * @param {Function} [callback] callback * @return {Promise} */ Model.exists = function exists(filter, options, callback) { _checkContext(this, 'exists'); - if (typeof options === 'function') { callback = options; options = null; @@ -1079,8 +1097,12 @@ Model.exists = function exists(filter, options, callback) { }); return; } + options = options || {}; + if (!options.explain) { + return query.then(doc => !!doc); + } - return query.then(doc => !!doc); + return query.exec(); }; /** @@ -1140,7 +1162,7 @@ Model.discriminator = function(name, schema, value) { schema.$isRootDiscriminator = true; schema.$globalPluginsApplied = true; - model = this.db.model(model || name, schema, this.collection.name); + model = this.db.model(model || name, schema, this.$__collection.name); this.discriminators[name] = model; const d = this.discriminators[name]; d.prototype.__proto__ = this.prototype; @@ -1326,12 +1348,12 @@ Model.createCollection = function createCollection(options, callback) { return this.db.base._promiseOrCallback(callback, cb => { cb = this.$wrapCallback(cb); - this.db.createCollection(this.collection.collectionName, options, utils.tick((error) => { - if (error != null && error.codeName !== 'NamespaceExists') { - return cb(error); + this.db.createCollection(this.$__collection.collectionName, options, utils.tick((err) => { + if (err != null && (err.name !== 'MongoError' || err.code !== 48)) { + return cb(err); } - this.collection = this.db.collection(this.collection.collectionName, options); - cb(null, this.collection); + this.$__collection = this.db.collection(this.$__collection.collectionName, options); + cb(null, this.$__collection); })); }, this.events); }; @@ -1369,7 +1391,7 @@ Model.syncIndexes = function syncIndexes(options, callback) { cb = this.$wrapCallback(cb); this.createCollection(err => { - if (err != null && err.codeName !== 'NamespaceExists') { + if (err != null && (err.name !== 'MongoError' || err.code !== 48)) { return cb(err); } this.cleanIndexes((err, dropped) => { @@ -1387,6 +1409,71 @@ Model.syncIndexes = function syncIndexes(options, callback) { }, this.events); }; +/** + * Does a dry-run of Model.syncIndexes(), meaning that + * the result of this function would be the result of + * Model.syncIndexes(). + * + * @param {Object} options not used at all. + * @param {Function} callback optional callback + * @returns {Promise} which containts an object, {toDrop, toCreate}, which + * are indexes that would be dropped in mongodb and indexes that would be created in mongodb. + */ + +Model.diffIndexes = function diffIndexes(options, callback) { + const toDrop = []; + const toCreate = []; + callback = this.$handleCallbackError(callback); + return this.db.base._promiseOrCallback(callback, cb => { + cb = this.$wrapCallback(cb); + this.listIndexes((err, indexes) => { + const schemaIndexes = this.schema.indexes(); + // Iterate through the indexes created in mongodb and + // compare against the indexes in the schema. + for (const index of indexes) { + let found = false; + // Never try to drop `_id` index, MongoDB server doesn't allow it + if (isDefaultIdIndex(index)) { + continue; + } + + for (const schemaIndex of schemaIndexes) { + const key = schemaIndex[0]; + const options = _decorateDiscriminatorIndexOptions(this, + utils.clone(schemaIndex[1])); + if (isIndexEqual(key, options, index)) { + found = true; + } + } + + if (!found) { + toDrop.push(index.name); + } + } + // Iterate through the indexes created on the schema and + // compare against the indexes in mongodb. + for (const schemaIndex of schemaIndexes) { + const key = schemaIndex[0]; + let found = false; + const options = _decorateDiscriminatorIndexOptions(this, + utils.clone(schemaIndex[1])); + for (const index of indexes) { + if (isDefaultIdIndex(index)) { + continue; + } + if (isIndexEqual(key, options, index)) { + found = true; + } + } + if (!found) { + toCreate.push(key); + } + } + cb(null, { toDrop, toCreate }); + }); + }); +}; + /** * Deletes all indexes that aren't defined in this model's schema. Used by * `syncIndexes()`. @@ -1404,7 +1491,7 @@ Model.cleanIndexes = function cleanIndexes(callback) { callback = this.$handleCallbackError(callback); return this.db.base._promiseOrCallback(callback, cb => { - const collection = this.collection; + const collection = this.$__collection; this.listIndexes((err, indexes) => { if (err != null) { @@ -1475,7 +1562,7 @@ Model.listIndexes = function init(callback) { _checkContext(this, 'listIndexes'); const _listIndexes = cb => { - this.collection.listIndexes().toArray(cb); + this.$__collection.listIndexes().toArray(cb); }; callback = this.$handleCallbackError(callback); @@ -1484,8 +1571,8 @@ Model.listIndexes = function init(callback) { cb = this.$wrapCallback(cb); // Buffering - if (this.collection.buffer) { - this.collection.addQueue(_listIndexes, [cb]); + if (this.$__collection.buffer) { + this.$__collection.addQueue(_listIndexes, [cb]); } else { _listIndexes(cb); } @@ -1574,7 +1661,6 @@ function _ensureIndexes(model, options, callback) { let indexError; options = options || {}; - const done = function(err) { if (err && !model.$caught) { model.emit('error', err); @@ -1633,8 +1719,13 @@ function _ensureIndexes(model, options, callback) { const indexFields = utils.clone(index[0]); const indexOptions = utils.clone(index[1]); + let isTextIndex = false; + for (const key of Object.keys(indexFields)) { + if (indexFields[key] === 'text') { + isTextIndex = true; + } + } delete indexOptions._autoIndex; - _decorateDiscriminatorIndexOptions(model, indexOptions); if ('safe' in options) { _handleSafe(options); @@ -1652,6 +1743,11 @@ function _ensureIndexes(model, options, callback) { if ('background' in options) { indexOptions.background = options.background; } + if (model.schema.options.hasOwnProperty('collation') && + !indexOptions.hasOwnProperty('collation') && + !isTextIndex) { + indexOptions.collation = model.schema.options.collation; + } const methodName = useCreateIndex ? 'createIndex' : 'ensureIndex'; model.collection[methodName](indexFields, indexOptions, utils.tick(function(err, name) { @@ -1745,6 +1841,15 @@ Model.db; Model.collection; +/** + * Internal collection the model uses. + * + * @property collection + * @api private + * @memberOf Model + */ +Model.$__collection; + /** * Base Mongoose instance the model uses. * @@ -1894,7 +1999,7 @@ Model.remove = function remove(conditions, options, callback) { } // get the mongodb collection object - const mq = new this.Query({}, {}, this, this.collection); + const mq = new this.Query({}, {}, this, this.$__collection); mq.setOptions(options); callback = this.$handleCallbackError(callback); @@ -1936,7 +2041,7 @@ Model.deleteOne = function deleteOne(conditions, options, callback) { options = null; } - const mq = new this.Query({}, {}, this, this.collection); + const mq = new this.Query({}, {}, this, this.$__collection); mq.setOptions(options); callback = this.$handleCallbackError(callback); @@ -1977,7 +2082,7 @@ Model.deleteMany = function deleteMany(conditions, options, callback) { options = null; } - const mq = new this.Query({}, {}, this, this.collection); + const mq = new this.Query({}, {}, this, this.$__collection); mq.setOptions(options); callback = this.$handleCallbackError(callback); @@ -2036,7 +2141,7 @@ Model.find = function find(conditions, projection, options, callback) { options = null; } - const mq = new this.Query({}, {}, this, this.collection); + const mq = new this.Query({}, {}, this, this.$__collection); mq.select(projection); mq.setOptions(options); @@ -2134,7 +2239,6 @@ Model.findById = function findById(id, projection, options, callback) { Model.findOne = function findOne(conditions, projection, options, callback) { _checkContext(this, 'findOne'); - if (typeof options === 'function') { callback = options; options = null; @@ -2149,7 +2253,7 @@ Model.findOne = function findOne(conditions, projection, options, callback) { options = null; } - const mq = new this.Query({}, {}, this, this.collection); + const mq = new this.Query({}, {}, this, this.$__collection); mq.select(projection); mq.setOptions(options); @@ -2160,7 +2264,6 @@ Model.findOne = function findOne(conditions, projection, options, callback) { } callback = this.$handleCallbackError(callback); - return mq.findOne(conditions, callback); }; @@ -2183,7 +2286,7 @@ Model.findOne = function findOne(conditions, projection, options, callback) { Model.estimatedDocumentCount = function estimatedDocumentCount(options, callback) { _checkContext(this, 'estimatedDocumentCount'); - const mq = new this.Query({}, {}, this, this.collection); + const mq = new this.Query({}, {}, this, this.$__collection); callback = this.$handleCallbackError(callback); @@ -2227,7 +2330,7 @@ Model.countDocuments = function countDocuments(conditions, callback) { conditions = {}; } - const mq = new this.Query({}, {}, this, this.collection); + const mq = new this.Query({}, {}, this, this.$__collection); callback = this.$handleCallbackError(callback); @@ -2263,7 +2366,7 @@ Model.count = function count(conditions, callback) { conditions = {}; } - const mq = new this.Query({}, {}, this, this.collection); + const mq = new this.Query({}, {}, this, this.$__collection); callback = this.$handleCallbackError(callback); @@ -2297,7 +2400,7 @@ Model.count = function count(conditions, callback) { Model.distinct = function distinct(field, conditions, callback) { _checkContext(this, 'distinct'); - const mq = new this.Query({}, {}, this, this.collection); + const mq = new this.Query({}, {}, this, this.$__collection); if (typeof conditions === 'function') { callback = conditions; @@ -2336,7 +2439,7 @@ Model.where = function where(path, val) { _checkContext(this, 'where'); void val; // eslint - const mq = new this.Query({}, {}, this, this.collection).find({}); + const mq = new this.Query({}, {}, this, this.$__collection).find({}); return mq.where.apply(mq, arguments); }; @@ -2358,7 +2461,7 @@ Model.where = function where(path, val) { Model.$where = function $where() { _checkContext(this, '$where'); - const mq = new this.Query({}, {}, this, this.collection).find({}); + const mq = new this.Query({}, {}, this, this.$__collection).find({}); return mq.$where.apply(mq, arguments); }; @@ -2475,7 +2578,7 @@ Model.findOneAndUpdate = function(conditions, update, options, callback) { _decorateUpdateWithVersionKey(update, options, this.schema.options.versionKey); - const mq = new this.Query({}, {}, this, this.collection); + const mq = new this.Query({}, {}, this, this.$__collection); mq.select(fields); return mq.findOneAndUpdate(conditions, update, options, callback); @@ -2694,7 +2797,7 @@ Model.findOneAndDelete = function(conditions, options, callback) { options.select = undefined; } - const mq = new this.Query({}, {}, this, this.collection); + const mq = new this.Query({}, {}, this, this.$__collection); mq.select(fields); return mq.findOneAndDelete(conditions, options, callback); @@ -2812,7 +2915,7 @@ Model.findOneAndReplace = function(filter, replacement, options, callback) { options.select = undefined; } - const mq = new this.Query({}, {}, this, this.collection); + const mq = new this.Query({}, {}, this, this.$__collection); mq.select(fields); return mq.findOneAndReplace(filter, replacement, options, callback); @@ -2898,7 +3001,7 @@ Model.findOneAndRemove = function(conditions, options, callback) { options.select = undefined; } - const mq = new this.Query({}, {}, this, this.collection); + const mq = new this.Query({}, {}, this, this.$__collection); mq.select(fields); return mq.findOneAndRemove(conditions, options, callback); @@ -3011,8 +3114,7 @@ Model.create = function create(doc, options, callback) { args[0] != null && args[1] != null && args[0].session == null && - last.session != null && - last.session.constructor.name === 'ClientSession' && + getConstructorName(last.session) === 'ClientSession' && !this.schema.path('session')) { // Probably means the user is running into the common mistake of trying // to use a spread to specify options, see gh-7535 @@ -3033,7 +3135,7 @@ Model.create = function create(doc, options, callback) { args.forEach(doc => { toExecute.push(callback => { const Model = this.discriminators && doc[discriminatorKey] != null ? - this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this, doc[discriminatorKey]) : + this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) : this; if (Model == null) { throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` + @@ -3134,16 +3236,16 @@ Model.watch = function(pipeline, options) { _checkContext(this, 'watch'); const changeStreamThunk = cb => { - if (this.collection.buffer) { - this.collection.addQueue(() => { + if (this.$__collection.buffer) { + this.$__collection.addQueue(() => { if (this.closed) { return; } - const driverChangeStream = this.collection.watch(pipeline, options); + const driverChangeStream = this.$__collection.watch(pipeline, options); cb(null, driverChangeStream); }); } else { - const driverChangeStream = this.collection.watch(pipeline, options); + const driverChangeStream = this.$__collection.watch(pipeline, options); cb(null, driverChangeStream); } }; @@ -3273,7 +3375,7 @@ Model.$__insertMany = function(arr, options, callback) { // we have to execute callback at the nextTick to be compatible // with parallelLimit, as `results` variable has TDZ issue if we // execute the callback synchronously - return process.nextTick(() => callback(null, doc)); + return immediate(() => callback(null, doc)); } doc.validate({ __noPromise: true }, function(error) { if (error) { @@ -3313,8 +3415,8 @@ Model.$__insertMany = function(arr, options, callback) { return; } const docObjects = docAttributes.map(function(doc) { - if (doc.schema.options.versionKey) { - doc[doc.schema.options.versionKey] = 0; + if (doc.$__schema.options.versionKey) { + doc[doc.$__schema.options.versionKey] = 0; } if (doc.initializeTimestamps) { return doc.initializeTimestamps().toObject(internalToObjectOptions); @@ -3322,7 +3424,7 @@ Model.$__insertMany = function(arr, options, callback) { return doc.toObject(internalToObjectOptions); }); - _this.collection.insertMany(docObjects, options, function(error, res) { + _this.$__collection.insertMany(docObjects, options, function(error, res) { if (error) { // `writeErrors` is a property reported by the MongoDB driver, // just not if there's only 1 error. @@ -3333,8 +3435,24 @@ Model.$__insertMany = function(arr, options, callback) { // `insertedDocs` is a Mongoose-specific property const erroredIndexes = new Set(get(error, 'writeErrors', []).map(err => err.index)); + + let firstErroredIndex = -1; error.insertedDocs = docAttributes. - filter((doc, i) => !erroredIndexes.has(i)). + filter((doc, i) => { + const isErrored = erroredIndexes.has(i); + + if (ordered) { + if (firstErroredIndex > -1) { + return i < firstErroredIndex; + } + + if (isErrored) { + firstErroredIndex = i; + } + } + + return !isErrored; + }). map(function setIsNewForInsertedDoc(doc) { doc.$__reset(); _setIsNew(doc, false); @@ -3386,7 +3504,7 @@ function _setIsNew(doc, val) { doc.emit('isNew', val); doc.constructor.emit('isNew', val); - const subdocs = doc.$__getAllSubdocs(); + const subdocs = doc.$getAllSubdocs(); for (const subdoc of subdocs) { subdoc.isNew = val; } @@ -3501,7 +3619,7 @@ Model.bulkWrite = function(ops, options, callback) { return cb(null, getDefaultBulkwriteResult()); } - this.collection.bulkWrite(ops, options, (error, res) => { + this.$__collection.bulkWrite(ops, options, (error, res) => { if (error) { return cb(error); } @@ -3512,6 +3630,148 @@ Model.bulkWrite = function(ops, options, callback) { }, this.events); }; +/** + * takes an array of documents, gets the changes and inserts/updates documents in the database + * according to whether or not the document is new, or whether it has changes or not. + * + * `bulkSave` uses `bulkWrite` under the hood, so it's mostly useful when dealing with many documents (10K+) + * + * @param {[Document]} documents + * + */ +Model.bulkSave = function(documents) { + const preSavePromises = documents.map(buildPreSavePromise); + + const writeOperations = this.buildBulkWriteOperations(documents, { skipValidation: true }); + + let bulkWriteResultPromise; + return Promise.all(preSavePromises) + .then(() => bulkWriteResultPromise = this.bulkWrite(writeOperations)) + .then(() => documents.map(buildSuccessfulWriteHandlerPromise)) + .then(() => bulkWriteResultPromise) + .catch((err) => { + if (!get(err, 'writeErrors.length')) { + throw err; + } + return Promise.all( + documents.map((document) => { + const documentError = err.writeErrors.find(writeError => { + const writeErrorDocumentId = writeError.err.op._id || writeError.err.op.q._id; + return writeErrorDocumentId.toString() === document._id.toString(); + }); + + if (documentError == null) { + return buildSuccessfulWriteHandlerPromise(document); + } + }) + ).then(() => { + throw err; + }); + }); +}; + +function buildPreSavePromise(document) { + return new Promise((resolve, reject) => { + document.schema.s.hooks.execPre('save', document, (err) => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); +} + +function buildSuccessfulWriteHandlerPromise(document) { + return new Promise((resolve, reject) => { + handleSuccessfulWrite(document, resolve, reject); + }); +} + +function handleSuccessfulWrite(document, resolve, reject) { + if (document.isNew) { + _setIsNew(document, false); + } + + document.$__reset(); + document.schema.s.hooks.execPost('save', document, {}, (err) => { + if (err) { + reject(err); + return; + } + resolve(); + }); +} + +/** + * + * @param {[Document]} documents The array of documents to build write operations of + * @param {Object} options + * @param {Boolean} options.skipValidation defaults to `false`, when set to true, building the write operations will bypass validating the documents. + * @returns + */ +Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, options) { + if (!Array.isArray(documents)) { + throw new Error(`bulkSave expects an array of documents to be passed, received \`${documents}\` instead`); + } + + setDefaultOptions(); + + const writeOperations = documents.reduce((accumulator, document, i) => { + if (!options.skipValidation) { + if (!(document instanceof Document)) { + throw new Error(`documents.${i} was not a mongoose document, documents must be an array of mongoose documents (instanceof mongoose.Document).`); + } + const validationError = document.validateSync(); + if (validationError) { + throw validationError; + } + } + + const isANewDocument = document.isNew; + if (isANewDocument) { + accumulator.push({ + insertOne: { document } + }); + + return accumulator; + } + + const delta = document.$__delta(); + const isDocumentWithChanges = delta != null && !utils.isEmptyObject(delta[0]); + + if (isDocumentWithChanges) { + const where = document.$__where(delta[0]); + const changes = delta[1]; + + _applyCustomWhere(document, where); + + document.$__version(where, delta); + + accumulator.push({ + updateOne: { + filter: where, + update: changes + } + }); + + return accumulator; + } + + return accumulator; + }, []); + + return writeOperations; + + + function setDefaultOptions() { + options = options || {}; + if (options.skipValidation == null) { + options.skipValidation = false; + } + } +}; + /** * Shortcut for creating a new Document from existing raw data, pre-saved in the DB. * The document returned has no paths marked as modified initially. @@ -3655,7 +3915,7 @@ Model.update = function update(conditions, doc, options, callback) { * - `updateMany()` * * @param {Object} filter - * @param {Object} doc + * @param {Object|Array} update * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document @@ -3693,7 +3953,7 @@ Model.updateMany = function updateMany(conditions, doc, options, callback) { * - `updateOne()` * * @param {Object} filter - * @param {Object} doc + * @param {Object|Array} update * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document @@ -3881,7 +4141,7 @@ Model.mapReduce = function mapReduce(o, callback) { q = undefined; } - this.collection.mapReduce(null, null, o, (err, res) => { + this.$__collection.mapReduce(null, null, o, (err, res) => { if (err) { return cb(err); } @@ -4006,6 +4266,12 @@ Model.aggregate = function aggregate(pipeline, callback) { */ Model.validate = function validate(obj, pathsToValidate, context, callback) { + if ((arguments.length < 3) || (arguments.length === 3 && typeof arguments[2] === 'function')) { + // For convenience, if we're validating a document or an object, make `context` default to + // the model so users don't have to always pass `context`, re: gh-10132, gh-10346 + context = obj; + } + return this.db.base._promiseOrCallback(callback, cb => { const schema = this.schema; let paths = Object.keys(schema.paths); @@ -4161,7 +4427,7 @@ Model.geoSearch = function(conditions, options, callback) { // send the conditions in the options object options.search = conditions; - this.collection.geoHaystackSearch(options.near[0], options.near[1], options, (err, res) => { + this.$__collection.geoHaystackSearch(options.near[0], options.near[1], options, (err, res) => { if (err) { return cb(err); } @@ -4263,6 +4529,7 @@ Model.geoSearch = function(conditions, options, callback) { * @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. * @param {Number} [options.perDocumentLimit=null] For legacy reasons, `limit` with `populate()` may give incorrect results because it only executes a single query for every document being populated. If you set `perDocumentLimit`, Mongoose will ensure correct `limit` per document by executing a separate query for each document to `populate()`. For example, `.find().populate({ path: 'test', perDocumentLimit: 2 })` will execute 2 additional queries if `.find()` returns 2 documents. * @param {Object} [options.options=null] Additional options like `limit` and `lean`. + * @param {Function} [options.transform=null] Function that Mongoose will call on every populated document that allows you to transform the populated document. * @param {Function} [callback(err,doc)] Optional callback, executed upon completion. Receives `err` and the `doc(s)`. * @return {Promise} * @api public @@ -4280,7 +4547,6 @@ Model.populate = function(docs, paths, callback) { const cache = {}; callback = this.$handleCallbackError(callback); - return this.db.base._promiseOrCallback(callback, cb => { cb = this.$wrapCallback(cb); _populate(_this, docs, paths, cache, cb); @@ -4300,7 +4566,6 @@ Model.populate = function(docs, paths, callback) { function _populate(model, docs, paths, cache, callback) { let pending = paths.length; - if (paths.length === 0) { return callback(null, docs); } @@ -4332,13 +4597,11 @@ function populate(model, docs, options, callback) { if (!Array.isArray(docs)) { docs = [docs]; } - if (docs.length === 0 || docs.every(utils.isNullOrUndefined)) { return callback(); } const modelsMap = getModelsMapForPopulate(model, docs, options); - if (modelsMap instanceof MongooseError) { return immediate(function() { callback(modelsMap); @@ -4359,8 +4622,6 @@ function populate(model, docs, options, callback) { for (let i = 0; i < len; ++i) { const mod = modelsMap[i]; let select = mod.options.select; - const match = _formatMatch(mod.match); - let ids = utils.array.flatten(mod.ids, flatten); ids = utils.array.unique(ids); @@ -4380,29 +4641,7 @@ function populate(model, docs, options, callback) { } hasOne = true; - if (mod.foreignField.size === 1) { - const foreignField = Array.from(mod.foreignField)[0]; - const foreignSchemaType = mod.model.schema.path(foreignField); - if (foreignField !== '_id' || !match['_id']) { - ids = _filterInvalidIds(ids, foreignSchemaType, mod.options.skipInvalidIds); - match[foreignField] = { $in: ids }; - } - } else { - const $or = []; - if (Array.isArray(match.$or)) { - match.$and = [{ $or: match.$or }, { $or: $or }]; - delete match.$or; - } else { - match.$or = $or; - } - for (const foreignField of mod.foreignField) { - if (foreignField !== '_id' || !match['_id']) { - const foreignSchemaType = mod.model.schema.path(foreignField); - ids = _filterInvalidIds(ids, foreignSchemaType, mod.options.skipInvalidIds); - $or.push({ [foreignField]: { $in: ids } }); - } - } - } + const match = createPopulateQueryFilter(ids, mod.match, mod.foreignField, mod.model, mod.options.skipInvalidIds); if (assignmentOpts.excludeId) { // override the exclusion from the query so we can use the _id @@ -4422,10 +4661,8 @@ function populate(model, docs, options, callback) { } else if (mod.options.limit != null) { assignmentOpts.originalLimit = mod.options.limit; } - params.push([mod, match, select, assignmentOpts, _next]); } - if (!hasOne) { // If no models to populate but we have a nested populate, // keep trying, re: gh-8946 @@ -4441,7 +4678,6 @@ function populate(model, docs, options, callback) { for (const arr of params) { _execPopulateQuery.apply(null, arr); } - function _next(err, valsFromDb) { if (err != null) { return callback(err, null); @@ -4475,7 +4711,6 @@ function populate(model, docs, options, callback) { function _execPopulateQuery(mod, match, select, assignmentOpts, callback) { const subPopulate = utils.clone(mod.options.populate); - const queryOptions = Object.assign({ skip: mod.options.skip, limit: mod.options.limit, @@ -4522,10 +4757,10 @@ function _execPopulateQuery(mod, match, select, assignmentOpts, callback) { if (err != null) { return callback(err); } + for (const val of docs) { leanPopulateMap.set(val, mod.model); } - callback(null, docs); }); } @@ -4548,7 +4783,6 @@ function _assign(model, vals, mod, assignmentOpts) { // Clone because `assignRawDocsToIdStructure` will mutate the array const allIds = utils.clone(mod.allIds); - // optimization: // record the document positions as returned by // the query result. @@ -4557,10 +4791,11 @@ function _assign(model, vals, mod, assignmentOpts) { if (val == null) { continue; } + for (const foreignField of mod.foreignField) { _val = utils.getValue(foreignField, val); if (Array.isArray(_val)) { - _val = utils.array.flatten(_val); + _val = utils.array.unique(utils.array.flatten(_val)); for (let __val of _val) { if (__val instanceof Document) { @@ -4594,7 +4829,12 @@ function _assign(model, vals, mod, assignmentOpts) { if (Array.isArray(rawDocs[key])) { rawDocs[key].push(val); rawOrder[key].push(i); - } else { + } else if (isVirtual || + rawDocs[key].constructor !== val.constructor || + String(rawDocs[key]._id) !== String(val._id)) { + // May need to store multiple docs with the same id if there's multiple models + // if we have discriminators or a ref function. But avoid converting to an array + // if we have multiple queries on the same model because of `perDocumentLimit` re: gh-9906 rawDocs[key] = [rawDocs[key], val]; rawOrder[key] = [rawOrder[key], i]; } @@ -4632,41 +4872,6 @@ function _assign(model, vals, mod, assignmentOpts) { }); } -/*! - * Optionally filter out invalid ids that don't conform to foreign field's schema - * to avoid cast errors (gh-7706) - */ - -function _filterInvalidIds(ids, foreignSchemaType, skipInvalidIds) { - ids = ids.filter(v => !(v instanceof SkipPopulateValue)); - if (!skipInvalidIds) { - return ids; - } - return ids.filter(id => { - try { - foreignSchemaType.cast(id); - return true; - } catch (err) { - return false; - } - }); -} - -/*! - * Format `mod.match` given that it may be an array that we need to $or if - * the client has multiple docs with match functions - */ - -function _formatMatch(match) { - if (Array.isArray(match)) { - if (match.length > 1) { - return { $or: [].concat(match.map(m => Object.assign({}, m))) }; - } - return Object.assign({}, match[0]); - } - return Object.assign({}, match); -} - /*! * Compiler utility. * @@ -4709,7 +4914,7 @@ Model.compile = function compile(name, schema, collectionName, connection, base) // If discriminator key is set, use the discriminator instead (gh-7586) const Discriminator = model.discriminators[doc[discriminatorKey]] || - getDiscriminatorByValue(model, doc[discriminatorKey]); + getDiscriminatorByValue(model.discriminators, doc[discriminatorKey]); if (Discriminator != null) { return new Discriminator(doc, fields, skipId); } @@ -4744,9 +4949,12 @@ Model.compile = function compile(name, schema, collectionName, connection, base) const collectionOptions = { schemaUserProvidedOptions: _userProvidedOptions, capped: schema.options.capped, - autoCreate: schema.options.autoCreate, - Promise: model.base.Promise + Promise: model.base.Promise, + modelName: name }; + if (schema.options.autoCreate !== void 0) { + collectionOptions.autoCreate = schema.options.autoCreate; + } model.prototype.collection = connection.collection( collectionName, @@ -4760,8 +4968,9 @@ Model.compile = function compile(name, schema, collectionName, connection, base) applyHooks(model, schema); applyStaticHooks(model, schema.s.hooks, schema.statics); - model.schema = model.prototype.schema; + model.schema = model.prototype.$__schema; model.collection = model.prototype.collection; + model.$__collection = model.collection; // Create custom query constructor model.Query = function() { @@ -4826,13 +5035,13 @@ Model.__subclass = function subclass(conn, schema, collection) { const s = schema && typeof schema !== 'string' ? schema - : _this.prototype.schema; + : _this.prototype.$__schema; const options = s.options || {}; const _userProvidedOptions = s._userProvidedOptions || {}; if (!collection) { - collection = _this.prototype.schema.get('collection') || + collection = _this.prototype.$__schema.get('collection') || utils.toCollectionName(_this.modelName, this.base.pluralize()); } @@ -4844,6 +5053,7 @@ Model.__subclass = function subclass(conn, schema, collection) { Model.prototype.collection = conn.collection(collection, collectionOptions); Model.prototype[modelCollectionSymbol] = Model.prototype.collection; Model.collection = Model.prototype.collection; + Model.$__collection = Model.collection; // Errors handled internally, so ignore Model.init(() => {}); return Model; @@ -4859,11 +5069,13 @@ Model.$handleCallbackError = function(callback) { const _this = this; return function() { - try { - callback.apply(null, arguments); - } catch (error) { - _this.emit('error', error); - } + immediate(() => { + try { + callback.apply(null, arguments); + } catch (error) { + _this.emit('error', error); + } + }); }; }; diff --git a/lib/options.js b/lib/options.js index 8e9fc2941ef..4826e59fdc5 100644 --- a/lib/options.js +++ b/lib/options.js @@ -10,5 +10,6 @@ exports.internalToObjectOptions = { getters: false, _skipDepopulateTopLevel: true, depopulate: true, - flattenDecimals: false + flattenDecimals: false, + useProjection: false }; diff --git a/lib/options/SchemaArrayOptions.js b/lib/options/SchemaArrayOptions.js index 87544944713..e3734ae4992 100644 --- a/lib/options/SchemaArrayOptions.js +++ b/lib/options/SchemaArrayOptions.js @@ -50,7 +50,7 @@ Object.defineProperty(SchemaArrayOptions.prototype, 'enum', opts); * @instance */ -Object.defineProperty(SchemaArrayOptions.prototype, 'enum', opts); +Object.defineProperty(SchemaArrayOptions.prototype, 'of', opts); /*! * ignore diff --git a/lib/plugins/removeSubdocs.js b/lib/plugins/removeSubdocs.js index 44b2ea62790..c2756fc5374 100644 --- a/lib/plugins/removeSubdocs.js +++ b/lib/plugins/removeSubdocs.js @@ -15,13 +15,13 @@ module.exports = function(schema) { } const _this = this; - const subdocs = this.$__getAllSubdocs(); + const subdocs = this.$getAllSubdocs(); each(subdocs, function(subdoc, cb) { subdoc.$__remove(cb); }, function(error) { if (error) { - return _this.schema.s.hooks.execPost('remove:error', _this, [_this], { error: error }, function(error) { + return _this.$__schema.s.hooks.execPost('remove:error', _this, [_this], { error: error }, function(error) { next(error); }); } diff --git a/lib/plugins/saveSubdocs.js b/lib/plugins/saveSubdocs.js index c0a3144e778..fcc73d88a71 100644 --- a/lib/plugins/saveSubdocs.js +++ b/lib/plugins/saveSubdocs.js @@ -15,7 +15,7 @@ module.exports = function(schema) { } const _this = this; - const subdocs = this.$__getAllSubdocs(); + const subdocs = this.$getAllSubdocs(); if (!subdocs.length) { next(); @@ -23,12 +23,12 @@ module.exports = function(schema) { } each(subdocs, function(subdoc, cb) { - subdoc.schema.s.hooks.execPre('save', subdoc, function(err) { + subdoc.$__schema.s.hooks.execPre('save', subdoc, function(err) { cb(err); }); }, function(error) { if (error) { - return _this.schema.s.hooks.execPost('save:error', _this, [_this], { error: error }, function(error) { + return _this.$__schema.s.hooks.execPost('save:error', _this, [_this], { error: error }, function(error) { next(error); }); } @@ -43,7 +43,7 @@ module.exports = function(schema) { } const _this = this; - const subdocs = this.$__getAllSubdocs(); + const subdocs = this.$getAllSubdocs(); if (!subdocs.length) { next(); @@ -51,12 +51,12 @@ module.exports = function(schema) { } each(subdocs, function(subdoc, cb) { - subdoc.schema.s.hooks.execPost('save', subdoc, [subdoc], function(err) { + subdoc.$__schema.s.hooks.execPost('save', subdoc, [subdoc], function(err) { cb(err); }); }, function(error) { if (error) { - return _this.schema.s.hooks.execPost('save:error', _this, [_this], { error: error }, function(error) { + return _this.$__schema.s.hooks.execPost('save:error', _this, [_this], { error: error }, function(error) { next(error); }); } diff --git a/lib/plugins/sharding.js b/lib/plugins/sharding.js index 560053ed30c..020ec06c633 100644 --- a/lib/plugins/sharding.js +++ b/lib/plugins/sharding.js @@ -56,7 +56,7 @@ module.exports.storeShard = storeShard; function storeShard() { // backwards compat - const key = this.schema.options.shardKey || this.schema.options.shardkey; + const key = this.$__schema.options.shardKey || this.$__schema.options.shardkey; if (!utils.isPOJO(key)) { return; } diff --git a/lib/plugins/trackTransaction.js b/lib/plugins/trackTransaction.js index 410a596f3bc..30ded8785f2 100644 --- a/lib/plugins/trackTransaction.js +++ b/lib/plugins/trackTransaction.js @@ -18,8 +18,8 @@ module.exports = function trackTransaction(schema) { if (this.isNew) { initialState.isNew = true; } - if (this.schema.options.versionKey) { - initialState.versionKey = this.get(this.schema.options.versionKey); + if (this.$__schema.options.versionKey) { + initialState.versionKey = this.get(this.$__schema.options.versionKey); } initialState.modifiedPaths = new Set(Object.keys(this.$__.activePaths.states.modify)); diff --git a/lib/plugins/validateBeforeSave.js b/lib/plugins/validateBeforeSave.js index 4635de1ccfb..c06d5e6e2c4 100644 --- a/lib/plugins/validateBeforeSave.js +++ b/lib/plugins/validateBeforeSave.js @@ -21,7 +21,7 @@ module.exports = function(schema) { if (hasValidateBeforeSaveOption) { shouldValidate = !!options.validateBeforeSave; } else { - shouldValidate = this.schema.options.validateBeforeSave; + shouldValidate = this.$__schema.options.validateBeforeSave; } // Validate @@ -33,7 +33,7 @@ module.exports = function(schema) { { validateModifiedOnly: options.validateModifiedOnly } : null; this.validate(validateOptions, function(error) { - return _this.schema.s.hooks.execPost('save:error', _this, [_this], { error: error }, function(error) { + return _this.$__schema.s.hooks.execPost('save:error', _this, [_this], { error: error }, function(error) { _this.$op = 'save'; next(error); }); diff --git a/lib/query.js b/lib/query.js index ce49c4cdb14..65817f65fc0 100644 --- a/lib/query.js +++ b/lib/query.js @@ -22,10 +22,13 @@ const promiseOrCallback = require('./helpers/promiseOrCallback'); const getDiscriminatorByValue = require('./helpers/discriminator/getDiscriminatorByValue'); const hasDollarKeys = require('./helpers/query/hasDollarKeys'); const helpers = require('./queryhelpers'); +const immediate = require('./helpers/immediate'); +const isExclusive = require('./helpers/projection/isExclusive'); const isInclusive = require('./helpers/projection/isInclusive'); const mquery = require('mquery'); const parseProjection = require('./helpers/projection/parseProjection'); const removeUnusedArrayFilters = require('./helpers/update/removeUnusedArrayFilters'); +const sanitizeProjection = require('./helpers/query/sanitizeProjection'); const selectPopulatedFields = require('./helpers/query/selectPopulatedFields'); const setDefaultsOnInsert = require('./helpers/setDefaultsOnInsert'); const slice = require('sliced'); @@ -84,6 +87,7 @@ function Query(conditions, options, model, collection) { this.schema = model.schema; } + // this is needed because map reduce returns a model that can be queried, but // all of the queries on said model should be lean if (this.model && this.model._mapreduce) { @@ -945,7 +949,6 @@ Query.prototype.projection = function(arg) { Query.prototype.select = function select() { let arg = arguments[0]; if (!arg) return this; - let i; if (arguments.length !== 1) { throw new Error('Invalid select: select only takes 1 argument'); @@ -955,14 +958,26 @@ Query.prototype.select = function select() { const fields = this._fields || (this._fields = {}); const userProvidedFields = this._userProvidedFields || (this._userProvidedFields = {}); + let sanitizeProjection = undefined; + if (this.model != null && utils.hasUserDefinedProperty(this.model.db.options, 'sanitizeProjection')) { + sanitizeProjection = this.model.db.options.sanitizeProjection; + } else if (this.model != null && utils.hasUserDefinedProperty(this.model.base.options, 'sanitizeProjection')) { + sanitizeProjection = this.model.base.options.sanitizeProjection; + } else { + sanitizeProjection = this._mongooseOptions.sanitizeProjection; + } arg = parseProjection(arg); if (utils.isObject(arg)) { const keys = Object.keys(arg); - for (i = 0; i < keys.length; ++i) { - fields[keys[i]] = arg[keys[i]]; - userProvidedFields[keys[i]] = arg[keys[i]]; + for (let i = 0; i < keys.length; ++i) { + let value = arg[keys[i]]; + if (typeof value === 'string' && sanitizeProjection) { + value = 1; + } + fields[keys[i]] = value; + userProvidedFields[keys[i]] = value; } return this; } @@ -1032,7 +1047,7 @@ Query.prototype.select = function select() { * // read from secondaries with matching tags * new Query().read('s', [{ dc:'sf', s: 1 },{ dc:'ma', s: 2 }]) * - * Read more about how to use read preferrences [here](http://docs.mongodb.org/manual/applications/replication/#read-preference) and [here](http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences). + * Read more about how to use read preferences [here](http://docs.mongodb.org/manual/applications/replication/#read-preference) and [here](http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences). * * @method read * @memberOf Query @@ -1082,6 +1097,53 @@ Query.prototype.session = function session(v) { return this; }; +/** + * Sets the 3 write concern parameters for this query: + * + * - `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`](/docs/api.html#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. + * + * This option is only valid for operations that write to the database: + * + * - `deleteOne()` + * - `deleteMany()` + * - `findOneAndDelete()` + * - `findOneAndReplace()` + * - `findOneAndUpdate()` + * - `remove()` + * - `update()` + * - `updateOne()` + * - `updateMany()` + * + * Defaults to the schema's [`writeConcern` option](/docs/guide.html#writeConcern) + * + * ####Example: + * + * // The 'majority' option means the `deleteOne()` promise won't resolve + * // until the `deleteOne()` has propagated to the majority of the replica set + * await mongoose.model('Person'). + * deleteOne({ name: 'Ned Stark' }). + * writeConcern({ w: 'majority' }); + * + * @method writeConcern + * @memberOf Query + * @instance + * @param {Object} writeConcern the write concern value to set + * @see mongodb https://mongodb.github.io/node-mongodb-native/3.1/api/global.html#WriteConcern + * @return {Query} this + * @api public + */ + +Query.prototype.writeConcern = function writeConcern(val) { + if (val == null) { + delete this.options.writeConcern; + return this; + } + this.options.writeConcern = val; + return this; +}; + /** * Sets the specified number of `mongod` servers, or tag set of `mongod` servers, * that must acknowledge this write before this write is considered successful. @@ -1120,7 +1182,11 @@ Query.prototype.w = function w(val) { if (val == null) { delete this.options.w; } - this.options.w = val; + if (this.options.writeConcern != null) { + this.options.writeConcern.w = val; + } else { + this.options.w = val; + } return this; }; @@ -1158,7 +1224,11 @@ Query.prototype.j = function j(val) { if (val == null) { delete this.options.j; } - this.options.j = val; + if (this.options.writeConcern != null) { + this.options.writeConcern.j = val; + } else { + this.options.j = val; + } return this; }; @@ -1204,7 +1274,11 @@ Query.prototype.wtimeout = function wtimeout(ms) { if (ms == null) { delete this.options.wtimeout; } - this.options.wtimeout = ms; + if (this.options.writeConcern != null) { + this.options.writeConcern.wtimeout = ms; + } else { + this.options.wtimeout = ms; + } return this; }; @@ -1284,12 +1358,13 @@ Query.prototype.getOptions = function() { * - [sort](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsort(\)%7D%7D) * - [limit](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Blimit%28%29%7D%7D) * - [skip](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bskip%28%29%7D%7D) - * - [maxscan](https://docs.mongodb.org/v3.2/reference/operator/meta/maxScan/#metaOp._S_maxScan) + * - [allowDiskUse](https://docs.mongodb.com/manual/reference/method/cursor.allowDiskUse/) * - [batchSize](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7BbatchSize%28%29%7D%7D) - * - [comment](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24comment) - * - [snapshot](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsnapshot%28%29%7D%7D) * - [readPreference](http://docs.mongodb.org/manual/applications/replication/#read-preference) * - [hint](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24hint) + * - [comment](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24comment) + * - [snapshot](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsnapshot%28%29%7D%7D) + * - [maxscan](https://docs.mongodb.org/v3.2/reference/operator/meta/maxScan/#metaOp._S_maxScan) * * The following options are only for write operations: `update()`, `updateOne()`, `updateMany()`, `replaceOne()`, `findOneAndUpdate()`, and `findByIdAndUpdate()`: * @@ -1305,6 +1380,7 @@ Query.prototype.getOptions = function() { * - [lean](./api.html#query_Query-lean) * - [populate](/docs/populate.html) * - [projection](/docs/api/query.html#query_Query-projection) + * - sanitizeProjection * * The following options are only for all operations **except** `update()`, `updateOne()`, `updateMany()`, `remove()`, `deleteOne()`, and `deleteMany()`: * @@ -1338,7 +1414,6 @@ Query.prototype.setOptions = function(options, overwrite) { } return this; } - if (options == null) { return this; } @@ -1371,6 +1446,19 @@ Query.prototype.setOptions = function(options, overwrite) { this._mongooseOptions.overwriteDiscriminatorKey = options.overwriteDiscriminatorKey; delete options.overwriteDiscriminatorKey; } + if ('sanitizeProjection' in options) { + if (options.sanitizeProjection && !this._mongooseOptions.sanitizeProjection) { + sanitizeProjection(this._fields); + } + + this._mongooseOptions.sanitizeProjection = options.sanitizeProjection; + delete options.sanitizeProjection; + } + + if ('defaults' in options) { + this._mongooseOptions.defaults = options.defaults; + // deleting options.defaults will cause 7287 to fail + } return Query.base.setOptions.call(this, options); }; @@ -1381,7 +1469,7 @@ Query.prototype.setOptions = function(options, overwrite) { * query result. This method is useful for determining what index your queries * use. * - * Calling `query.explain(v)` is equivalent to `query.setOption({ explain: v })` + * Calling `query.explain(v)` is equivalent to `query.setOptions({ explain: v })` * * ####Example: * @@ -1397,9 +1485,43 @@ Query.prototype.setOptions = function(options, overwrite) { Query.prototype.explain = function(verbose) { if (arguments.length === 0) { this.options.explain = true; - return this; + } else if (verbose === false) { + delete this.options.explain; + } else { + this.options.explain = verbose; + } + return this; +}; + +/** + * Sets the [`allowDiskUse` option](https://docs.mongodb.com/manual/reference/method/cursor.allowDiskUse/), + * which allows the MongoDB server to use more than 100 MB for this query's `sort()`. This option can + * let you work around `QueryExceededMemoryLimitNoDiskUseAllowed` errors from the MongoDB server. + * + * Note that this option requires MongoDB server >= 4.4. Setting this option is a no-op for MongoDB 4.2 + * and earlier. + * + * Calling `query.allowDiskUse(v)` is equivalent to `query.setOptions({ allowDiskUse: v })` + * + * ####Example: + * + * await query.find().sort({ name: 1 }).allowDiskUse(true); + * // Equivalent: + * await query.find().sort({ name: 1 }).allowDiskUse(); + * + * @param {Boolean} [v] Enable/disable `allowDiskUse`. If called with 0 arguments, sets `allowDiskUse: true` + * @return {Query} this + * @api public + */ + +Query.prototype.allowDiskUse = function(v) { + if (arguments.length === 0) { + this.options.allowDiskUse = true; + } else if (v === false) { + delete this.options.allowDiskUse; + } else { + this.options.allowDiskUse = v; } - this.options.explain = verbose; return this; }; @@ -1408,7 +1530,7 @@ Query.prototype.explain = function(verbose) { * option. This will tell the MongoDB server to abort if the query or write op * has been running for more than `ms` milliseconds. * - * Calling `query.maxTimeMS(v)` is equivalent to `query.setOption({ maxTimeMS: v })` + * Calling `query.maxTimeMS(v)` is equivalent to `query.setOptions({ maxTimeMS: v })` * * ####Example: * @@ -1611,7 +1733,6 @@ Query.prototype._updateForExec = function() { Query.prototype._optionsForExec = function(model) { const options = utils.clone(this.options); - delete options.populate; model = model || this.model; @@ -1635,7 +1756,20 @@ Query.prototype._optionsForExec = function(model) { if (options.upsert !== void 0) { options.upsert = !!options.upsert; } - + if (options.writeConcern) { + if (options.j) { + options.writeConcern.j = options.j; + delete options.j; + } + if (options.w) { + options.writeConcern.w = options.w; + delete options.w; + } + if (options.wtimeout) { + options.writeConcern.wtimeout = options.wtimeout; + delete options.wtimeout; + } + } return options; }; @@ -2154,7 +2288,6 @@ Query.prototype._findOne = wrapThunk(function(callback) { Query.prototype.findOne = function(conditions, projection, options, callback) { this.op = 'findOne'; - if (typeof conditions === 'function') { callback = conditions; conditions = null; @@ -2194,7 +2327,6 @@ Query.prototype.findOne = function(conditions, projection, options, callback) { } this.exec(callback); - return this; }; @@ -2839,7 +2971,7 @@ function completeOne(model, doc, res, options, fields, userProvidedFields, pop, return null; } - const casted = helpers.createModel(model, doc, fields, userProvidedFields); + const casted = helpers.createModel(model, doc, fields, userProvidedFields, options); try { casted.init(doc, opts, _init); } catch (error) { @@ -2848,21 +2980,25 @@ function completeOne(model, doc, res, options, fields, userProvidedFields, pop, function _init(err) { if (err) { - return process.nextTick(() => callback(err)); + return immediate(() => callback(err)); } if (options.rawResult) { if (doc && casted) { - casted.$session(options.session); + if (options.session != null) { + casted.$session(options.session); + } res.value = casted; } else { res.value = null; } - return process.nextTick(() => callback(null, res)); + return immediate(() => callback(null, res)); + } + if (options.session != null) { + casted.$session(options.session); } - casted.$session(options.session); - process.nextTick(() => callback(null, casted)); + immediate(() => callback(null, casted)); } } @@ -2998,7 +3134,7 @@ Query.prototype.findOneAndUpdate = function(criteria, doc, options, callback) { const returnOriginal = get(this, 'model.base.options.returnOriginal'); - if (options.returnOriginal == null && returnOriginal != null) { + if (options.new == null && options.returnDocument == null && options.returnOriginal == null && returnOriginal != null) { options.returnOriginal = returnOriginal; } @@ -3324,11 +3460,11 @@ Query.prototype.findOneAndReplace = function(filter, replacement, options, callb options = options || {}; const returnOriginal = get(this, 'model.base.options.returnOriginal'); - if (options.returnOriginal == null && returnOriginal != null) { + if (options.new == null && options.returnDocument == null && options.returnOriginal == null && returnOriginal != null) { options.returnOriginal = returnOriginal; } - this.setOptions(options); + this.setOptions({ overwrite: true }); if (!callback) { return this; @@ -3356,7 +3492,7 @@ Query.prototype._findOneAndReplace = wrapThunk(function(callback) { const filter = this._conditions; const options = this._optionsForExec(); - convertNewToReturnOriginal(options); + convertNewToReturnDocument(options); let fields = null; let castedDoc = new this.model(this._update, null, true); @@ -3398,11 +3534,15 @@ Query.prototype._findOneAndReplace = wrapThunk(function(callback) { * compat. */ -function convertNewToReturnOriginal(options) { +function convertNewToReturnDocument(options) { if ('new' in options) { - options.returnOriginal = !options['new']; + options.returnDocument = options['new'] ? 'after' : 'before'; delete options['new']; } + if ('returnOriginal' in options) { + options.returnDocument = options['returnOriginal'] ? 'before' : 'after'; + delete options['returnOriginal']; + } } /*! @@ -3476,7 +3616,7 @@ Query.prototype._findAndModify = function(type, callback) { if (type === 'remove') { opts.remove = true; } else { - if (!('new' in opts) && !('returnOriginal' in opts)) { + if (!('new' in opts) && !('returnOriginal' in opts) && !('returnDocument' in opts)) { opts.new = false; } if (!('upsert' in opts)) { @@ -3557,7 +3697,7 @@ Query.prototype._findAndModify = function(type, callback) { if (useFindAndModify === false) { // Bypass mquery const collection = _this._collection.collection; - convertNewToReturnOriginal(opts); + convertNewToReturnDocument(opts); if (type === 'remove') { collection.findOneAndDelete(castedQuery, opts, _wrapThunkCallback(_this, function(error, res) { @@ -3637,7 +3777,7 @@ const _legacyFindAndModify = util.deprecate(function(filter, update, opts, cb) { collection.collection._findAndModify(filter, sort, update, opts, _cb); }, 'Mongoose: `findOneAndUpdate()` and `findOneAndDelete()` without the ' + '`useFindAndModify` option set to false are deprecated. See: ' + - 'https://mongoosejs.com/docs/deprecations.html#findandmodify'); + 'https://mongoosejs.com/docs/5.x/docs/deprecations.html#findandmodify'); /*! * Override mquery.prototype._mergeUpdate to handle mongoose objects in @@ -3799,7 +3939,7 @@ Query.prototype.validate = function validate(castedDoc, options, isOverwriting, updateValidators(this, this.model.schema, castedDoc, options, cb); } } catch (err) { - process.nextTick(function() { + immediate(function() { cb(err); }); } @@ -3997,7 +4137,7 @@ Query.prototype.update = function(conditions, doc, options, callback) { * - `updateMany()` * * @param {Object} [filter] - * @param {Object} [doc] the update command + * @param {Object|Array} [update] the update command * @param {Object} [options] * @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.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. @@ -4063,7 +4203,7 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) { * - `updateOne()` * * @param {Object} [filter] - * @param {Object} [doc] the update command + * @param {Object|Array} [update] the update command * @param {Object} [options] * @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.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. @@ -4440,6 +4580,8 @@ function _wrapThunkCallback(query, cb) { * Executes the query returning a `Promise` which will be * resolved with either the doc(s) or rejected with the error. * + * More about [`then()` in JavaScript](https://masteringjs.io/tutorials/fundamentals/then). + * * @param {Function} [resolve] * @param {Function} [reject] * @return {Promise} @@ -4455,6 +4597,8 @@ Query.prototype.then = function(resolve, reject) { * resolved with either the doc(s) or rejected with the error. * Like `.then()`, but only takes a rejection handler. * + * More about [Promise `catch()` in JavaScript](https://masteringjs.io/tutorials/fundamentals/catch). + * * @param {Function} [reject] * @return {Promise} * @api public @@ -4464,20 +4608,54 @@ Query.prototype.catch = function(reject) { return this.exec().then(null, reject); }; -/*! - * ignore +/** + * Add pre [middleware](/docs/middleware.html) to this query instance. Doesn't affect + * other queries. + * + * ####Example: + * + * const q1 = Question.find({ answer: 42 }); + * q1.pre(function middleware() { + * console.log(this.getFilter()); + * }); + * await q1.exec(); // Prints "{ answer: 42 }" + * + * // Doesn't print anything, because `middleware()` is only + * // registered on `q1`. + * await Question.find({ answer: 42 }); + * + * @param {Function} fn + * @return {Promise} + * @api public */ -Query.prototype._pre = function(fn) { +Query.prototype.pre = function(fn) { this._hooks.pre('exec', fn); return this; }; -/*! - * ignore +/** + * Add post [middleware](/docs/middleware.html) to this query instance. Doesn't affect + * other queries. + * + * ####Example: + * + * const q1 = Question.find({ answer: 42 }); + * q1.post(function middleware() { + * console.log(this.getFilter()); + * }); + * await q1.exec(); // Prints "{ answer: 42 }" + * + * // Doesn't print anything, because `middleware()` is only + * // registered on `q1`. + * await Question.find({ answer: 42 }); + * + * @param {Function} fn + * @return {Promise} + * @api public */ -Query.prototype._post = function(fn) { +Query.prototype.post = function(fn) { this._hooks.post('exec', fn); return this; }; @@ -4534,7 +4712,7 @@ Query.prototype._castUpdate = function _castUpdate(obj, overwrite) { typeof filter[schema.options.discriminatorKey] !== 'object' && schema.discriminators != null) { const discriminatorValue = filter[schema.options.discriminatorKey]; - const byValue = getDiscriminatorByValue(this.model, discriminatorValue); + const byValue = getDiscriminatorByValue(this.model.discriminators, discriminatorValue); schema = schema.discriminators[discriminatorValue] || (byValue && byValue.schema) || schema; @@ -4545,7 +4723,8 @@ Query.prototype._castUpdate = function _castUpdate(obj, overwrite) { strict: strict, omitUndefined, useNestedStrict: useNestedStrict, - upsert: upsert + upsert: upsert, + arrayFilters: this.options.arrayFilters }, this, this._conditions); }; @@ -4618,6 +4797,7 @@ function castDoc(query, overwrite) { * @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.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://docs.mongodb.com/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 ./populate.html * @see Query#select #query_Query-select @@ -4678,7 +4858,6 @@ Query.prototype.populate = function() { pop[populateOptions.path] = populateOptions; } - return this; }; @@ -4748,11 +4927,10 @@ Query.prototype.cast = function(model, obj) { obj || (obj = this._conditions); model = model || this.model; - const discriminatorKey = model.schema.options.discriminatorKey; if (obj != null && obj.hasOwnProperty(discriminatorKey)) { - model = getDiscriminatorByValue(model, obj[discriminatorKey]) || model; + model = getDiscriminatorByValue(model.discriminators, obj[discriminatorKey]) || model; } try { @@ -4846,7 +5024,7 @@ Query.prototype._applyPaths = function applyPaths() { } if (_selectPopulatedPaths) { - selectPopulatedFields(this); + selectPopulatedFields(this._fields, this._userProvidedFields, this._mongooseOptions.populate); } }; @@ -4950,7 +5128,7 @@ Query.prototype.maxscan = Query.base.maxScan; Query.prototype.tailable = function(val, opts) { // we need to support the tailable({ awaitdata : true }) as well as the // tailable(true, {awaitdata :true}) syntax that mquery does not support - if (val && val.constructor.name === 'Object') { + if (val != null && typeof val.constructor === 'function' && val.constructor.name === 'Object') { opts = val; val = true; } @@ -4960,7 +5138,7 @@ Query.prototype.tailable = function(val, opts) { } if (opts && typeof opts === 'object') { - for (const key in opts) { + for (const key of Object.keys(opts)) { if (key === 'awaitdata') { // For backwards compatibility this.options[key] = !!opts[key]; @@ -5307,11 +5485,11 @@ Query.prototype.center = Query.base.circle; */ Query.prototype.centerSphere = function() { - if (arguments[0] && arguments[0].constructor.name === 'Object') { + if (arguments[0] != null && typeof arguments[0].constructor === 'function' && arguments[0].constructor.name === 'Object') { arguments[0].spherical = true; } - if (arguments[1] && arguments[1].constructor.name === 'Object') { + if (arguments[1] != null && typeof arguments[1].constructor === 'function' && arguments[1].constructor.name === 'Object') { arguments[1].spherical = true; } @@ -5362,22 +5540,24 @@ Query.prototype.selectedInclusively = function selectedInclusively() { */ Query.prototype.selectedExclusively = function selectedExclusively() { - if (!this._fields) { - return false; - } + return isExclusive(this._fields); +}; - const keys = Object.keys(this._fields); - for (const key of keys) { - if (key === '_id') { - continue; - } - if (this._fields[key] === 0 || this._fields[key] === false) { - return true; - } - } +/** + * The model this query is associated with. + * + * #### Example: + * + * const q = MyModel.find(); + * q.model === MyModel; // true + * + * @api public + * @property model + * @memberOf Query + * @instance + */ - return false; -}; +Query.prototype.model; /*! * Export diff --git a/lib/queryhelpers.js b/lib/queryhelpers.js index 26c2386b7fc..7fdebdb6303 100644 --- a/lib/queryhelpers.js +++ b/lib/queryhelpers.js @@ -31,6 +31,10 @@ exports.preparePopulationOptions = function preparePopulationOptions(query, opti forEach(makeLean(options.lean)); } + pop.forEach(opts => { + opts._localModel = query.model; + }); + return pop; }; @@ -71,6 +75,9 @@ exports.preparePopulationOptionsMQ = function preparePopulationOptionsMQ(query, pop.forEach(p => { p._queryProjection = projection; }); + pop.forEach(opts => { + opts._localModel = query.model; + }); return pop; }; @@ -85,7 +92,7 @@ exports.preparePopulationOptionsMQ = function preparePopulationOptionsMQ(query, * * @return {Document} */ -exports.createModel = function createModel(model, doc, fields, userProvidedFields) { +exports.createModel = function createModel(model, doc, fields, userProvidedFields, options) { model.hooks.execPreSync('createModel', doc); const discriminatorMapping = model.schema ? model.schema.discriminatorMapping : @@ -97,18 +104,22 @@ exports.createModel = function createModel(model, doc, fields, userProvidedField const value = doc[key]; if (key && value && model.discriminators) { - const discriminator = model.discriminators[value] || getDiscriminatorByValue(model, value); + const discriminator = model.discriminators[value] || getDiscriminatorByValue(model.discriminators, value); if (discriminator) { const _fields = clone(userProvidedFields); exports.applyPaths(_fields, discriminator.schema); return new discriminator(undefined, _fields, true); } } - + if (typeof options === 'undefined') { + options = {}; + options.defaults = true; + } return new model(undefined, fields, { skipId: true, isNew: false, - willInit: true + willInit: true, + defaults: options.defaults }); }; @@ -199,7 +210,11 @@ exports.applyPaths = function applyPaths(fields, schema) { schema.eachPath(function(path, type) { if (prefix) path = prefix + '.' + path; - const addedPath = analyzePath(path, type); + let addedPath = analyzePath(path, type); + // arrays + if (addedPath == null && type.$isMongooseArray && !type.$isMongooseDocumentArray) { + addedPath = analyzePath(path, type.caster); + } if (addedPath != null) { addedPaths.push(addedPath); } diff --git a/lib/schema.js b/lib/schema.js index af350ffbaa9..895e452a36c 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -14,6 +14,7 @@ const VirtualType = require('./virtualtype'); const addAutoId = require('./helpers/schema/addAutoId'); const arrayParentSymbol = require('./helpers/symbols').arrayParentSymbol; const get = require('./helpers/get'); +const getConstructorName = require('./helpers/getConstructorName'); const getIndexes = require('./helpers/schema/getIndexes'); const merge = require('./helpers/schema/merge'); const mpath = require('mpath'); @@ -53,6 +54,7 @@ let id = 0; * - [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 - 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 @@ -113,6 +115,7 @@ function Schema(obj, options) { this.plugins = []; // For internal debugging. Do not use this to try to save a schema in MDB. this.$id = ++id; + this.mapPaths = []; this.s = { hooks: new Kareem() @@ -319,6 +322,7 @@ Schema.prototype.clone = function() { s.$globalPluginsApplied = this.$globalPluginsApplied; s.$isRootDiscriminator = this.$isRootDiscriminator; s.$implicitlyCreated = this.$implicitlyCreated; + s.mapPaths = [].concat(this.mapPaths); if (this.discriminatorMapping != null) { s.discriminatorMapping = Object.assign({}, this.discriminatorMapping); @@ -466,9 +470,18 @@ Schema.prototype.add = function add(obj, prefix) { } prefix = prefix || ''; + // avoid prototype pollution + if (prefix === '__proto__.' || prefix === 'constructor.' || prefix === 'prototype.') { + return this; + } + const keys = Object.keys(obj); for (const key of keys) { + if (utils.specialProperties.has(key)) { + continue; + } + const fullPath = prefix + key; if (obj[key] == null) { @@ -591,7 +604,6 @@ reserved.isNew = reserved.populated = reserved.remove = reserved.save = -reserved.schema = reserved.toObject = reserved.validate = 1; @@ -655,6 +667,9 @@ Schema.prototype.path = function(path, obj) { let fullPath = ''; for (const sub of subpaths) { + if (utils.specialProperties.has(sub)) { + throw new Error('Cannot set special property `' + sub + '` on a schema'); + } fullPath = fullPath += (fullPath.length > 0 ? '.' : '') + sub; if (!branch[sub]) { this.nested[fullPath] = true; @@ -688,24 +703,29 @@ Schema.prototype.path = function(path, obj) { !utils.hasUserDefinedProperty(obj.of, this.options.typeKey); _mapType = isInlineSchema ? new Schema(obj.of) : obj.of; } + if (utils.hasUserDefinedProperty(obj, 'ref')) { + _mapType = { type: _mapType, ref: obj.ref }; + } + this.paths[mapPath] = this.interpretAsType(mapPath, _mapType, this.options); + this.mapPaths.push(this.paths[mapPath]); schemaType.$__schemaType = this.paths[mapPath]; } if (schemaType.$isSingleNested) { - for (const key in schemaType.schema.paths) { + for (const key of Object.keys(schemaType.schema.paths)) { this.singleNestedPaths[path + '.' + key] = schemaType.schema.paths[key]; } - for (const key in schemaType.schema.singleNestedPaths) { + for (const key of Object.keys(schemaType.schema.singleNestedPaths)) { this.singleNestedPaths[path + '.' + key] = schemaType.schema.singleNestedPaths[key]; } - for (const key in schemaType.schema.subpaths) { + for (const key of Object.keys(schemaType.schema.subpaths)) { this.singleNestedPaths[path + '.' + key] = schemaType.schema.subpaths[key]; } - for (const key in schemaType.schema.nested) { + for (const key of Object.keys(schemaType.schema.nested)) { this.singleNestedPaths[path + '.' + key] = 'nested'; } @@ -766,19 +786,25 @@ Schema.prototype.path = function(path, obj) { if (schemaType.$isMongooseDocumentArray) { for (const key of Object.keys(schemaType.schema.paths)) { - this.subpaths[path + '.' + key] = schemaType.schema.paths[key]; - schemaType.schema.paths[key].$isUnderneathDocArray = true; + const _schemaType = schemaType.schema.paths[key]; + this.subpaths[path + '.' + key] = _schemaType; + if (typeof _schemaType === 'object' && _schemaType != null) { + _schemaType.$isUnderneathDocArray = true; + } } for (const key of Object.keys(schemaType.schema.subpaths)) { - this.subpaths[path + '.' + key] = schemaType.schema.subpaths[key]; - schemaType.schema.subpaths[key].$isUnderneathDocArray = true; + const _schemaType = schemaType.schema.subpaths[key]; + this.subpaths[path + '.' + key] = _schemaType; + if (typeof _schemaType === 'object' && _schemaType != null) { + _schemaType.$isUnderneathDocArray = true; + } } for (const key of Object.keys(schemaType.schema.singleNestedPaths)) { - if (typeof schemaType.schema.singleNestedPaths[cleanPath] !== 'object') { - continue; + const _schemaType = schemaType.schema.singleNestedPaths[key]; + this.subpaths[path + '.' + key] = _schemaType; + if (typeof _schemaType === 'object' && _schemaType != null) { + _schemaType.$isUnderneathDocArray = true; } - this.subpaths[path + '.' + key] = schemaType.schema.singleNestedPaths[key]; - schemaType.schema.singleNestedPaths[key].$isUnderneathDocArray = true; } } @@ -836,10 +862,11 @@ function _pathToPositionalSyntax(path) { */ function getMapPath(schema, path) { - for (const _path of Object.keys(schema.paths)) { - if (!_path.includes('.$*')) { - continue; - } + if (schema.mapPaths.length === 0) { + return null; + } + for (const val of schema.mapPaths) { + const _path = val.path; const re = new RegExp('^' + _path.replace(/\.\$\*/g, '\\.[^.]+') + '$'); if (re.test(path)) { return schema.paths[_path]; @@ -913,11 +940,21 @@ Schema.prototype.interpretAsType = function(path, obj, options) { : type[0]; if (cast && cast.instanceOfSchema) { + if (!(cast instanceof Schema)) { + throw new TypeError('Schema for array path `' + path + + '` is from a different copy of the Mongoose module. Please make sure you\'re using the same version ' + + 'of Mongoose everywhere with `npm list mongoose`.'); + } return new MongooseTypes.DocumentArray(path, cast, obj); } if (cast && cast[options.typeKey] && cast[options.typeKey].instanceOfSchema) { + if (!(cast[options.typeKey] instanceof Schema)) { + throw new TypeError('Schema for array path `' + path + + '` is from a different copy of the Mongoose module. Please make sure you\'re using the same version ' + + 'of Mongoose everywhere with `npm list mongoose`.'); + } return new MongooseTypes.DocumentArray(path, cast[options.typeKey], obj, cast); } @@ -970,6 +1007,11 @@ Schema.prototype.interpretAsType = function(path, obj, options) { ? type : type.schemaName || utils.getFunctionName(type); + // For Jest 26+, see #10296 + if (name === 'ClockDate') { + name = 'Date'; + } + if (!MongooseTypes.hasOwnProperty(name)) { throw new TypeError('Invalid schema configuration: ' + `\`${name}\` is not a valid type within the array \`${path}\`.` + @@ -1000,6 +1042,10 @@ Schema.prototype.interpretAsType = function(path, obj, options) { if (name === 'ObjectID') { name = 'ObjectId'; } + // For Jest 26+, see #10296 + if (name === 'ClockDate') { + name = 'Date'; + } if (MongooseTypes[name] == null) { throw new TypeError(`Invalid schema configuration: \`${name}\` is not ` + @@ -1156,7 +1202,7 @@ Schema.prototype.hasMixedParent = function(path) { path = ''; for (let i = 0; i < subpaths.length; ++i) { path = i > 0 ? path + '.' + subpaths[i] : subpaths[i]; - if (path in this.paths && + if (this.paths.hasOwnProperty(path) && this.paths[path] instanceof MongooseTypes.Mixed) { return this.paths[path]; } @@ -1454,7 +1500,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](./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](/docs/guide.html#methods) * * @param {String|Object} method name * @param {Function} [fn] @@ -1516,7 +1562,7 @@ Schema.prototype.static = function(name, fn) { * * @param {Object} fields * @param {Object} [options] Options to pass to [MongoDB driver's `createIndex()` function](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#createIndex) - * @param {String} [options.expires=null] Mongoose-specific syntactic sugar, uses [ms](https://www.npmjs.com/package/ms) to convert `expires` option into seconds for the `expireAfterSeconds` in the above link. + * @param {String | number} [options.expires=null] Mongoose-specific syntactic sugar, uses [ms](https://www.npmjs.com/package/ms) to convert `expires` option into seconds for the `expireAfterSeconds` in the above link. * @api public */ @@ -1637,8 +1683,8 @@ Object.defineProperty(Schema, 'indexTypes', { }); /** - * Returns a list of indexes that this schema declares, via `schema.index()` - * or by `index: true` in a path's options. + * Returns a list of indexes that this schema declares, via `schema.index()` or by `index: true` in a path's options. + * Indexes are expressed as an array `[spec, options]`. * * ####Example: * @@ -1651,6 +1697,17 @@ 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. + * For example, the below plugin makes every index unique by default. + * + * function myPlugin(schema) { + * for (const index of schema.indexes()) { + * if (index[1].unique === undefined) { + * index[1].unique = true; + * } + * } + * } + * * @api public * @return {Array} list of indexes defined in the schema */ @@ -1674,7 +1731,7 @@ Schema.prototype.indexes = function() { */ Schema.prototype.virtual = function(name, options) { - if (name instanceof VirtualType || (name != null && name.constructor.name === 'VirtualType')) { + if (name instanceof VirtualType || getConstructorName(name) === 'VirtualType') { return this.virtual(name.path, name.options); } @@ -1858,9 +1915,9 @@ function _deletePath(schema, name) { /** * 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](http://mongoosejs.com/docs/guide.html#virtuals), - * [statics](http://mongoosejs.com/docs/guide.html#statics), and - * [methods](http://mongoosejs.com/docs/guide.html#methods). + * to schema [virtuals](/docs/guide.html#virtuals), + * [statics](/docs/guide.html#statics), and + * [methods](/docs/guide.html#methods). * * ####Example: * @@ -1900,17 +1957,17 @@ Schema.prototype.loadClass = function(model, virtualsOnly) { return this; } - this.loadClass(Object.getPrototypeOf(model)); + this.loadClass(Object.getPrototypeOf(model), virtualsOnly); // Add static methods if (!virtualsOnly) { Object.getOwnPropertyNames(model).forEach(function(name) { - if (name.match(/^(length|name|prototype)$/)) { + if (name.match(/^(length|name|prototype|constructor|__proto__)$/)) { return; } - const method = Object.getOwnPropertyDescriptor(model, name); - if (typeof method.value === 'function') { - this.static(name, method.value); + const prop = Object.getOwnPropertyDescriptor(model, name); + if (prop.hasOwnProperty('value')) { + this.static(name, prop.value); } }, this); } @@ -1927,9 +1984,15 @@ Schema.prototype.loadClass = function(model, virtualsOnly) { } } if (typeof method.get === 'function') { + if (this.virtuals[name]) { + this.virtuals[name].getters = []; + } this.virtual(name).get(method.get); } if (typeof method.set === 'function') { + if (this.virtuals[name]) { + this.virtuals[name].setters = []; + } this.virtual(name).set(method.set); } }, this); @@ -2002,7 +2065,7 @@ Schema.prototype._getSchema = function(path) { } } else if (foundschema.$isSchemaMap) { if (p + 1 >= parts.length) { - return foundschema.$__schemaType; + return foundschema; } const ret = search(parts.slice(p + 1), foundschema.$__schemaType.schema); return ret; @@ -2113,14 +2176,14 @@ module.exports = exports = Schema; * * ####Types: * - * - [String](#schema-string-js) - * - [Number](#schema-number-js) - * - [Boolean](#schema-boolean-js) | Bool - * - [Array](#schema-array-js) - * - [Buffer](#schema-buffer-js) - * - [Date](#schema-date-js) - * - [ObjectId](#schema-objectid-js) | Oid - * - [Mixed](#schema-mixed-js) + * - [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) * * Using this exposed access to the `Mixed` SchemaType, we can use them in our schema. * diff --git a/lib/schema/SingleNestedPath.js b/lib/schema/SingleNestedPath.js index 5024728046a..bbbf523addd 100644 --- a/lib/schema/SingleNestedPath.js +++ b/lib/schema/SingleNestedPath.js @@ -147,7 +147,7 @@ SingleNestedPath.prototype.$conditionalHandlers.$exists = $exists; * @api private */ -SingleNestedPath.prototype.cast = function(val, doc, init, priorVal) { +SingleNestedPath.prototype.cast = function(val, doc, init, priorVal, options) { if (val && val.$isSingleNested && val.parent === doc) { return val; } @@ -169,16 +169,16 @@ SingleNestedPath.prototype.cast = function(val, doc, init, priorVal) { } return obj; }, {}); - + options = Object.assign({}, options, { priorDoc: priorVal }); if (init) { subdoc = new Constructor(void 0, selected, doc); subdoc.init(val); } else { if (Object.keys(val).length === 0) { - return new Constructor({}, selected, doc, undefined, { priorDoc: priorVal }); + return new Constructor({}, selected, doc, undefined, options); } - return new Constructor(val, selected, doc, undefined, { priorDoc: priorVal }); + return new Constructor(val, selected, doc, undefined, options); } return subdoc; diff --git a/lib/schema/array.js b/lib/schema/array.js index 7351cbef241..872eeeba68f 100644 --- a/lib/schema/array.js +++ b/lib/schema/array.js @@ -25,6 +25,7 @@ let MongooseArray; let EmbeddedDoc; const isNestedArraySymbol = Symbol('mongoose#isNestedArray'); +const emptyOpts = Object.freeze({}); /** * Array SchemaType constructor @@ -60,6 +61,10 @@ function SchemaArray(key, cast, options, schemaOptions) { } } + if (options != null && options.ref != null && castOptions.ref == null) { + castOptions.ref = options.ref; + } + if (cast === Object) { cast = Mixed; } @@ -81,16 +86,16 @@ function SchemaArray(key, cast, options, schemaOptions) { if (typeof caster === 'function' && !caster.$isArraySubdocument && !caster.$isSchemaMap) { - this.caster = new caster(null, castOptions); + const path = this.caster instanceof EmbeddedDoc ? null : key; + this.caster = new caster(path, castOptions); } else { this.caster = caster; + if (!(this.caster instanceof EmbeddedDoc)) { + this.caster.path = key; + } } this.$embeddedSchemaType = this.caster; - - if (!(this.caster instanceof EmbeddedDoc)) { - this.caster.path = key; - } } this.$isMongooseArray = true; @@ -141,6 +146,10 @@ SchemaArray.schemaName = 'Array'; SchemaArray.options = { castNonArrays: true }; +/*! + * ignore + */ + SchemaArray.defaultOptions = {}; /** @@ -158,7 +167,6 @@ SchemaArray.defaultOptions = {}; * @param {*} value - value for option * @return {undefined} * @function set - * @static * @api public */ SchemaArray.set = SchemaType.set; @@ -191,7 +199,6 @@ SchemaArray._checkRequired = SchemaType.prototype.checkRequired; * @param {Function} fn * @return {Function} * @function checkRequired - * @static * @api public */ @@ -261,23 +268,30 @@ SchemaArray.prototype.enum = function() { */ SchemaArray.prototype.applyGetters = function(value, scope) { - if (this.caster.options && this.caster.options.ref) { + if (scope != null && scope.$__ != null && scope.populated(this.path)) { // means the object id was populated return value; } - return SchemaType.prototype.applyGetters.call(this, value, scope); + const ret = SchemaType.prototype.applyGetters.call(this, value, scope); + if (Array.isArray(ret)) { + const len = ret.length; + for (let i = 0; i < len; ++i) { + ret[i] = this.caster.applyGetters(ret[i], scope); + } + } + return ret; }; SchemaArray.prototype._applySetters = function(value, scope, init, priorVal) { - if (this.casterConstructor instanceof SchemaArray && + if (this.casterConstructor.$isMongooseArray && SchemaArray.options.castNonArrays && !this[isNestedArraySymbol]) { // Check nesting levels and wrap in array if necessary let depth = 0; let arr = this; while (arr != null && - arr instanceof SchemaArray && + arr.$isMongooseArray && !arr.$isMongooseDocumentArray) { ++depth; arr = arr.casterConstructor; @@ -314,7 +328,8 @@ SchemaArray.prototype.cast = function(value, doc, init, prev, options) { let l; if (Array.isArray(value)) { - if (!value.length && doc) { + const len = value.length; + if (!len && doc) { const indexes = doc.schema.indexedPaths(); const arrayPath = this.path; @@ -339,39 +354,35 @@ SchemaArray.prototype.cast = function(value, doc, init, prev, options) { } } - if (!(value && value.isMongooseArray)) { - value = MongooseArray(value, this._arrayPath || this.path, doc, this); - } else if (value && value.isMongooseArray) { - // We need to create a new array, otherwise change tracking will - // update the old doc (gh-4449) - value = MongooseArray(value, this._arrayPath || this.path, doc, this); - } + options = options || emptyOpts; - const isPopulated = doc != null && doc.$__ != null && doc.populated(this.path); - if (isPopulated) { + value = MongooseArray(value, options.path || this._arrayPath || this.path, doc, this); + + if (init && doc != null && doc.$__ != null && doc.populated(this.path)) { return value; } const caster = this.caster; + const isMongooseArray = caster.$isMongooseArray; + const isArrayOfNumbers = caster.instance === 'Number'; if (caster && this.casterConstructor !== Mixed) { try { - const len = value.length; for (i = 0; i < len; i++) { // Special case: number arrays disallow undefined. // Re: gh-840 // See commit 1298fe92d2c790a90594bd08199e45a4a09162a6 - if (caster.instance === 'Number' && value[i] === void 0) { + if (isArrayOfNumbers && value[i] === void 0) { throw new MongooseError('Mongoose number arrays disallow storing undefined'); } const opts = {}; // Perf: creating `arrayPath` is expensive for large arrays. // We only need `arrayPath` if this is a nested array, so // skip if possible. - if (caster.$isMongooseArray) { - if (options != null && options.arrayPath != null) { - opts.arrayPath = options.arrayPath + '.' + i; - } else if (this.caster._arrayParentPath != null) { - opts.arrayPath = this.caster._arrayParentPath + '.' + i; + if (isMongooseArray) { + if (options.arrayPath != null) { + opts.arrayPathIndex = i; + } else if (caster._arrayParentPath != null) { + opts.arrayPathIndex = i; } } value[i] = caster.applySetters(value[i], doc, init, void 0, opts); @@ -397,12 +408,50 @@ SchemaArray.prototype.cast = function(value, doc, init, prev, options) { throw new CastError('Array', util.inspect(value), this.path, null, this); }; +/*! + * ignore + */ + +SchemaArray.prototype._castForPopulate = function _castForPopulate(value, doc) { + // lazy load + MongooseArray || (MongooseArray = require('../types').Array); + + if (Array.isArray(value)) { + let i; + const len = value.length; + + const caster = this.caster; + if (caster && this.casterConstructor !== Mixed) { + try { + for (i = 0; i < len; i++) { + const opts = {}; + // Perf: creating `arrayPath` is expensive for large arrays. + // We only need `arrayPath` if this is a nested array, so + // skip if possible. + if (caster.$isMongooseArray && caster._arrayParentPath != null) { + opts.arrayPathIndex = i; + } + + value[i] = caster.cast(value[i], doc, false, void 0, opts); + } + } catch (e) { + // rethrow + throw new CastError('[' + e.kind + ']', util.inspect(value), this.path + '.' + i, e, this); + } + } + + return value; + } + + throw new CastError('Array', util.inspect(value), this.path, null, this); +}; + /*! * Ignore */ SchemaArray.prototype.discriminator = function(name, schema) { - let arr = this; // eslint-disable-line consistent-this + let arr = this; while (arr.$isMongooseArray && !arr.$isMongooseDocumentArray) { arr = arr.casterConstructor; if (arr == null || typeof arr === 'function') { @@ -460,7 +509,7 @@ SchemaArray.prototype.castForQuery = function($conditional, value) { Constructor.discriminators[val[Constructor.schema.options.discriminatorKey]]) { Constructor = Constructor.discriminators[val[Constructor.schema.options.discriminatorKey]]; } else { - const constructorByValue = getDiscriminatorByValue(Constructor, val[Constructor.schema.options.discriminatorKey]); + const constructorByValue = getDiscriminatorByValue(Constructor.discriminators, val[Constructor.schema.options.discriminatorKey]); if (constructorByValue) { Constructor = constructorByValue; } diff --git a/lib/schema/buffer.js b/lib/schema/buffer.js index c5b8cb7a114..d8354166200 100644 --- a/lib/schema/buffer.js +++ b/lib/schema/buffer.js @@ -10,11 +10,8 @@ const SchemaType = require('../schematype'); const handleBitwiseOperator = require('./operators/bitwise'); const utils = require('../utils'); -const populateModelSymbol = require('../helpers/symbols').populateModelSymbol; - const Binary = MongooseBuffer.Binary; const CastError = SchemaType.CastError; -let Document; /** * Buffer SchemaType constructor @@ -124,36 +121,30 @@ SchemaBuffer.prototype.checkRequired = function(value, doc) { SchemaBuffer.prototype.cast = function(value, doc, init) { let ret; if (SchemaType._isRef(this, value, doc, init)) { - // wait! we may need to cast this to a document - - if (value === null || value === undefined) { + if (value && value.isMongooseBuffer) { return value; } - // lazy load - Document || (Document = require('./../document')); - - if (value instanceof Document) { - value.$__.wasPopulated = true; + if (Buffer.isBuffer(value)) { + if (!value || !value.isMongooseBuffer) { + value = new MongooseBuffer(value, [this.path, doc]); + if (this.options.subtype != null) { + value._subtype = this.options.subtype; + } + } return value; } - // setting a populated path - if (Buffer.isBuffer(value)) { - return value; - } else if (!utils.isObject(value)) { - throw new CastError('Buffer', value, this.path, null, this); + if (value instanceof Binary) { + ret = new MongooseBuffer(value.value(true), [this.path, doc]); + if (typeof value.sub_type !== 'number') { + throw new CastError('Buffer', value, this.path, null, this); + } + ret._subtype = value.sub_type; + return ret; } - // Handle the case where user directly sets a populated - // path to a plain object; cast to the Model used in - // the population query. - const path = doc.$__fullPath(this.path); - const owner = doc.ownerDocument ? doc.ownerDocument() : doc; - const pop = owner.populated(path, true); - ret = new pop.options[populateModelSymbol](value); - ret.$__.wasPopulated = true; - return ret; + return this._castRef(value, doc, init); } // documents diff --git a/lib/schema/date.js b/lib/schema/date.js index fb7f061c1f6..5ee0eb9e98c 100644 --- a/lib/schema/date.js +++ b/lib/schema/date.js @@ -8,6 +8,7 @@ const MongooseError = require('../error/index'); const SchemaDateOptions = require('../options/SchemaDateOptions'); const SchemaType = require('../schematype'); const castDate = require('../cast/date'); +const getConstructorName = require('../helpers/getConstructorName'); const utils = require('../utils'); const CastError = SchemaType.CastError; @@ -147,7 +148,7 @@ SchemaDate._defaultCaster = v => { */ SchemaDate.prototype.expires = function(when) { - if (!this._index || this._index.constructor.name !== 'Object') { + if (getConstructorName(this._index) !== 'Object') { this._index = {}; } diff --git a/lib/schema/decimal128.js b/lib/schema/decimal128.js index 0c8038f207d..9c0e1d40d55 100644 --- a/lib/schema/decimal128.js +++ b/lib/schema/decimal128.js @@ -10,10 +10,6 @@ const Decimal128Type = require('../types/decimal128'); const castDecimal128 = require('../cast/decimal128'); const utils = require('../utils'); -const populateModelSymbol = require('../helpers/symbols').populateModelSymbol; - -let Document; - /** * Decimal128 SchemaType constructor. * @@ -168,44 +164,11 @@ Decimal128.prototype.checkRequired = function checkRequired(value, doc) { Decimal128.prototype.cast = function(value, doc, init) { if (SchemaType._isRef(this, value, doc, init)) { - // wait! we may need to cast this to a document - - if (value === null || value === undefined) { - return value; - } - - // lazy load - Document || (Document = require('./../document')); - - if (value instanceof Document) { - value.$__.wasPopulated = true; - return value; - } - - // setting a populated path if (value instanceof Decimal128Type) { return value; - } else if (Buffer.isBuffer(value) || !utils.isObject(value)) { - throw new CastError('Decimal128', value, this.path, null, this); - } - - // Handle the case where user directly sets a populated - // path to a plain object; cast to the Model used in - // the population query. - const path = doc.$__fullPath(this.path); - const owner = doc.ownerDocument ? doc.ownerDocument() : doc; - const pop = owner.populated(path, true); - let ret = value; - if (!doc.$__.populated || - !doc.$__.populated[path] || - !doc.$__.populated[path].options || - !doc.$__.populated[path].options.options || - !doc.$__.populated[path].options.options.lean) { - ret = new pop.options[populateModelSymbol](value); - ret.$__.wasPopulated = true; } - return ret; + return this._castRef(value, doc, init); } let castDecimal128; diff --git a/lib/schema/documentarray.js b/lib/schema/documentarray.js index ac88560286e..509c7bf4595 100644 --- a/lib/schema/documentarray.js +++ b/lib/schema/documentarray.js @@ -18,6 +18,7 @@ const util = require('util'); const utils = require('../utils'); const getConstructor = require('../helpers/discriminator/getConstructor'); +const arrayAtomicsSymbol = require('../helpers/symbols').arrayAtomicsSymbol; const arrayPathSymbol = require('../helpers/symbols').arrayPathSymbol; const documentArrayParent = require('../helpers/symbols').documentArrayParent; @@ -251,6 +252,11 @@ DocumentArrayPath.prototype.doValidate = function(array, fn, scope, options) { doc = array[i] = new Constructor(doc, array, undefined, undefined, i); } + if (options != null && options.validateModifiedOnly && !doc.isModified()) { + --count || fn(error); + continue; + } + doc.$__validate(callback); } } @@ -267,7 +273,7 @@ DocumentArrayPath.prototype.doValidate = function(array, fn, scope, options) { * @api private */ -DocumentArrayPath.prototype.doValidateSync = function(array, scope) { +DocumentArrayPath.prototype.doValidateSync = function(array, scope, options) { const schemaTypeError = SchemaType.prototype.doValidateSync.call(this, array, scope); if (schemaTypeError != null) { schemaTypeError.$isArrayValidatorError = true; @@ -299,6 +305,10 @@ DocumentArrayPath.prototype.doValidateSync = function(array, scope) { doc = array[i] = new Constructor(doc, array, undefined, undefined, i); } + if (options != null && options.validateModifiedOnly && !doc.isModified()) { + continue; + } + const subdocValidateError = doc.validateSync(); if (subdocValidateError && resultError == null) { @@ -392,8 +402,12 @@ DocumentArrayPath.prototype.cast = function(value, doc, init, prev, options) { value = new MongooseDocumentArray(value, this.path, doc); } - if (options.arrayPath != null) { - value[arrayPathSymbol] = options.arrayPath; + if (prev != null) { + value[arrayAtomicsSymbol] = prev[arrayAtomicsSymbol] || {}; + } + + if (options.arrayPathIndex != null) { + value[arrayPathSymbol] = this.path + '.' + options.arrayPathIndex; } const len = value.length; @@ -479,6 +493,14 @@ DocumentArrayPath.prototype.clone = function() { return schematype; }; +/*! + * ignore + */ + +DocumentArrayPath.prototype.applyGetters = function(value, scope) { + return SchemaType.prototype.applyGetters.call(this, value, scope); +}; + /*! * Scopes paths selected in a query to this array. * Necessary for proper default application of subdocument values. diff --git a/lib/schema/map.js b/lib/schema/map.js index 30e3eba8b30..e81dccf4540 100644 --- a/lib/schema/map.js +++ b/lib/schema/map.js @@ -26,8 +26,10 @@ class Map extends SchemaType { return val; } + const path = this.path; + if (init) { - const map = new MongooseMap({}, this.path, doc, this.$__schemaType); + const map = new MongooseMap({}, path, doc, this.$__schemaType); if (val instanceof global.Map) { for (const key of val.keys()) { @@ -35,7 +37,7 @@ class Map extends SchemaType { if (_val == null) { _val = map.$__schemaType._castNullish(_val); } else { - _val = map.$__schemaType.cast(_val, doc, true); + _val = map.$__schemaType.cast(_val, doc, true, null, { path: path + '.' + key }); } map.$init(key, _val); } @@ -45,7 +47,7 @@ class Map extends SchemaType { if (_val == null) { _val = map.$__schemaType._castNullish(_val); } else { - _val = map.$__schemaType.cast(_val, doc, true); + _val = map.$__schemaType.cast(_val, doc, true, null, { path: path + '.' + key }); } map.$init(key, _val); } @@ -54,7 +56,7 @@ class Map extends SchemaType { return map; } - return new MongooseMap(val, this.path, doc, this.$__schemaType); + return new MongooseMap(val, path, doc, this.$__schemaType); } clone() { diff --git a/lib/schema/mixed.js b/lib/schema/mixed.js index 88e4db601f4..09944bf6039 100644 --- a/lib/schema/mixed.js +++ b/lib/schema/mixed.js @@ -7,6 +7,7 @@ const SchemaType = require('../schematype'); const symbols = require('./symbols'); const isObject = require('../helpers/isObject'); +const utils = require('../utils'); /** * Mixed SchemaType constructor. @@ -103,6 +104,9 @@ Mixed.set = SchemaType.set; */ Mixed.prototype.cast = function(val) { + if (val instanceof Error) { + return utils.errorToPOJO(val); + } return val; }; diff --git a/lib/schema/number.js b/lib/schema/number.js index 85e96058084..2b2c1c1e4b3 100644 --- a/lib/schema/number.js +++ b/lib/schema/number.js @@ -11,10 +11,7 @@ const castNumber = require('../cast/number'); const handleBitwiseOperator = require('./operators/bitwise'); const utils = require('../utils'); -const populateModelSymbol = require('../helpers/symbols').populateModelSymbol; - const CastError = SchemaType.CastError; -let Document; /** * Number SchemaType constructor. @@ -350,36 +347,11 @@ SchemaNumber.prototype.enum = function(values, message) { SchemaNumber.prototype.cast = function(value, doc, init) { if (SchemaType._isRef(this, value, doc, init)) { - // wait! we may need to cast this to a document - - if (value === null || value === undefined) { - return value; - } - - // lazy load - Document || (Document = require('./../document')); - - if (value instanceof Document) { - value.$__.wasPopulated = true; - return value; - } - - // setting a populated path if (typeof value === 'number') { return value; - } else if (Buffer.isBuffer(value) || !utils.isObject(value)) { - throw new CastError('Number', value, this.path, null, this); } - // Handle the case where user directly sets a populated - // path to a plain object; cast to the Model used in - // the population query. - const path = doc.$__fullPath(this.path); - const owner = doc.ownerDocument ? doc.ownerDocument() : doc; - const pop = owner.populated(path, true); - const ret = new pop.options[populateModelSymbol](value); - ret.$__.wasPopulated = true; - return ret; + return this._castRef(value, doc, init); } const val = value && typeof value._id !== 'undefined' ? diff --git a/lib/schema/objectid.js b/lib/schema/objectid.js index fb07c149bbd..21105b66e9f 100644 --- a/lib/schema/objectid.js +++ b/lib/schema/objectid.js @@ -7,11 +7,10 @@ const SchemaObjectIdOptions = require('../options/SchemaObjectIdOptions'); const SchemaType = require('../schematype'); const castObjectId = require('../cast/objectid'); +const getConstructorName = require('../helpers/getConstructorName'); const oid = require('../types/objectid'); const utils = require('../utils'); -const populateModelSymbol = require('../helpers/symbols').populateModelSymbol; - const CastError = SchemaType.CastError; let Document; @@ -225,45 +224,13 @@ ObjectId.prototype.checkRequired = function checkRequired(value, doc) { ObjectId.prototype.cast = function(value, doc, init) { if (SchemaType._isRef(this, value, doc, init)) { // wait! we may need to cast this to a document - - if (value === null || value === undefined) { - return value; - } - - // lazy load - Document || (Document = require('./../document')); - - if (value instanceof Document) { - value.$__.wasPopulated = true; - return value; - } - - // setting a populated path if (value instanceof oid) { return value; - } else if ((value.constructor.name || '').toLowerCase() === 'objectid') { + } else if ((getConstructorName(value) || '').toLowerCase() === 'objectid') { return new oid(value.toHexString()); - } else if (Buffer.isBuffer(value) || !utils.isObject(value)) { - throw new CastError('ObjectId', value, this.path, null, this); - } - - // Handle the case where user directly sets a populated - // path to a plain object; cast to the Model used in - // the population query. - const path = doc.$__fullPath(this.path); - const owner = doc.ownerDocument ? doc.ownerDocument() : doc; - const pop = owner.populated(path, true); - let ret = value; - if (!doc.$__.populated || - !doc.$__.populated[path] || - !doc.$__.populated[path].options || - !doc.$__.populated[path].options.options || - !doc.$__.populated[path].options.options.lean) { - ret = new pop.options[populateModelSymbol](value); - ret.$__.wasPopulated = true; } - return ret; + return this._castRef(value, doc, init); } let castObjectId; diff --git a/lib/schema/string.js b/lib/schema/string.js index ac9552b9ebc..6778b5c48ed 100644 --- a/lib/schema/string.js +++ b/lib/schema/string.js @@ -10,10 +10,7 @@ const SchemaStringOptions = require('../options/SchemaStringOptions'); const castString = require('../cast/string'); const utils = require('../utils'); -const populateModelSymbol = require('../helpers/symbols').populateModelSymbol; - const CastError = SchemaType.CastError; -let Document; /** * String SchemaType constructor. @@ -584,36 +581,11 @@ SchemaString.prototype.checkRequired = function checkRequired(value, doc) { SchemaString.prototype.cast = function(value, doc, init) { if (SchemaType._isRef(this, value, doc, init)) { - // wait! we may need to cast this to a document - - if (value === null || value === undefined) { - return value; - } - - // lazy load - Document || (Document = require('./../document')); - - if (value instanceof Document) { - value.$__.wasPopulated = true; - return value; - } - - // setting a populated path if (typeof value === 'string') { return value; - } else if (Buffer.isBuffer(value) || !utils.isObject(value)) { - throw new CastError('string', value, this.path, null, this); } - // Handle the case where user directly sets a populated - // path to a plain object; cast to the Model used in - // the population query. - const path = doc.$__fullPath(this.path); - const owner = doc.ownerDocument ? doc.ownerDocument() : doc; - const pop = owner.populated(path, true); - const ret = new pop.options[populateModelSymbol](value); - ret.$__.wasPopulated = true; - return ret; + return this._castRef(value, doc, init); } let castString; diff --git a/lib/schematype.js b/lib/schematype.js index f54fbd9d92b..62a714f85c5 100644 --- a/lib/schematype.js +++ b/lib/schematype.js @@ -15,10 +15,10 @@ const schemaTypeSymbol = require('./helpers/symbols').schemaTypeSymbol; const util = require('util'); const utils = require('./utils'); const validatorErrorSymbol = require('./helpers/symbols').validatorErrorSymbol; - -const documentIsSelected = require('./helpers/symbols').documentIsSelected; const documentIsModified = require('./helpers/symbols').documentIsModified; +const populateModelSymbol = require('./helpers/symbols').populateModelSymbol; + const CastError = MongooseError.CastError; const ValidatorError = MongooseError.ValidatorError; @@ -47,6 +47,8 @@ function SchemaType(path, options, instance) { []; this.setters = []; + this.splitPath(); + options = options || {}; const defaultOptions = this.constructor.defaultOptions || {}; const defaultOptionsKeys = Object.keys(defaultOptions); @@ -121,11 +123,27 @@ function SchemaType(path, options, instance) { } /*! - * ignore + * The class that Mongoose uses internally to instantiate this SchemaType's `options` property. */ SchemaType.prototype.OptionsConstructor = SchemaTypeOptions; +/*! + * ignore + */ + +SchemaType.prototype.splitPath = function() { + if (this._presplitPath != null) { + return this._presplitPath; + } + if (this.path == null) { + return undefined; + } + + this._presplitPath = this.path.indexOf('.') === -1 ? [this.path] : this.path.split('.'); + return this._presplitPath; +}; + /** * Get/set the function used to cast arbitrary values to this type. * @@ -192,6 +210,19 @@ SchemaType.prototype.castFunction = function castFunction(caster) { return this._castFunction; }; +/** + * The function that Mongoose calls to cast arbitrary values to this SchemaType. + * + * @param {Object} value value to cast + * @param {Document} doc document that triggers the casting + * @param {Boolean} init + * @api public + */ + +SchemaType.prototype.cast = function cast() { + throw new Error('Base SchemaType class does not implement a `cast()` function'); +}; + /** * Sets a default option for this schema type. * @@ -953,7 +984,7 @@ SchemaType.prototype.required = function(required, message) { const cachedRequired = get(this, '$__.cachedRequired'); // no validation when this path wasn't selected in the query. - if (cachedRequired != null && !this[documentIsSelected](_this.path) && !this[documentIsModified](_this.path)) { + if (cachedRequired != null && !this.$__isSelected(_this.path) && !this[documentIsModified](_this.path)) { return true; } @@ -1051,31 +1082,15 @@ SchemaType.prototype.getDefault = function(scope, init) { * @api private */ -SchemaType.prototype._applySetters = function(value, scope, init, priorVal) { +SchemaType.prototype._applySetters = function(value, scope, init) { let v = value; - const setters = this.setters; - const caster = this.caster; - - for (const setter of utils.clone(setters).reverse()) { - v = setter.call(scope, v, this); + if (init) { + return v; } + const setters = this.setters; - if (caster && !caster.$isMongooseArray && Array.isArray(v) && caster.setters) { - const newVal = []; - - for (let i = 0; i < v.length; ++i) { - const value = v[i]; - try { - newVal.push(caster.applySetters(value, scope, init, priorVal)); - } catch (err) { - if (err instanceof MongooseError.CastError) { - err.$originalErrorPath = err.path; - err.path = err.path + '.' + i; - } - throw err; - } - } - v = newVal; + for (let i = setters.length - 1; i >= 0; i--) { + v = setters[i].call(scope, v, this); } return v; @@ -1100,7 +1115,6 @@ SchemaType.prototype._castNullish = function _castNullish(v) { SchemaType.prototype.applySetters = function(value, scope, init, priorVal, options) { let v = this._applySetters(value, scope, init, priorVal, options); - if (v == null) { return this._castNullish(v); } @@ -1432,11 +1446,54 @@ SchemaType._isRef = function(self, value, doc, init) { ) { return true; } + + return init; } return false; }; +/*! + * ignore + */ + +SchemaType.prototype._castRef = function _castRef(value, doc, init) { + if (value == null) { + return value; + } + + if (value.$__ != null) { + value.$__.wasPopulated = true; + return value; + } + + // setting a populated path + if (Buffer.isBuffer(value) || !utils.isObject(value)) { + if (init) { + return value; + } + throw new CastError(this.instance, value, this.path, null, this); + } + + // Handle the case where user directly sets a populated + // path to a plain object; cast to the Model used in + // the population query. + const path = doc.$__fullPath(this.path); + const owner = doc.ownerDocument ? doc.ownerDocument() : doc; + const pop = owner.populated(path, true); + let ret = value; + if (!doc.$__.populated || + !doc.$__.populated[path] || + !doc.$__.populated[path].options || + !doc.$__.populated[path].options.options || + !doc.$__.populated[path].options.options.lean) { + ret = new pop.options[populateModelSymbol](value); + ret.$__.wasPopulated = true; + } + + return ret; +}; + /*! * ignore */ diff --git a/lib/types/array.js b/lib/types/array.js index 802a62887a1..420edeeea91 100644 --- a/lib/types/array.js +++ b/lib/types/array.js @@ -5,7 +5,6 @@ 'use strict'; const CoreMongooseArray = require('./core_array'); -const Document = require('../document'); const arrayAtomicsSymbol = require('../helpers/symbols').arrayAtomicsSymbol; const arrayParentSymbol = require('../helpers/symbols').arrayParentSymbol; @@ -30,28 +29,44 @@ const _basePush = Array.prototype.push; */ function MongooseArray(values, path, doc, schematype) { - const arr = new CoreMongooseArray(); - arr[arrayAtomicsSymbol] = {}; + let arr; if (Array.isArray(values)) { const len = values.length; - for (let i = 0; i < len; ++i) { - _basePush.call(arr, values[i]); + + // Perf optimizations for small arrays: much faster to use `...` than `for` + `push`, + // but large arrays may cause stack overflows. And for arrays of length 0/1, just + // modifying the array is faster. Seems small, but adds up when you have a document + // with thousands of nested arrays. + if (len === 0) { + arr = new CoreMongooseArray(); + } else if (len === 1) { + arr = new CoreMongooseArray(1); + arr[0] = values[0]; + } else if (len < 10000) { + arr = new CoreMongooseArray(); + _basePush.apply(arr, values); + } else { + arr = new CoreMongooseArray(); + for (let i = 0; i < len; ++i) { + _basePush.call(arr, values[i]); + } } if (values[arrayAtomicsSymbol] != null) { arr[arrayAtomicsSymbol] = values[arrayAtomicsSymbol]; } + } else { + arr = new CoreMongooseArray(); } arr[arrayPathSymbol] = path; - arr[arraySchemaSymbol] = void 0; // Because doc comes from the context of another function, doc === global // can happen if there was a null somewhere up the chain (see #3020) // RB Jun 17, 2015 updated to check for presence of expected paths instead // to make more proof against unusual node environments - if (doc && doc instanceof Document) { + if (doc != null && doc.$__ != null) { arr[arrayParentSymbol] = doc; arr[arraySchemaSymbol] = schematype || doc.schema.path(path); } diff --git a/lib/types/core_array.js b/lib/types/core_array.js index df66617d451..2c15b0a9f8a 100644 --- a/lib/types/core_array.js +++ b/lib/types/core_array.js @@ -94,7 +94,7 @@ class CoreMongooseArray extends Array { */ $atomics() { - return this[arrayAtomicsSymbol]; + return this[arrayAtomicsSymbol] || {}; } /*! @@ -248,8 +248,8 @@ class CoreMongooseArray extends Array { // gh-2399 // we should cast model only when it's not a discriminator - const isDisc = value.schema && value.schema.discriminatorMapping && - value.schema.discriminatorMapping.key !== undefined; + const isDisc = value.$__schema && value.$__schema.discriminatorMapping && + value.$__schema.discriminatorMapping.key !== undefined; if (!isDisc) { value = new Model(value); } @@ -284,7 +284,7 @@ class CoreMongooseArray extends Array { * @memberOf MongooseArray */ - _markModified(elem, embeddedPath) { + _markModified(elem) { const parent = this[arrayParentSymbol]; let dirtyPath; @@ -292,13 +292,7 @@ class CoreMongooseArray extends Array { dirtyPath = this[arrayPathSymbol]; if (arguments.length) { - if (embeddedPath != null) { - // an embedded doc bubbled up the change - dirtyPath = dirtyPath + '.' + this.indexOf(elem) + '.' + embeddedPath; - } else { - // directly set an index - dirtyPath = dirtyPath + '.' + elem; - } + dirtyPath = dirtyPath + '.' + elem; } if (dirtyPath != null && dirtyPath.endsWith('.$')) { @@ -334,6 +328,8 @@ class CoreMongooseArray extends Array { return this; } + this[arrayAtomicsSymbol] || (this[arrayAtomicsSymbol] = {}); + const atomics = this[arrayAtomicsSymbol]; // reset pop/shift after save diff --git a/lib/types/documentarray.js b/lib/types/documentarray.js index 67afcbcfd3c..90d2637269d 100644 --- a/lib/types/documentarray.js +++ b/lib/types/documentarray.js @@ -96,7 +96,7 @@ class CoreDocumentArray extends CoreMongooseArray { Constructor.discriminators[value[Constructor.schema.options.discriminatorKey]]) { Constructor = Constructor.discriminators[value[Constructor.schema.options.discriminatorKey]]; } else { - const constructorByValue = getDiscriminatorByValue(Constructor, value[Constructor.schema.options.discriminatorKey]); + const constructorByValue = getDiscriminatorByValue(Constructor.discriminators, value[Constructor.schema.options.discriminatorKey]); if (constructorByValue) { Constructor = constructorByValue; } @@ -289,7 +289,7 @@ class CoreDocumentArray extends CoreMongooseArray { Constructor.discriminators[obj[Constructor.schema.options.discriminatorKey]]) { Constructor = Constructor.discriminators[obj[Constructor.schema.options.discriminatorKey]]; } else { - const constructorByValue = getDiscriminatorByValue(Constructor, obj[Constructor.schema.options.discriminatorKey]); + const constructorByValue = getDiscriminatorByValue(Constructor.discriminators, obj[Constructor.schema.options.discriminatorKey]); if (constructorByValue) { Constructor = constructorByValue; } @@ -330,6 +330,34 @@ class CoreDocumentArray extends CoreMongooseArray { } }; } + + _markModified(elem, embeddedPath) { + const parent = this[arrayParentSymbol]; + let dirtyPath; + + if (parent) { + dirtyPath = this[arrayPathSymbol]; + + if (arguments.length) { + if (embeddedPath != null) { + // an embedded doc bubbled up the change + const index = elem.__index; + dirtyPath = dirtyPath + '.' + index + '.' + embeddedPath; + } else { + // directly set an index + dirtyPath = dirtyPath + '.' + elem; + } + } + + if (dirtyPath != null && dirtyPath.endsWith('.$')) { + return this; + } + + parent.markModified(dirtyPath, arguments.length > 0 ? elem : parent); + } + + return this; + } } if (util.inspect.custom) { diff --git a/lib/types/embedded.js b/lib/types/embedded.js index 7f582727001..956af1da723 100644 --- a/lib/types/embedded.js +++ b/lib/types/embedded.js @@ -106,7 +106,7 @@ EmbeddedDocument.prototype.markModified = function(path) { } const pathToCheck = this.__parentArray.$path() + '.0.' + path; - if (this.isNew && this.ownerDocument().isSelected(pathToCheck)) { + if (this.isNew && this.ownerDocument().$__isSelected(pathToCheck)) { // Mark the WHOLE parent array as modified // if this is a new document (i.e., we are initializing // a document), @@ -201,6 +201,9 @@ function registerRemoveListener(sub) { */ EmbeddedDocument.prototype.$__remove = function(cb) { + if (cb == null) { + return; + } return cb(null, this); }; @@ -218,7 +221,7 @@ EmbeddedDocument.prototype.remove = function(options, fn) { options = undefined; } if (!this.__parentArray || (options && options.noop)) { - fn && fn(null); + this.$__remove(fn); return this; } @@ -234,9 +237,7 @@ EmbeddedDocument.prototype.remove = function(options, fn) { registerRemoveListener(this); } - if (fn) { - fn(null); - } + this.$__remove(fn); return this; }; @@ -396,7 +397,7 @@ EmbeddedDocument.prototype.ownerDocument = function() { EmbeddedDocument.prototype.$__fullPath = function(path) { if (!this.$__.fullPath) { - let parent = this; // eslint-disable-line consistent-this + let parent = this; if (!parent[documentArrayParent]) { return path; } diff --git a/lib/types/map.js b/lib/types/map.js index f50402e1341..fc2b7056bdd 100644 --- a/lib/types/map.js +++ b/lib/types/map.js @@ -1,8 +1,11 @@ 'use strict'; const Mixed = require('../schema/mixed'); +const ObjectId = require('./objectid'); +const clone = require('../helpers/clone'); const deepEqual = require('../utils').deepEqual; const get = require('../helpers/get'); +const getConstructorName = require('../helpers/getConstructorName'); const handleSpreadDoc = require('../helpers/document/handleSpreadDoc'); const util = require('util'); const specialProperties = require('../helpers/specialProperties'); @@ -15,11 +18,10 @@ const populateModelSymbol = require('../helpers/symbols').populateModelSymbol; class MongooseMap extends Map { constructor(v, path, doc, schemaType) { - if (v != null && v.constructor.name === 'Object') { + if (getConstructorName(v) === 'Object') { v = Object.keys(v).reduce((arr, key) => arr.concat([[key, v[key]]]), []); } super(v); - this.$__parent = doc != null && doc.$__ != null ? doc : null; this.$__path = path; this.$__schemaType = schemaType == null ? new Mixed(path) : schemaType; @@ -42,6 +44,10 @@ class MongooseMap extends Map { } get(key, options) { + if (key instanceof ObjectId) { + key = key.toString(); + } + options = options || {}; if (options.getters === false) { return super.get(key); @@ -50,6 +56,10 @@ class MongooseMap extends Map { } set(key, value) { + if (key instanceof ObjectId) { + key = key.toString(); + } + checkValidKey(key); value = handleSpreadDoc(value); @@ -77,7 +87,7 @@ class MongooseMap extends Map { } else { try { value = this.$__schemaType. - applySetters(value, this.$__parent, false, this.get(key)); + applySetters(value, this.$__parent, false, this.get(key), { path: fullPath }); } catch (error) { if (this.$__parent != null && this.$__parent.$__ != null) { this.$__parent.invalidate(fullPath, error); @@ -108,6 +118,10 @@ class MongooseMap extends Map { } delete(key) { + if (key instanceof ObjectId) { + key = key.toString(); + } + this.set(key, undefined); super.delete(key); } @@ -121,7 +135,7 @@ class MongooseMap extends Map { const ret = {}; const keys = this.keys(); for (const key of keys) { - ret[key] = this.get(key); + ret[key] = clone(this.get(key)); } return ret; } diff --git a/lib/types/subdocument.js b/lib/types/subdocument.js index e2693054ab7..5a8612b147a 100644 --- a/lib/types/subdocument.js +++ b/lib/types/subdocument.js @@ -18,14 +18,16 @@ module.exports = Subdocument; function Subdocument(value, fields, parent, skipId, options) { this.$isSingleNested = true; - + if (options != null && options.path != null) { + this.$basePath = options.path; + } const hasPriorDoc = options != null && options.priorDoc; let initedPaths = null; if (hasPriorDoc) { this._doc = Object.assign({}, options.priorDoc._doc); - delete this._doc[this.schema.options.discriminatorKey]; + delete this._doc[this.$__schema.options.discriminatorKey]; initedPaths = Object.keys(options.priorDoc._doc || {}). - filter(key => key !== this.schema.options.discriminatorKey); + filter(key => key !== this.$__schema.options.discriminatorKey); } if (parent != null) { // If setting a nested path, should copy isNew from parent re: gh-7048 @@ -41,7 +43,7 @@ function Subdocument(value, fields, parent, skipId, options) { if (!this.$__.activePaths.states.modify[key] && !this.$__.activePaths.states.default[key] && !this.$__.$setCalled.has(key)) { - const schematype = this.schema.path(key); + const schematype = this.$__schema.path(key); const def = schematype == null ? void 0 : schematype.getDefault(this); if (def === void 0) { delete this._doc[key]; @@ -253,7 +255,6 @@ Subdocument.prototype.remove = function(options, callback) { callback = options; options = null; } - registerRemoveListener(this); // If removing entire doc, no need to remove subdoc diff --git a/lib/utils.js b/lib/utils.js index c19fcabfa9f..1c531f4efd0 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -12,11 +12,13 @@ const Decimal = require('./types/decimal128'); const ObjectId = require('./types/objectid'); const PopulateOptions = require('./options/PopulateOptions'); const clone = require('./helpers/clone'); +const immediate = require('./helpers/immediate'); const isObject = require('./helpers/isObject'); const isBsonType = require('./helpers/isBsonType'); const getFunctionName = require('./helpers/getFunctionName'); const isMongooseObject = require('./helpers/isMongooseObject'); const promiseOrCallback = require('./helpers/promiseOrCallback'); +const schemaMerge = require('./helpers/schema/merge'); const specialProperties = require('./helpers/specialProperties'); let Document; @@ -282,7 +284,7 @@ exports.merge = function merge(to, from, options, path) { continue; } else if (from[key].instanceOfSchema) { if (to[key].instanceOfSchema) { - to[key].add(from[key].clone()); + schemaMerge(to[key], from[key].clone(), options.isDiscriminatorSchemaMerge); } else { to[key] = from[key].clone(); } @@ -332,7 +334,7 @@ exports.toObject = function toObject(obj) { if (exports.isPOJO(obj)) { ret = {}; - for (const k in obj) { + for (const k of Object.keys(obj)) { if (specialProperties.has(k)) { continue; } @@ -439,7 +441,7 @@ exports.tick = function tick(callback) { } catch (err) { // only nextTick on err to get out of // the event loop and avoid state corruption. - process.nextTick(function() { + immediate(function() { throw err; }); } @@ -755,23 +757,23 @@ exports.isArrayIndex = function(val) { */ exports.array.unique = function(arr) { - const primitives = {}; - const ids = {}; + const primitives = new Set(); + const ids = new Set(); const ret = []; for (const item of arr) { if (typeof item === 'number' || typeof item === 'string' || item == null) { - if (primitives[item]) { + if (primitives.has(item)) { continue; } ret.push(item); - primitives[item] = true; + primitives.add(item); } else if (item instanceof ObjectId) { - if (ids[item.toString()]) { + if (ids.has(item.toString())) { continue; } ret.push(item); - ids[item.toString()] = true; + ids.add(item.toString()); } else { ret.push(item); } @@ -920,3 +922,18 @@ exports.getOption = function(name) { */ exports.noop = function() {}; + +exports.errorToPOJO = function errorToPOJO(error) { + const isError = error instanceof Error; + if (!isError) { + throw new Error('`error` must be `instanceof Error`.'); + } + + const ret = {}; + for (const properyName of Object.getOwnPropertyNames(error)) { + ret[properyName] = error[properyName]; + } + return ret; +}; + +exports.nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10); diff --git a/lib/validoptions.js b/lib/validoptions.js index fb47f316a8f..836eadbf5c3 100644 --- a/lib/validoptions.js +++ b/lib/validoptions.js @@ -19,6 +19,7 @@ const VALID_OPTIONS = Object.freeze([ 'overwriteModels', 'returnOriginal', 'runValidators', + 'sanitizeProjection', 'selectPopulatedPaths', 'setDefaultsOnInsert', 'strict', diff --git a/package.json b/package.json index 666062bd04b..cbb17a6b225 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "5.11.11", + "version": "5.13.15", "author": "Guillermo Rauch ", "keywords": [ "mongodb", @@ -19,17 +19,19 @@ ], "license": "MIT", "dependencies": { + "@types/bson": "1.x || 4.0.x", "@types/mongodb": "^3.5.27", "bson": "^1.1.4", "kareem": "2.3.2", - "mongodb": "3.6.3", + "mongodb": "3.7.3", "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.8.3", - "mquery": "3.2.3", + "mpath": "0.8.4", + "mquery": "3.2.5", "ms": "2.1.2", + "optional-require": "1.0.x", "regexp-clone": "1.0.0", "safe-buffer": "5.2.1", - "sift": "7.0.1", + "sift": "13.5.2", "sliced": "1.0.1" }, "devDependencies": { @@ -37,10 +39,10 @@ "@babel/preset-env": "7.10.4", "@typescript-eslint/eslint-plugin": "4.10.0", "@typescript-eslint/parser": "4.10.0", + "@types/node": "16.11.23", "acquit": "1.x", "acquit-ignore": "0.1.x", "acquit-require": "0.1.x", - "async": "2.6.2", "babel-loader": "8.1.0", "benchmark": "2.1.4", "bluebird": "3.5.5", @@ -54,16 +56,16 @@ "lodash.isequal": "4.5.0", "lodash.isequalwith": "4.4.0", "marked": "1.1.1", + "mkdirp": "0.5.5", "mocha": "5.x", "moment": "2.x", - "mongodb-topology-manager": "1.0.11", - "mongoose-long": "0.2.1", "node-static": "0.7.11", "object-sizeof": "1.3.0", "pug": "2.0.3", "q": "1.5.1", + "rimraf": "2.6.3", "semver": "5.5.0", - "typescript": "4.x", + "typescript": "4.1.x", "uuid": "2.0.3", "uuid-parse": "1.0.0", "validator": "10.8.0", @@ -77,11 +79,13 @@ "build-browser": "node build-browser.js", "prepublishOnly": "npm run build-browser", "release": "git pull && git push origin master --tags && npm publish", - "release-legacy": "git pull origin 4.x && git push origin 4.x --tags && npm publish --tag legacy", + "release-legacy": "git pull origin 5.x && git push origin 5.x --tags && npm publish --tag legacy", "test": "mocha --exit ./test/*.test.js ./test/typescript/main.test.js", + "tdd": "mocha ./test/*.test.js ./test/typescript/main.test.js --watch --recursive --watch-files ./**/*.js", "test-cov": "nyc --reporter=html --reporter=text npm test" }, "main": "./index.js", + "types": "./index.d.ts", "engines": { "node": ">=4.0.0" }, @@ -120,7 +124,9 @@ ], "rules": { "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/ban-types": "off" + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/explicit-module-boundary-types": "off" } } ], diff --git a/static.js b/static.js index 78e5d71b601..8aab78191e8 100644 --- a/static.js +++ b/static.js @@ -20,6 +20,6 @@ require('http').createServer(function(req, res) { }); }); req.resume(); -}).listen(8088); +}).listen(8089); -console.error('now listening on http://localhost:8088'); +console.error('now listening on http://localhost:8089'); diff --git a/test/collection.capped.test.js b/test/collection.capped.test.js index a0270fa47a2..53def94437b 100644 --- a/test/collection.capped.test.js +++ b/test/collection.capped.test.js @@ -37,7 +37,7 @@ describe('collections: capped:', function() { }); it('creation', function() { - this.timeout(30000); + this.timeout(15000); return co(function*() { yield db.dropCollection('Test').catch(() => {}); diff --git a/test/common.js b/test/common.js index 80ffac4f9b9..62580bf66f1 100644 --- a/test/common.js +++ b/test/common.js @@ -4,12 +4,10 @@ * Module dependencies. */ -const Server = require('mongodb-topology-manager').Server; const mongoose = require('../'); const Collection = mongoose.Collection; const assert = require('assert'); -let server; const collectionNames = new Map(); if (process.env.D === '1') { @@ -192,15 +190,6 @@ function dropDBs(done) { }); } -before(function() { - return server.purge(); -}); - -after(function() { - this.timeout(15000); - - return server.stop(); -}); before(function(done) { this.timeout(10 * 1000); @@ -214,12 +203,6 @@ after(function(done) { setTimeout(() => done(), 250); }); -module.exports.server = server = new Server('mongod', { - bind_ip: '127.0.0.1', - port: 27000, - dbpath: './data/db/27000' -}); - beforeEach(function() { if (this.currentTest) { global.CURRENT_TEST = this.currentTest.title; diff --git a/test/connection.test.js b/test/connection.test.js index 74fa858d4f6..c0a28ffc078 100644 --- a/test/connection.test.js +++ b/test/connection.test.js @@ -11,13 +11,28 @@ const Q = require('q'); const assert = require('assert'); const co = require('co'); const mongodb = require('mongodb'); -const server = require('./common').server; + +const Server = require('./connection_server'); +const server = new Server('mongod', { + bind_ip: '127.0.0.1', + port: 27000, + dbpath: './data/db/27000' +}); const mongoose = start.mongoose; const Schema = mongoose.Schema; const uri = 'mongodb://localhost:27017/mongoose_test'; +before(function() { + return server.purge(); +}); + +after(function() { + this.timeout(15000); + return server.stop(); +}); + /** * Test. */ @@ -1124,16 +1139,22 @@ describe('connections:', function() { it('deleteModel()', function() { const conn = mongoose.createConnection('mongodb://localhost:27017/gh6813'); - conn.model('gh6813', new Schema({ name: String })); + let Model = conn.model('gh6813', new Schema({ name: String })); + + const events = []; + conn.on('deleteModel', model => events.push(model)); assert.ok(conn.model('gh6813')); conn.deleteModel('gh6813'); + assert.equal(events.length, 1); + assert.equal(events[0], Model); + assert.throws(function() { conn.model('gh6813'); }, /Schema hasn't been registered/); - const Model = conn.model('gh6813', new Schema({ name: String })); + Model = conn.model('gh6813', new Schema({ name: String })); assert.ok(Model); return Model.create({ name: 'test' }); }); @@ -1242,9 +1263,20 @@ describe('connections:', function() { it('allows overwriting models (gh-9406)', function() { const m = new mongoose.Mongoose(); + const events = []; + m.connection.on('model', model => events.push(model)); + const M1 = m.model('Test', Schema({ name: String }), null, { overwriteModels: true }); + assert.equal(events.length, 1); + assert.equal(events[0], M1); + const M2 = m.model('Test', Schema({ name: String }), null, { overwriteModels: true }); + assert.equal(events.length, 2); + assert.equal(events[1], M2); + const M3 = m.connection.model('Test', Schema({ name: String }), null, { overwriteModels: true }); + assert.equal(events.length, 3); + assert.equal(events[2], M3); assert.ok(M1 !== M2); assert.ok(M2 !== M3); @@ -1282,4 +1314,16 @@ describe('connections:', function() { assert.ifError(errorOnDisconnect); }); }); + it('Connection id should be scoped per Mongoose Instance (gh-10025)', function() { + const m = new mongoose.Mongoose; + const conn = m.createConnection(); + const m1 = new mongoose.Mongoose; + const conn2 = m1.createConnection(); + const conn3 = m.createConnection(); + assert.deepStrictEqual(m.connection.id, 0); + assert.deepStrictEqual(conn.id, m.connection.id + 1); + assert.deepStrictEqual(m1.connection.id, 0); + assert.deepStrictEqual(conn2.id, m1.connection.id + 1); + assert.deepStrictEqual(conn3.id, m.connection.id + 2); + }); }); diff --git a/test/connection_server.js b/test/connection_server.js new file mode 100644 index 00000000000..b2e67ad59a6 --- /dev/null +++ b/test/connection_server.js @@ -0,0 +1,80 @@ +'use strict'; +const child_process = require('child_process'); +const mkdirp = require('mkdirp'); +const rimraf = require('rimraf'); + +// For starting/stopping mongod to test connections +// Adapted from mongodb-topology-manager: +// https://github.com/mongodb-js/mongodb-topology-manager/blob/master/lib/server.js +class Server { + constructor(binary, options) { + this.binary = binary; + this.port = options['port'] || 27017; + this.bind_ip = options['bind_ip'] || '127.0.0.1'; + this.dbpath = options['dbpath'] || './data'; + } + + // Start server + start() { + const self = this; + return new Promise(function(resolve, reject) { + self.purge().then(function() { + // Spawn server process + self.server = child_process.spawn(self.binary, + ['--port', self.port, '--bind_ip', + self.bind_ip, '--dbpath', self.dbpath]); + + // Wait for ready + self.server.stdout.on('data', function(data) { + if (data.toString().includes('Waiting for connections') + || data.toString().includes('waiting for connections')) { + self.server.stdout.removeAllListeners('data'); + self.server.removeAllListeners('exit'); + resolve(); + } + }); + + // Error + self.server.on('exit', function(code) { + reject(code); + }); + }).catch(function(err) { + reject(err); + }); + }); + } + + // Stop server + stop() { + const self = this; + return new Promise(function(resolve) { + if (!self.server) { + resolve(); + } + // Resolve on exit + self.server.on('exit', function(code) { + self.server.stdout.removeAllListeners('data'); + self.server.removeAllListeners('exit'); + self.server = null; + resolve(code); + }); + // Kill + self.server.kill('SIGINT'); + }); + } + + // Remove and recreate data directory + purge() { + const self = this; + return new Promise(function(resolve, reject) { + rimraf(self.dbpath, {}, function(err) { + if (err) reject(err); + mkdirp(self.dbpath, { recursive: true }, function(err) { + if (err) reject(err); + resolve(); + }); + }); + }); + } +} +module.exports = Server; diff --git a/test/docs/custom-casting.test.js b/test/docs/custom-casting.test.js index bd9d8484702..601e80e41b0 100644 --- a/test/docs/custom-casting.test.js +++ b/test/docs/custom-casting.test.js @@ -28,7 +28,7 @@ describe('custom casting', function() { // "Cast to Number failed for value "二" at path "age"" err.message; // acquit:ignore:start - assert.ok(err.message.indexOf('Cast to Number failed for value "二" at path "age"') !== -1, err.message); + assert.ok(err.message.indexOf('Cast to Number failed for value "二" (type string) at path "age"') !== -1, err.message); // acquit:ignore:end }); diff --git a/test/docs/promises.test.js b/test/docs/promises.test.js index 6a76d3691cf..ec7b6d1da63 100644 --- a/test/docs/promises.test.js +++ b/test/docs/promises.test.js @@ -24,7 +24,7 @@ describe('promises docs', function() { }); /** - * Mongoose async operations, like `.save()` and queries, return thenables. + * Mongoose async operations, like `.save()` and queries, return [thenables](https://masteringjs.io/tutorials/fundamentals/thenable). * This means that you can do things like `MyModel.findOne({}).then()` and * `await MyModel.findOne({}).exec()` if you're using * [async/await](http://thecodebarbarian.com/80-20-guide-to-async-await-in-node.js.html). @@ -50,9 +50,9 @@ describe('promises docs', function() { }); /** - * [Mongoose queries](http://mongoosejs.com/docs/queries.html) are **not** promises. They have a `.then()` - * function for [co](https://www.npmjs.com/package/co) and async/await as - * a convenience. If you need + * [Mongoose queries](http://mongoosejs.com/docs/queries.html) are **not** promises. They have a + * [`.then()` function](https://masteringjs.io/tutorials/fundamentals/then) for [co](https://www.npmjs.com/package/co) + * and async/await as a convenience. If you need * a fully-fledged promise, use the `.exec()` function. */ it('Queries are not promises', function(done) { @@ -86,7 +86,7 @@ describe('promises docs', function() { }); /** - * Although queries are not promises, queries are [thenables](https://promisesaplus.com/#terminology). + * Although queries are not promises, queries are [thenables](https://masteringjs.io/tutorials/fundamentals/thenable). * That means they have a `.then()` function, so you can use queries as promises with either * promise chaining or [async await](https://asyncawait.net) */ diff --git a/test/docs/schematypes.test.js b/test/docs/schematypes.test.js index f3e454969b9..20acac5da72 100644 --- a/test/docs/schematypes.test.js +++ b/test/docs/schematypes.test.js @@ -61,7 +61,7 @@ describe('schemaTypes', function() { assert.ok(t.validateSync()); assert.equal(t.validateSync().errors['test'].name, 'CastError'); assert.equal(t.validateSync().errors['test'].message, - 'Cast to Int8 failed for value "abc" at path "test"'); + 'Cast to Int8 failed for value "abc" (type string) at path "test"'); assert.equal(t.validateSync().errors['test'].reason.message, 'Int8: abc is not a number'); }); diff --git a/test/docs/validation.test.js b/test/docs/validation.test.js index 50d1268c9df..f257bf38873 100644 --- a/test/docs/validation.test.js +++ b/test/docs/validation.test.js @@ -58,9 +58,9 @@ describe('validation docs', function() { /** * Mongoose has several built-in validators. * - * - All [SchemaTypes](./schematypes.html) have the built-in [required](./api.html#schematype_SchemaType-required) validator. The required validator uses the [SchemaType's `checkRequired()` function](./api.html#schematype_SchemaType-checkRequired) to determine if the value satisfies the required validator. - * - [Numbers](./api.html#schema-number-js) have [`min` and `max`](./schematypes.html#number-validators) validators. - * - [Strings](./api.html#schema-string-js) have [`enum`, `match`, `minLength`, and `maxLength`](./schematypes.html#string-validators) validators. + * - All [SchemaTypes](/docs/schematypes.html) have the built-in [required](./api.html#schematype_SchemaType-required) validator. The required validator uses the [SchemaType's `checkRequired()` function](./api.html#schematype_SchemaType-checkRequired) to determine if the value satisfies the required validator. + * - [Numbers](/docs/schematypes.html#numbers) have [`min` and `max`](./schematypes.html#number-validators) validators. + * - [Strings](/docs/schematypes.html#strings) have [`enum`, `match`, `minLength`, and `maxLength`](./schematypes.html#string-validators) validators. * * Each of the validator links above provide more information about how to enable them and customize their error messages. */ @@ -112,6 +112,50 @@ describe('validation docs', function() { // acquit:ignore:end }); + /** + * You can configure the error message for individual validators in your schema. There are two equivalent + * ways to set the validator error message: + * + * - Array syntax: `min: [6, 'Must be at least 6, got {VALUE}']` + * - Object syntax: `enum: { values: ['Coffee', 'Tea'], message: '{VALUE} is not supported' }` + * + * Mongoose also supports rudimentary templating for error messages. + * Mongoose replaces `{VALUE}` with the value being validated. + */ + + it('Custom Error Messages', function(done) { + const breakfastSchema = new Schema({ + eggs: { + type: Number, + min: [6, 'Must be at least 6, got {VALUE}'], + max: 12 + }, + drink: { + type: String, + enum: { + values: ['Coffee', 'Tea'], + message: '{VALUE} is not supported' + } + } + }); + // acquit:ignore:start + db.deleteModel(/Breakfast/); + // acquit:ignore:end + const Breakfast = db.model('Breakfast', breakfastSchema); + + const badBreakfast = new Breakfast({ + eggs: 2, + drink: 'Milk' + }); + let error = badBreakfast.validateSync(); + assert.equal(error.errors['eggs'].message, + 'Must be at least 6, got 2'); + assert.equal(error.errors['drink'].message, 'Milk is not supported'); + // acquit:ignore:start + done(); + // acquit:ignore:end + }); + /** * A common gotcha for beginners is that the `unique` option for schemas * is *not* a validator. It's a convenient helper for building [MongoDB unique indexes](https://docs.mongodb.com/manual/core/index-unique/). @@ -332,7 +376,7 @@ describe('validation docs', function() { // acquit:ignore:start assert.equal(err.errors['numWheels'].name, 'CastError'); assert.equal(err.errors['numWheels'].message, - 'Cast to Number failed for value "not a number" at path "numWheels"'); + 'Cast to Number failed for value "not a number" (type string) at path "numWheels"'); // acquit:ignore:end }); @@ -583,34 +627,4 @@ describe('validation docs', function() { }); }); }); - - /** - * New in 4.8.0: update validators also run on `$push` and `$addToSet` - */ - - it('On $push and $addToSet', function(done) { - const testSchema = new Schema({ - numbers: [{ type: Number, max: 0 }], - docs: [{ - name: { type: String, required: true } - }] - }); - - const Test = db.model('TestPush', testSchema); - - const update = { - $push: { - numbers: 1, - docs: { name: null } - } - }; - const opts = { runValidators: true }; - Test.updateOne({}, update, opts, function(error) { - assert.ok(error.errors['numbers']); - assert.ok(error.errors['docs']); - // acquit:ignore:start - done(); - // acquit:ignore:end - }); - }); }); diff --git a/test/document.test.js b/test/document.test.js index 6949d55d824..e8b66176a1a 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -2519,16 +2519,24 @@ describe('document', function() { }); }); - it('filters out validation on unmodified paths when validateModifiedOnly set (gh-7421)', function(done) { - const testSchema = new Schema({ title: { type: String, required: true }, other: String }); + it('filters out validation on unmodified paths when validateModifiedOnly set (gh-7421) (gh-9963)', function(done) { + const testSchema = new Schema({ + title: { type: String, required: true }, + other: String, + subdocs: [{ name: { type: String, required: true } }] + }); const Test = db.model('Test', testSchema); - Test.create([{}], { validateBeforeSave: false }, function(createError, docs) { + const doc = { subdocs: [{ name: null }, { name: 'test' }] }; + Test.create([doc], { validateBeforeSave: false }, function(createError, docs) { assert.equal(createError, null); const doc = docs[0]; doc.other = 'something'; - assert.equal(doc.validateSync(undefined, { validateModifiedOnly: true }), null); + doc.subdocs[1].name = 'test2'; + assert.equal(doc.validateSync({ validateModifiedOnly: true }), null); + assert.equal(doc.validateSync('other'), null); + assert.ok(doc.validateSync('other title').errors['title']); doc.save({ validateModifiedOnly: true }, function(error) { assert.equal(error, null); done(); @@ -2545,7 +2553,7 @@ describe('document', function() { assert.equal(createError, null); const doc = docs[0]; doc.title = ''; - assert.ok(doc.validateSync(undefined, { validateModifiedOnly: true }).errors); + assert.ok(doc.validateSync({ validateModifiedOnly: true }).errors); doc.save({ validateModifiedOnly: true }, function(error) { assert.ok(error.errors); done(); @@ -5853,6 +5861,51 @@ describe('document', function() { assert.equal(doc.totalValue, 5); }); + it('calls array getters (gh-9889)', function() { + let called = 0; + const testSchema = new mongoose.Schema({ + arr: [{ + type: 'ObjectId', + ref: 'Doesnt Matter', + get: () => { + ++called; + return 42; + } + }] + }); + + const Test = db.model('Test', testSchema); + + const doc = new Test({ arr: [new mongoose.Types.ObjectId()] }); + assert.deepEqual(doc.toObject({ getters: true }).arr, [42]); + assert.equal(called, 1); + }); + + it('doesnt call setters when init-ing an array (gh-9889)', function() { + let called = 0; + const testSchema = new mongoose.Schema({ + arr: [{ + type: 'ObjectId', + set: v => { + ++called; + return v; + } + }] + }); + + const Test = db.model('Test', testSchema); + + return co(function*() { + let doc = yield Test.create({ arr: [new mongoose.Types.ObjectId()] }); + assert.equal(called, 1); + + called = 0; + doc = yield Test.findById(doc._id); + assert.ok(doc); + assert.equal(called, 0); + }); + }); + it('nested virtuals + nested toJSON (gh-6294)', function() { const schema = mongoose.Schema({ nested: { @@ -7106,11 +7159,13 @@ describe('document', function() { return event.validate(); }); - it('flattenMaps option for toObject() (gh-7274)', function() { + it('flattenMaps option for toObject() (gh-7274) (gh-10486)', function() { + const subSchema = new Schema({ name: String }); + let schema = new Schema({ test: { type: Map, - of: String, + of: subSchema, default: new Map() } }, { versionKey: false }); @@ -7118,13 +7173,14 @@ describe('document', function() { let Test = db.model('Test', schema); let mapTest = new Test({}); - mapTest.test.set('key1', 'value1'); - assert.equal(mapTest.toObject({ flattenMaps: true }).test.key1, 'value1'); + mapTest.test.set('key1', { name: 'value1' }); + // getters: true for gh-10486 + assert.equal(mapTest.toObject({ getters: true, flattenMaps: true }).test.key1.name, 'value1'); schema = new Schema({ test: { type: Map, - of: String, + of: subSchema, default: new Map() } }, { versionKey: false }); @@ -7134,10 +7190,8 @@ describe('document', function() { Test = db.model('Test', schema); mapTest = new Test({}); - mapTest.test.set('key1', 'value1'); - assert.equal(mapTest.toObject({}).test.key1, 'value1'); - - return Promise.resolve(); + mapTest.test.set('key1', { name: 'value1' }); + assert.equal(mapTest.toObject({}).test.key1.name, 'value1'); }); it('`collection` property with strict: false (gh-7276)', function() { @@ -8942,7 +8996,6 @@ describe('document', function() { const err = t.validateSync(); assert.ok(err); assert.ok(err.errors); - assert.ok(err.errors['test']); assert.ok(err.errors['test.1']); }); @@ -9834,4 +9887,645 @@ describe('document', function() { assert.equal(fromDb.arr[0].abc, 'ghi'); }); }); + + it('supports getting a list of populated docs (gh-9702)', function() { + const Child = db.model('Child', Schema({ name: String })); + const Parent = db.model('Parent', { + children: [{ type: ObjectId, ref: 'Child' }], + child: { type: ObjectId, ref: 'Child' } + }); + + return co(function*() { + const c = yield Child.create({ name: 'test' }); + yield Parent.create({ + children: [c._id], + child: c._id + }); + + const p = yield Parent.findOne().populate('children child'); + + p.children; // [{ _id: '...', name: 'test' }] + + assert.equal(p.$getPopulatedDocs().length, 2); + assert.equal(p.$getPopulatedDocs()[0], p.children[0]); + assert.equal(p.$getPopulatedDocs()[0].name, 'test'); + assert.equal(p.$getPopulatedDocs()[1], p.child); + assert.equal(p.$getPopulatedDocs()[1].name, 'test'); + }); + }); + + it('with virtual populate (gh-10148)', function() { + const childSchema = Schema({ name: String, parentId: 'ObjectId' }); + childSchema.virtual('parent', { + ref: 'Parent', + localField: 'parentId', + foreignField: '_id', + justOne: true + }); + const Child = db.model('Child', childSchema); + + const Parent = db.model('Parent', Schema({ name: String })); + + return co(function*() { + const p = yield Parent.create({ name: 'Anakin' }); + yield Child.create({ name: 'Luke', parentId: p._id }); + + const res = yield Child.findOne().populate('parent'); + assert.equal(res.parent.name, 'Anakin'); + const docs = res.$getPopulatedDocs(); + assert.equal(docs.length, 1); + assert.equal(docs[0].name, 'Anakin'); + }); + }); + + it('handles paths named `db` (gh-9798)', function() { + const schema = new Schema({ + db: String + }); + const Test = db.model('Test', schema); + + return co(function*() { + const doc = yield Test.create({ db: 'foo' }); + doc.db = 'bar'; + yield doc.save(); + yield doc.deleteOne(); + + const _doc = yield Test.findOne({ db: 'bar' }); + assert.ok(!_doc); + }); + }); + + it('handles paths named `schema` gh-8798', function() { + const schema = new Schema({ + schema: String, + name: String + }); + const Test = db.model('Test', schema); + + return co(function*() { + const doc = yield Test.create({ schema: 'test', name: 'test' }); + yield doc.save(); + assert.ok(doc); + assert.equal(doc.schema, 'test'); + assert.equal(doc.name, 'test'); + + const fromDb = yield Test.findById(doc); + assert.equal(fromDb.schema, 'test'); + assert.equal(fromDb.name, 'test'); + + doc.schema = 'test2'; + yield doc.save(); + + yield fromDb.remove(); + doc.name = 'test3'; + const err = yield doc.save().then(() => null, err => err); + assert.ok(err); + assert.equal(err.name, 'DocumentNotFoundError'); + }); + }); + + it('handles nested paths named `schema` gh-8798', function() { + const schema = new Schema({ + nested: { + schema: String + }, + name: String + }); + const Test = db.model('Test', schema); + + return co(function*() { + const doc = yield Test.create({ nested: { schema: 'test' }, name: 'test' }); + yield doc.save(); + assert.ok(doc); + assert.equal(doc.nested.schema, 'test'); + assert.equal(doc.name, 'test'); + + const fromDb = yield Test.findById(doc); + assert.equal(fromDb.nested.schema, 'test'); + assert.equal(fromDb.name, 'test'); + + doc.nested.schema = 'test2'; + yield doc.save(); + }); + }); + + it('object setters will be applied for each object in array after populate (gh-9838)', function() { + const updatedElID = '123456789012345678901234'; + + const ElementSchema = new Schema({ + name: 'string', + nested: [{ type: Schema.Types.ObjectId, ref: 'Nested' }] + }); + + const NestedSchema = new Schema({}); + + const Element = db.model('Test', ElementSchema); + const NestedElement = db.model('Nested', NestedSchema); + + return co(function*() { + const nes = new NestedElement({}); + yield nes.save(); + const ele = new Element({ nested: [nes.id], name: 'test' }); + yield ele.save(); + + const ss = yield Element.findById(ele._id).populate({ path: 'nested', model: NestedElement }); + ss.nested = [updatedElID]; + yield ss.save(); + + assert.ok(typeof ss.nested[0] !== 'string'); + assert.equal(ss.nested[0].toHexString(), updatedElID); + }); + }); + it('gh9884', function() { + return co(function*() { + + const obi = new Schema({ + eType: { + type: String, + required: true, + uppercase: true + }, + eOrigin: { + type: String, + required: true + }, + eIds: [ + { + type: String + } + ] + }, { _id: false }); + + const schema = new Schema({ + name: String, + description: String, + isSelected: { + type: Boolean, + default: false + }, + wan: { + type: [obi], + default: undefined, + required: true + } + }); + + const newDoc = { + name: 'name', + description: 'new desc', + isSelected: true, + wan: [ + { + eType: 'X', + eOrigin: 'Y', + eIds: ['Y', 'Z'] + } + ] + }; + + const Model = db.model('Test', schema); + yield Model.create(newDoc); + const doc = yield Model.findOne(); + assert.ok(doc); + }); + }); + + it('Makes sure pre remove hook is executed gh-9885', function() { + const SubSchema = new Schema({ + myValue: { + type: String + } + }, {}); + let count = 0; + SubSchema.pre('remove', function(next) { + count++; + next(); + }); + const thisSchema = new Schema({ + foo: { + type: String, + required: true + }, + mySubdoc: { + type: [SubSchema], + required: true + } + }, { minimize: false, collection: 'test' }); + + const Model = db.model('TestModel', thisSchema); + + return co(function*() { + yield Model.deleteMany({}); // remove all existing documents + const newModel = { + foo: 'bar', + mySubdoc: [{ myValue: 'some value' }] + }; + const document = yield Model.create(newModel); + document.mySubdoc[0].remove(); + yield document.save().catch((error) => { + console.error(error); + }); + assert.equal(count, 1); + }); + }); + + it('gh9880', function(done) { + const testSchema = new Schema({ + prop: String, + nestedProp: { + prop: String + } + }); + const Test = db.model('Test', testSchema); + + new Test({ + prop: 'Test', + nestedProp: null + }).save((err, doc) => { + doc.id; + doc.nestedProp; + + // let's clone this document: + new Test({ + prop: 'Test 2', + nestedProp: doc.nestedProp + }); + + Test.updateOne({ + _id: doc._id + }, { + nestedProp: null + }, (err) => { + assert.ifError(err); + Test.findOne({ + _id: doc._id + }, (err, updatedDoc) => { + assert.ifError(err); + new Test({ + prop: 'Test 3', + nestedProp: updatedDoc.nestedProp + }); + done(); + }); + }); + }); + }); + + it('handles directly setting embedded document array element with projection (gh-9909)', function() { + const schema = Schema({ + elements: [{ + text: String, + subelements: [{ + text: String + }] + }] + }); + + const Test = db.model('Test', schema); + + return co(function*() { + let doc = yield Test.create({ elements: [{ text: 'hello' }] }); + doc = yield Test.findById(doc).select('elements'); + + doc.elements[0].subelements[0] = { text: 'my text' }; + yield doc.save(); + + const fromDb = yield Test.findById(doc).lean(); + assert.equal(fromDb.elements.length, 1); + assert.equal(fromDb.elements[0].subelements.length, 1); + assert.equal(fromDb.elements[0].subelements[0].text, 'my text'); + }); + }); + + it('toObject() uses child schema `flattenMaps` option by default (gh-9995)', function() { + const MapSchema = new Schema({ + value: { type: Number } + }, { _id: false }); + + const ChildSchema = new Schema({ + map: { type: Map, of: MapSchema } + }); + ChildSchema.set('toObject', { flattenMaps: true }); + + const ParentSchema = new Schema({ + child: { type: Schema.ObjectId, ref: 'Child' } + }); + + const ChildModel = db.model('Child', ChildSchema); + const ParentModel = db.model('Parent', ParentSchema); + + return co(function*() { + const childDocument = new ChildModel({ + map: { first: { value: 1 }, second: { value: 2 } } + }); + yield childDocument.save(); + + const parentDocument = new ParentModel({ child: childDocument }); + yield parentDocument.save(); + + const resultDocument = yield ParentModel.findOne().populate('child').exec(); + + let resultObject = resultDocument.toObject(); + assert.ok(resultObject.child.map); + assert.ok(!(resultObject.child.map instanceof Map)); + + resultObject = resultDocument.toObject({ flattenMaps: false }); + assert.ok(resultObject.child.map instanceof Map); + }); + }); + + it('does not double validate paths under mixed objects (gh-10141)', function() { + let validatorCallCount = 0; + const Test = db.model('Test', Schema({ + name: String, + object: { + type: Object, + validate: () => { + validatorCallCount++; + return true; + } + } + })); + + return co(function*() { + const doc = yield Test.create({ name: 'test', object: { answer: 42 } }); + + validatorCallCount = 0; + doc.set('object.question', 'secret'); + doc.set('object.answer', 0); + yield doc.validate(); + assert.equal(validatorCallCount, 0); + }); + }); + + it('clears child document modified when setting map path underneath single nested (gh-10295)', function() { + const SecondMapSchema = new mongoose.Schema({ + data: { type: Map, of: Number, default: {}, _id: false } + }); + + const FirstMapSchema = new mongoose.Schema({ + data: { type: Map, of: SecondMapSchema, default: {}, _id: false } + }); + + const NestedSchema = new mongoose.Schema({ + data: { type: Map, of: SecondMapSchema, default: {}, _id: false } + }); + + const TestSchema = new mongoose.Schema({ + _id: Number, + firstMap: { type: Map, of: FirstMapSchema, default: {}, _id: false }, + nested: { type: NestedSchema, default: {}, _id: false } + }); + + const Test = db.model('Test', TestSchema); + + return co(function*() { + const doc = yield Test.create({ _id: Date.now() }); + + doc.nested.data.set('second', {}); + assert.ok(doc.modifiedPaths().indexOf('nested.data.second') !== -1, doc.modifiedPaths()); + yield doc.save(); + + doc.nested.data.get('second').data.set('final', 3); + assert.ok(doc.modifiedPaths().indexOf('nested.data.second.data.final') !== -1, doc.modifiedPaths()); + yield doc.save(); + + const fromDb = yield Test.findById(doc).lean(); + assert.equal(fromDb.nested.data.second.data.final, 3); + }); + }); + + it('avoids infinite recursion when setting single nested subdoc to array (gh-10351)', function() { + const userInfoSchema = new mongoose.Schema({ _id: String }, { _id: false }); + const observerSchema = new mongoose.Schema({ user: {} }, { _id: false }); + + const entrySchema = new mongoose.Schema({ + creator: userInfoSchema, + observers: [observerSchema] + }); + + entrySchema.pre('save', function(next) { + this.observers = [{ user: this.creator }]; + + next(); + }); + + const Test = db.model('Test', entrySchema); + + return co(function*() { + const entry = new Test({ + creator: { _id: 'u1' } + }); + + yield entry.save(); + + const fromDb = yield Test.findById(entry); + assert.equal(fromDb.observers.length, 1); + }); + }); + + describe('virtuals `pathsToSkip` (gh-10120)', () => { + it('adds support for `pathsToSkip` for virtuals feat-10120', function() { + const schema = new mongoose.Schema({ + name: String, + age: Number, + nested: { + test: String + } + }); + schema.virtual('nameUpper').get(function() { return this.name.toUpperCase(); }); + schema.virtual('answer').get(() => 42); + schema.virtual('nested.hello').get(() => 'world'); + + const Model = db.model('Person', schema); + const doc = new Model({ name: 'Jean-Luc Picard', age: 59, nested: { test: 'hello' } }); + let obj = doc.toObject({ virtuals: { pathsToSkip: ['answer'] } }); + assert.ok(obj.nameUpper); + assert.equal(obj.answer, null); + assert.equal(obj.nested.hello, 'world'); + obj = doc.toObject({ virtuals: { pathsToSkip: ['nested.hello'] } }); + assert.equal(obj.nameUpper, 'JEAN-LUC PICARD'); + assert.equal(obj.answer, 42); + assert.equal(obj.nested.hello, null); + }); + + it('supports passing a list of virtuals to `toObject()` (gh-10120)', function() { + const schema = new mongoose.Schema({ + name: String, + age: Number, + nested: { + test: String + } + }); + schema.virtual('nameUpper').get(function() { return this.name.toUpperCase(); }); + schema.virtual('answer').get(() => 42); + schema.virtual('nested.hello').get(() => 'world'); + + const Model = db.model('Person', schema); + + const doc = new Model({ name: 'Jean-Luc Picard', age: 59, nested: { test: 'hello' } }); + + let obj = doc.toObject({ virtuals: true }); + assert.equal(obj.nameUpper, 'JEAN-LUC PICARD'); + assert.equal(obj.answer, 42); + assert.equal(obj.nested.hello, 'world'); + + obj = doc.toObject({ virtuals: ['answer'] }); + assert.ok(!obj.nameUpper); + assert.equal(obj.answer, 42); + assert.equal(obj.nested.hello, null); + + obj = doc.toObject({ virtuals: ['nameUpper'] }); + assert.equal(obj.nameUpper, 'JEAN-LUC PICARD'); + assert.equal(obj.answer, null); + assert.equal(obj.nested.hello, null); + + obj = doc.toObject({ virtuals: ['nested.hello'] }); + assert.equal(obj.nameUpper, null); + assert.equal(obj.answer, null); + assert.equal(obj.nested.hello, 'world'); + }); + }); + describe('validation `pathsToSkip` (gh-10230)', () => { + it('support `pathsToSkip` option for `Document#validate()`', function() { + return co(function*() { + const User = getUserModel(); + const user = new User(); + + const err1 = yield user.validate({ pathsToSkip: ['age'] }).then(() => null, err => err); + assert.deepEqual(Object.keys(err1.errors), ['name']); + + const err2 = yield user.validate({ pathsToSkip: ['name'] }).then(() => null, err => err); + assert.deepEqual(Object.keys(err2.errors), ['age']); + }); + }); + + it('support `pathsToSkip` option for `Document#validate()`', function() { + return co(function*() { + const User = getUserModel(); + const user = new User(); + + const err1 = yield user.validate({ pathsToSkip: ['age'] }).then(() => null, err => err); + assert.deepEqual(Object.keys(err1.errors), ['name']); + + const err2 = yield user.validate({ pathsToSkip: ['name'] }).then(() => null, err => err); + assert.deepEqual(Object.keys(err2.errors), ['age']); + }); + }); + + it('support `pathsToSkip` option for `Document#validateSync()`', () => { + const User = getUserModel(); + + const user = new User(); + + const err1 = user.validateSync({ pathsToSkip: ['age'] }); + assert.deepEqual(Object.keys(err1.errors), ['name']); + + const err2 = user.validateSync({ pathsToSkip: ['name'] }); + assert.deepEqual(Object.keys(err2.errors), ['age']); + }); + + // skip until gh-10367 is implemented + xit('support `pathsToSkip` option for `Model.validate()`', () => { + return co(function*() { + const User = getUserModel(); + const err1 = yield User.validate({}, { pathsToSkip: ['age'] }); + assert.deepEqual(Object.keys(err1.errors), ['name']); + + const err2 = yield User.validate({}, { pathsToSkip: ['name'] }); + assert.deepEqual(Object.keys(err2.errors), ['age']); + }); + }); + + it('`pathsToSkip` accepts space separated paths', () => { + const userSchema = Schema({ + name: { type: String, required: true }, + age: { type: Number, required: true }, + country: { type: String, required: true }, + rank: { type: String, required: true } + }); + + const User = db.model('User', userSchema); + return co(function* () { + const user = new User({ name: 'Sam', age: 26 }); + + const err1 = user.validateSync({ pathsToSkip: 'country rank' }); + assert.ok(err1 == null); + + const err2 = yield user.validate({ pathsToSkip: 'country rank' }).then(() => null, err => err); + assert.ok(err2 == null); + }); + }); + + + function getUserModel() { + const userSchema = Schema({ + name: { type: String, required: true }, + age: { type: Number, required: true }, + rank: String + }); + + const User = db.model('User', userSchema); + return User; + } + }); + + it('does not pull non-schema paths from parent documents into nested paths (gh-10449)', function() { + const schema = new Schema({ + name: String, + nested: { + data: String + } + }); + const Test = db.model('Test', schema); + + const doc = new Test({}); + doc.otherProp = 'test'; + + assert.ok(!doc.nested.otherProp); + }); + + it('depopulate all should depopulate nested array population (gh-10592)', function() { + + const Person = db.model('Person', { + name: String + }); + + const Band = db.model('Band', { + name: String, + members: [{ type: Schema.Types.ObjectId, ref: 'Person' }], + lead: { type: Schema.Types.ObjectId, ref: 'Person' }, + embeddedMembers: [{ + active: Boolean, + member: { + type: Schema.Types.ObjectId, ref: 'Person' + } + }] + }); + + return co(function*() { + const people = [{ name: 'Axl Rose' }, { name: 'Slash' }]; + + const docs = yield Person.create(people); + let band = { + name: 'Guns N\' Roses', + members: [docs[0]._id, docs[1]], + lead: docs[0]._id, + embeddedMembers: [{ active: true, member: docs[0]._id }, { active: false, member: docs[1]._id }] + }; + band = yield Band.create(band); + yield band.populate('members lead').populate('embeddedMembers.member').execPopulate(); + assert.ok(band.populated('members')); + assert.ok(band.populated('lead')); + assert.ok(band.populated('embeddedMembers.member')); + assert.equal(band.members[0].name, 'Axl Rose'); + assert.equal(band.embeddedMembers[0].member.name, 'Axl Rose'); + band.depopulate(); + + assert.ok(!band.populated('members')); + assert.ok(!band.populated('lead')); + assert.ok(!band.populated('embeddedMembers.member')); + assert.ok(!band.embeddedMembers[0].member.name); + }); + }); }); diff --git a/test/document.unit.test.js b/test/document.unit.test.js index bc7fbd3bc50..0ebdc16dc1b 100644 --- a/test/document.unit.test.js +++ b/test/document.unit.test.js @@ -19,7 +19,7 @@ describe('sharding', function() { } }; const Stub = function() { - this.schema = mockSchema; + this.$__schema = mockSchema; this.$__ = {}; }; Stub.prototype.__proto__ = mongoose.Document.prototype; @@ -37,7 +37,7 @@ describe('toObject()', function() { beforeEach(function() { Stub = function() { - const schema = this.schema = { + const schema = this.$__schema = { options: { toObject: { minimize: false, virtuals: true } }, virtuals: { virtual: 'test' } }; diff --git a/test/errors.validation.test.js b/test/errors.validation.test.js index 34c9c9f7127..ebb884a6c97 100644 --- a/test/errors.validation.test.js +++ b/test/errors.validation.test.js @@ -243,17 +243,29 @@ describe('ValidationError', function() { }); describe('when user code defines a r/o Error#toJSON', function() { - it('shoud not fail', function() { + it('should not fail', function(done) { const err = []; const child = require('child_process') - .fork('./test/isolated/project-has-error.toJSON.js', { silent: true }); + .fork('./test/isolated/project-has-error.toJSON.js', ['--no-warnings'], { silent: true }); child.stderr.on('data', function(buf) { err.push(buf); }); child.on('exit', function(code) { - const stderr = err.join(''); + const stderr = err.filter(line => !line.includes('Warning: ')).join(''); assert.equal(stderr, ''); assert.equal(code, 0); + done(); }); }); }); + it('should have error name in Cast error gh-10166', function(done) { + const zetaSchema = new Schema({ text: { type: String, required: [true, 'Text is required'] }, number: { + type: Number, required: [true, 'Number is required'] } }); + const Zeta = mongoose.model('Zeta', zetaSchema); + const entry = new Zeta({ text: false, number: 'fsfsf' }); + entry.validate(function(error) { + assert.ok(JSON.parse(JSON.stringify(error.errors.number.message))); + assert.ok(JSON.parse(JSON.stringify(error.errors.number.name))); + }); + done(); + }); }); diff --git a/test/es-next/cast.test.es6.js b/test/es-next/cast.test.es6.js index 92c76b6d05c..563cdeff0ab 100644 --- a/test/es-next/cast.test.es6.js +++ b/test/es-next/cast.test.es6.js @@ -88,7 +88,7 @@ describe('Cast Tutorial', function() { // acquit:ignore:start assert.ok(err instanceof mongoose.CastError); assert.equal(err.message, 'Cast to Number failed for value "not a ' + - 'number" at path "age" for model "Character"'); + 'number" (type string) at path "age" for model "Character"'); // acquit:ignore:end }); diff --git a/test/es-next/transactions.test.es6.js b/test/es-next/transactions.test.es6.js index 1158361c072..c12bb7af763 100644 --- a/test/es-next/transactions.test.es6.js +++ b/test/es-next/transactions.test.es6.js @@ -127,25 +127,25 @@ describe('transactions', function() { await User.createCollection(); // acquit:ignore:end const session = await db.startSession(); - session.startTransaction(); - - await User.create({ name: 'foo' }); - - const user = await User.findOne({ name: 'foo' }).session(session); - // Getter/setter for the session associated with this document. - assert.ok(user.$session()); - user.name = 'bar'; - // By default, `save()` uses the associated session - await user.save(); - // Won't find the doc because `save()` is part of an uncommitted transaction - let doc = await User.findOne({ name: 'bar' }); - assert.ok(!doc); + await session.withTransaction(async () => { + await User.create({ name: 'foo' }); + + const user = await User.findOne({ name: 'foo' }).session(session); + // Getter/setter for the session associated with this document. + assert.ok(user.$session()); + user.name = 'bar'; + // By default, `save()` uses the associated session + await user.save(); + + // Won't find the doc because `save()` is part of an uncommitted transaction + const doc = await User.findOne({ name: 'bar' }); + assert.ok(!doc); + }); - await session.commitTransaction(); session.endSession(); - doc = await User.findOne({ name: 'bar' }); + const doc = await User.findOne({ name: 'bar' }); assert.ok(doc); }); @@ -174,35 +174,35 @@ describe('transactions', function() { await Event.createCollection(); // acquit:ignore:end const session = await db.startSession(); - session.startTransaction(); - - await Event.insertMany([ - { createdAt: new Date('2018-06-01') }, - { createdAt: new Date('2018-06-02') }, - { createdAt: new Date('2017-06-01') }, - { createdAt: new Date('2017-05-31') } - ], { session: session }); - - const res = await Event.aggregate([ - { - $group: { - _id: { - month: { $month: '$createdAt' }, - year: { $year: '$createdAt' } - }, - count: { $sum: 1 } - } - }, - { $sort: { count: -1, '_id.year': -1, '_id.month': -1 } } - ]).session(session); - - assert.deepEqual(res, [ - { _id: { month: 6, year: 2018 }, count: 2 }, - { _id: { month: 6, year: 2017 }, count: 1 }, - { _id: { month: 5, year: 2017 }, count: 1 } - ]); + + await session.withTransaction(async () => { + await Event.insertMany([ + { createdAt: new Date('2018-06-01') }, + { createdAt: new Date('2018-06-02') }, + { createdAt: new Date('2017-06-01') }, + { createdAt: new Date('2017-05-31') } + ], { session: session }); + + const res = await Event.aggregate([ + { + $group: { + _id: { + month: { $month: '$createdAt' }, + year: { $year: '$createdAt' } + }, + count: { $sum: 1 } + } + }, + { $sort: { count: -1, '_id.year': -1, '_id.month': -1 } } + ]).session(session); + + assert.deepEqual(res, [ + { _id: { month: 6, year: 2018 }, count: 2 }, + { _id: { month: 6, year: 2017 }, count: 1 }, + { _id: { month: 5, year: 2017 }, count: 1 } + ]); + }); - await session.commitTransaction(); session.endSession(); }); @@ -372,12 +372,15 @@ describe('transactions', function() { it('can save document after aborted transaction (gh-8380)', async function() { const schema = Schema({ name: String, arr: [String], arr2: [String] }); + // acquit:ignore:start + db.deleteModel(/Test/); + // acquit:ignore:end - const Test = db.model('gh8380', schema); + const Test = db.model('Test', schema); await Test.createCollection(); - await Test.create({ name: 'foo', arr: ['bar'], arr2: ['foo'] }); - const doc = await Test.findOne(); + let doc = await Test.create({ name: 'foo', arr: ['bar'], arr2: ['foo'] }); + doc = await Test.findById(doc); await db. transaction(async (session) => { doc.arr.pull('bar'); @@ -391,7 +394,7 @@ describe('transactions', function() { assert.equal(err.message, 'Oops'); }); - const changes = doc.$__delta()[1]; + const changes = doc.getChanges(); assert.equal(changes.$set.name, 'baz'); assert.deepEqual(changes.$pullAll.arr, ['bar']); assert.deepEqual(changes.$push.arr2, { $each: ['bar'] }); @@ -399,7 +402,7 @@ describe('transactions', function() { await doc.save({ session: null }); - const newDoc = await Test.collection.findOne(); + const newDoc = await Test.findById(doc); assert.equal(newDoc.name, 'baz'); assert.deepEqual(newDoc.arr, []); assert.deepEqual(newDoc.arr2, ['foo', 'bar']); @@ -408,7 +411,7 @@ describe('transactions', function() { it('can save a new document with an array', async function () { const schema = Schema({ arr: [String] }); - const Test = db.model('new_doc_array', schema); + const Test = db.model('new_doc_array1', schema); await Test.createCollection(); const doc = new Test({ arr: ['foo'] }); @@ -419,4 +422,37 @@ describe('transactions', function() { const createdDoc = await Test.collection.findOne(); assert.deepEqual(createdDoc.arr, ['foo']); }); + + it('can save a new document with an array and read within transaction', async function () { + const schema = Schema({ arr: [String] }); + + const Test = db.model('new_doc_array2', schema); + + await Test.createCollection(); + const doc = new Test({ arr: ['foo'] }); + await db.transaction( + async (session) => { + await doc.save({ session }); + const testDocs = await Test.find({}).session(session); + assert.deepStrictEqual(testDocs.length, 1); + }, + { readPreference: 'primary' } + ); + }); + + it('does not throw an error if using document after session has ended (gh-10306)', async function () { + const schema = Schema({ arr: [String] }); + + const Test = db.model('new_doc_array3', schema); + + await Test.createCollection(); + + const session = await db.startSession(); + + const doc = new Test({ arr: ['foo'] }); + doc.$session(session); + await session.endSession(); + + await doc.save(); + }); }); diff --git a/test/helpers/aggregate.test.js b/test/helpers/aggregate.test.js index c3683cea959..54f5702d553 100644 --- a/test/helpers/aggregate.test.js +++ b/test/helpers/aggregate.test.js @@ -1,9 +1,9 @@ 'use strict'; const assert = require('assert'); -const stringifyAccumulatorOptions = require('../../lib/helpers/aggregate/stringifyAccumulatorOptions'); +const stringifyFunctionOperators = require('../../lib/helpers/aggregate/stringifyFunctionOperators'); -describe('stringifyAccumulatorOptions', function() { +describe('stringifyFunctionOperators', function() { it('converts accumulator args to strings (gh-9364)', function() { const pipeline = [{ $group: { @@ -35,11 +35,31 @@ describe('stringifyAccumulatorOptions', function() { } }]; - stringifyAccumulatorOptions(pipeline); + stringifyFunctionOperators(pipeline); assert.equal(typeof pipeline[0].$group.avgCopies.$accumulator.init, 'string'); assert.equal(typeof pipeline[0].$group.avgCopies.$accumulator.accumulate, 'string'); assert.equal(typeof pipeline[0].$group.avgCopies.$accumulator.merge, 'string'); assert.equal(typeof pipeline[0].$group.avgCopies.$accumulator.finalize, 'string'); }); + + it('converts function args to strings (gh-9897)', function() { + const pipeline = [{ + $addFields: { + newField: { + $function: { + body: function() { + return true; + }, + args: ['$oldField'], + lang: 'js' + } + } + } + }]; + + stringifyFunctionOperators(pipeline); + + assert.equal(typeof pipeline[0].$addFields.newField.$function.body, 'string'); + }); }); \ No newline at end of file diff --git a/test/helpers/cursor.eachAsync.test.js b/test/helpers/cursor.eachAsync.test.js index b7d847b055a..5ea083ef3e5 100644 --- a/test/helpers/cursor.eachAsync.test.js +++ b/test/helpers/cursor.eachAsync.test.js @@ -59,4 +59,79 @@ describe('eachAsync()', function() { then(() => eachAsync(next, fn, { parallel: 2 })). then(() => assert.equal(numDone, max)); }); + + it('it processes the documents in batches successfully', () => { + const batchSize = 3; + let numberOfDocuments = 0; + const maxNumberOfDocuments = 9; + let numberOfBatchesProcessed = 0; + + function next(cb) { + setTimeout(() => { + if (++numberOfDocuments > maxNumberOfDocuments) { + cb(null, null); + } + return cb(null, { id: numberOfDocuments }); + }, 0); + } + + const fn = (docs, index) => { + assert.equal(docs.length, batchSize); + assert.equal(index, numberOfBatchesProcessed++); + }; + + return eachAsync(next, fn, { batchSize }); + }); + + it('it processes the documents in batches even if the batch size % document count is not zero successfully', () => { + const batchSize = 3; + let numberOfDocuments = 0; + const maxNumberOfDocuments = 10; + let numberOfBatchesProcessed = 0; + + function next(cb) { + setTimeout(() => { + if (++numberOfDocuments > maxNumberOfDocuments) { + cb(null, null); + } + return cb(null, { id: numberOfDocuments }); + }, 0); + } + + const fn = (docs, index) => { + assert.equal(index, numberOfBatchesProcessed++); + if (index == 3) { + assert.equal(docs.length, 1); + } + else { + assert.equal(docs.length, batchSize); + } + }; + + return eachAsync(next, fn, { batchSize }); + }); + + it('it processes the documents in batches with the parallel option provided', () => { + const batchSize = 3; + const parallel = 3; + let numberOfDocuments = 0; + const maxNumberOfDocuments = 9; + let numberOfBatchesProcessed = 0; + + function next(cb) { + setTimeout(() => { + if (++numberOfDocuments > maxNumberOfDocuments) { + cb(null, null); + } + return cb(null, { id: numberOfDocuments }); + }, 0); + } + + const fn = (docs, index) => { + assert.equal(index, numberOfBatchesProcessed++); + assert.equal(docs.length, batchSize); + }; + + return eachAsync(next, fn, { batchSize, parallel }); + }); }); \ No newline at end of file diff --git a/test/helpers/populate.getSchemaTypes.test.js b/test/helpers/populate.getSchemaTypes.test.js index b13dd9da84f..20ba5dc8718 100644 --- a/test/helpers/populate.getSchemaTypes.test.js +++ b/test/helpers/populate.getSchemaTypes.test.js @@ -166,4 +166,22 @@ describe('getSchemaTypes', function() { done(); }); + + it('handles embedded discriminators in nested arrays (gh-9984)', function() { + const mapSchema = new Schema({ + tiles: [[new Schema({}, { discriminatorKey: 'kind', _id: false })]] + }); + + const contentPath = mapSchema.path('tiles'); + + contentPath.discriminator('Enemy', new Schema({ + enemy: { type: Schema.Types.ObjectId, ref: 'Enemy' } + })); + contentPath.discriminator('Wall', new Schema({ color: String })); + + const schemaTypes = getSchemaTypes(mapSchema, null, 'tiles.enemy'); + assert.ok(Array.isArray(schemaTypes)); + assert.equal(schemaTypes.length, 1); + assert.equal(schemaTypes[0].options.ref, 'Enemy'); + }); }); diff --git a/test/helpers/query.test.js b/test/helpers/query.test.js new file mode 100644 index 00000000000..ad71b4f05a1 --- /dev/null +++ b/test/helpers/query.test.js @@ -0,0 +1,45 @@ +'use strict'; + +require('../common'); + +const assert = require('assert'); +const selectPopulatedFields = require('../../lib/helpers/query/selectPopulatedFields'); + +describe('Query helpers', function() { + describe('selectPopulatedFields', function() { + it('handles nested populate if parent key is projected in (gh-5669)', function(done) { + const fields = { nested: 1 }; + selectPopulatedFields(fields, { nested: 1 }, { 'nested.key1': true }); + + assert.deepEqual(fields, { nested: 1 }); + + done(); + }); + + it('handles nested populate if parent key is projected out (gh-5669)', function(done) { + const fields = { nested: 0 }; + selectPopulatedFields(fields, { nested: 0 }, { 'nested.key1': true }); + + assert.deepEqual(fields, { nested: 0 }); + + done(); + }); + + it('handle explicitly excluded paths (gh-7383)', function(done) { + const fields = { name: 1, other: 0 }; + selectPopulatedFields(fields, Object.assign({}, fields), { other: 1 }); + + assert.deepEqual(fields, { name: 1 }); + + done(); + }); + + it('handles paths selected with elemMatch (gh-9973)', function(done) { + const fields = { 'arr.$': 1 }; + selectPopulatedFields(fields, Object.assign({}, fields), { 'arr.el': 1 }); + assert.deepEqual(fields, { 'arr.$': 1 }); + + done(); + }); + }); +}); diff --git a/test/helpers/schema.cleanPositionalOperators.test.js b/test/helpers/schema.cleanPositionalOperators.test.js new file mode 100644 index 00000000000..e22a7d89015 --- /dev/null +++ b/test/helpers/schema.cleanPositionalOperators.test.js @@ -0,0 +1,22 @@ +'use strict'; + +const assert = require('assert'); +const cleanPositionalOperators = require('../../lib/helpers/schema/cleanPositionalOperators'); + +describe('cleanPositionalOperators', function() { + it('replaces trailing array filter', function() { + assert.equal(cleanPositionalOperators('questions.$[q]'), 'questions.0'); + }); + + it('replaces trailing $', function() { + assert.equal(cleanPositionalOperators('questions.$'), 'questions.0'); + }); + + it('replaces interior array filters', function() { + assert.equal(cleanPositionalOperators('questions.$[q].$[r].test'), 'questions.0.0.test'); + }); + + it('replaces interior elemMatch', function() { + assert.equal(cleanPositionalOperators('questions.$.$.test'), 'questions.0.0.test'); + }); +}); \ No newline at end of file diff --git a/test/index.test.js b/test/index.test.js index 322fa4df39d..3a78fb4e88f 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -530,8 +530,14 @@ describe('mongoose module:', function() { cb(); }); + const events = []; + mong.events.on('createConnection', conn => events.push(conn)); + const db2 = mong.createConnection(process.env.MONGOOSE_TEST_URI || uri, options); + assert.equal(events.length, 1); + assert.equal(events[0], db2); + db2.on('open', function() { connections++; cb(); @@ -812,5 +818,13 @@ describe('mongoose module:', function() { assert.ok(!movie.genre); }); }); + it('should prevent non-hexadecimal strings (gh-9996)', function() { + const badIdString = 'z'.repeat(24); + assert.deepStrictEqual(mongoose.isValidObjectId(badIdString), false); + const goodIdString = '1'.repeat(24); + assert.deepStrictEqual(mongoose.isValidObjectId(goodIdString), true); + const goodIdString2 = '1'.repeat(12); + assert.deepStrictEqual(mongoose.isValidObjectId(goodIdString2), true); + }); }); }); \ No newline at end of file diff --git a/test/model.discriminator.querying.test.js b/test/model.discriminator.querying.test.js index 4cd246f3b3e..a1bbf6155e5 100644 --- a/test/model.discriminator.querying.test.js +++ b/test/model.discriminator.querying.test.js @@ -871,6 +871,43 @@ describe('model', function() { assert.ok(events[0].surveys[0] instanceof Survey); }); }); + + it('correctly populates doc with nonexistent discriminator key (gh-10082)', function() { + const foodSchema = Schema({ name: String, animal: String }); + const Food = db.model('Food', foodSchema); + + const animalSchema = Schema({ type: String }, { + discriminatorKey: 'type', + toJSON: { virtuals: true }, + toObject: { virtuals: true } + }); + const catSchema = Schema({ catYears: Number }); + animalSchema.virtual('foods', { + ref: 'Food', + localField: 'type', + foreignField: 'animal', + justOne: false + }); + const Animal = db.model('Animal', animalSchema); + Animal.discriminator('cat', catSchema); + // const Rabbit = Animal.discriminator('rabbit', rabbitSchema); + + return co(function*() { + yield Promise.all([ + new Food({ name: 'Cat Food', animal: 'cat' }).save(), + new Food({ name: 'Rabbit Food', animal: 'rabbit' }).save() + ]); + yield Animal.collection.insertOne({ type: 'cat', catYears: 4 }); + yield Animal.collection.insertOne({ type: 'rabbit' }); // <-- "rabbit" has no discriminator + + const cat = yield Animal.findOne({ type: 'cat' }).populate('foods'); + const rabbit = yield Animal.findOne({ type: 'rabbit' }).populate('foods'); + assert.equal(cat.foods.length, 1); + assert.equal(cat.foods[0].name, 'Cat Food'); + assert.equal(rabbit.foods.length, 1); + assert.equal(rabbit.foods[0].name, 'Rabbit Food'); + }); + }); }); describe('deleteOne and deleteMany (gh-8471)', function() { diff --git a/test/model.discriminator.test.js b/test/model.discriminator.test.js index 02b46d5ac7f..7cba3e97551 100644 --- a/test/model.discriminator.test.js +++ b/test/model.discriminator.test.js @@ -1094,6 +1094,35 @@ describe('model', function() { }); }); + it('supports ObjectId as tied value (gh-10130)', function() { + const eventSchema = new Schema({ message: String, kind: 'ObjectId' }, + { discriminatorKey: 'kind' }); + + const Event = db.model('Event', eventSchema); + const clickedId = new mongoose.Types.ObjectId(); + const purchasedId = new mongoose.Types.ObjectId(); + Event.discriminator('Clicked', new Schema({ + element: String + }), clickedId); + Event.discriminator('Purchased', new Schema({ + product: String + }), purchasedId); + + return co(function*() { + yield Event.create([ + { message: 'test', element: '#buy', kind: clickedId }, + { message: 'test2', product: 'Turbo Man', kind: purchasedId } + ]); + + const docs = yield Event.find().sort({ message: 1 }); + assert.equal(docs.length, 2); + assert.equal(docs[0].kind.toHexString(), clickedId.toHexString()); + assert.equal(docs[0].element, '#buy'); + assert.equal(docs[1].kind.toHexString(), purchasedId.toHexString()); + assert.equal(docs[1].product, 'Turbo Man'); + }); + }); + it('Embedded discriminators in nested doc arrays (gh-6202)', function() { const eventSchema = new Schema({ message: String }, { discriminatorKey: 'kind', @@ -1646,6 +1675,41 @@ describe('model', function() { assert.ok(actions.schema.discriminators['message']); }); + it('embedded discriminator array of arrays (gh-9984)', function() { + const enemySchema = new Schema({ + name: String, + level: Number + }); + const Enemy = db.model('Enemy', enemySchema); + + const mapSchema = new Schema({ + tiles: [[new Schema({}, { discriminatorKey: 'kind', _id: false })]] + }); + + const contentPath = mapSchema.path('tiles'); + + contentPath.discriminator('Enemy', new Schema({ + enemy: { type: Schema.Types.ObjectId, ref: 'Enemy' } + })); + contentPath.discriminator('Wall', new Schema({ color: String })); + + const Map = db.model('Map', mapSchema); + + return co(function*() { + const e = yield Enemy.create({ + name: 'Bowser', + level: 10 + }); + + let map = yield Map.create({ + tiles: [[{ kind: 'Enemy', enemy: e._id }, { kind: 'Wall', color: 'Blue' }]] + }); + + map = yield Map.findById(map).populate({ path: 'tiles.enemy' }); + assert.equal(map.tiles[0][0].enemy.name, 'Bowser'); + }); + }); + it('recursive embedded discriminator using schematype (gh-9600)', function() { const contentSchema = new mongoose.Schema({}, { discriminatorKey: 'type' }); const nestedSchema = new mongoose.Schema({ @@ -1685,4 +1749,51 @@ describe('model', function() { assert.deepEqual(nestedDocument.body.children[1].body.children[0].body.children[0].body.children, []); }); + + it('takes discriminator schema\'s single nested over base schema\'s (gh-10157)', function() { + const personSchema = new Schema({ + name: Schema({ firstName: String, lastName: String }), + kind: { type: 'String', enum: ['normal', 'vip'], required: true } + }, { discriminatorKey: 'kind' }); + + const Person = db.model('Person', personSchema); + + const vipSchema = Schema({ + name: Schema({ + firstName: { type: 'String', required: true }, + title: { type: 'String', required: true } + }) + }); + const Vip = Person.discriminator('vip', vipSchema); + + const doc1 = new Vip({ name: { firstName: 'John' } }); + let err = doc1.validateSync(); + assert.ok(err); + assert.ok(err.errors['name.title']); + + const doc2 = new Vip({ name: { title: 'Dr' } }); + err = doc2.validateSync(); + assert.ok(err); + assert.ok(err.errors['name.firstName']); + }); + + it('allows using array as tied value (gh-10303)', function() { + const mongooseSchema = new mongoose.Schema({ + tenantRefs: [{ + type: String, + required: true + }], + otherData: { + type: String, + required: true + } + }, { discriminatorKey: 'tenantRefs' }); + const Model = db.model('Test', mongooseSchema); + + const D = Model.discriminator('D', Schema({}), ['abc', '123']); + + return D.create({ otherData: 'test', tenantRefs: ['abc', '123'] }).then(res => { + assert.deepEqual(res.toObject().tenantRefs, ['abc', '123']); + }); + }); }); diff --git a/test/model.findOneAndUpdate.test.js b/test/model.findOneAndUpdate.test.js index 8502ea10227..19bc3612fa2 100644 --- a/test/model.findOneAndUpdate.test.js +++ b/test/model.findOneAndUpdate.test.js @@ -1691,7 +1691,7 @@ describe('model: findOneAndUpdate:', function() { Person.findOneAndUpdate({}, update, function(error) { assert.ok(error.message.indexOf('street') !== -1); assert.equal(error.reason.message, - 'Cast to Number failed for value "not a num" at path "street"'); + 'Cast to Number failed for value "not a num" (type string) at path "street"'); done(); }); }); @@ -2495,4 +2495,42 @@ describe('model: findOneAndUpdate:', function() { assert.equal(doc.jobCategory.value, 'from setter 2'); }); }); + + it('returnDocument should work (gh-10321)', function() { + const testSchema = Schema({ a: Number }); + const Model = db.model('Test', testSchema); + + const opts = { returnDocument: 'after' }; + return co(function*() { + const tmp = yield Model.create({ a: 1 }); + const doc = yield Model.findOneAndUpdate({ _id: tmp._id }, { a: 2 }, opts).lean(); + assert.equal(doc.a, 2); + }); + }); + + it('supports overwriting nested map paths (gh-10485)', function() { + const child = new mongoose.Schema({ + vals: { + type: mongoose.Schema.Types.Map, + of: String + } + }); + + const parent = new mongoose.Schema({ + children: { + type: [child] + } + }); + const Parent = db.model('Parent', parent); + + return co(function*() { + const parent = yield Parent.create({ + children: [{ vals: { github: 'hello', twitter: 'world' } }] + }); + + const res = yield Parent.findOneAndUpdate({ _id: parent._id, 'children.vals.github': { $exists: true } }, + { $set: { 'children.$.vals': { telegram: 'hello' } } }, { new: true }); + assert.deepEqual(res.toObject().children[0].vals, new Map([['telegram', 'hello']])); + }); + }); }); diff --git a/test/model.indexes.test.js b/test/model.indexes.test.js index 107aabe52ed..e00e52c08bf 100644 --- a/test/model.indexes.test.js +++ b/test/model.indexes.test.js @@ -552,6 +552,30 @@ describe('model', function() { then(dropped => assert.equal(dropped.length, 0)); }); + it('uses schema-level collation by default (gh-9912)', function() { + return co(function*() { + yield db.db.collection('User').drop().catch(() => {}); + + const userSchema = new mongoose.Schema({ username: String }, { + collation: { + locale: 'en', + strength: 2 + } + }); + userSchema.index({ username: 1 }, { unique: true }); + const User = db.model('User', userSchema, 'User'); + + yield User.init(); + const indexes = yield User.listIndexes(); + assert.equal(indexes.length, 2); + assert.deepEqual(indexes[1].key, { username: 1 }); + assert.ok(indexes[1].collation); + assert.equal(indexes[1].collation.strength, 2); + + yield User.collection.drop(); + }); + }); + it('different collation with syncIndexes() (gh-8521)', function() { return co(function*() { yield db.db.collection('User').drop().catch(() => {}); @@ -642,5 +666,36 @@ describe('model', function() { ]); }); }); + it('should prevent collation on text indexes (gh-10044)', function() { + return co(function*() { + const userSchema = new mongoose.Schema({ username: String }, { + collation: { + locale: 'en', + strength: 2 + } + }); + userSchema.index({ username: 'text' }, { unique: true }); + const User = db.model('User', userSchema, 'User'); + + yield User.init(); + const indexes = yield User.listIndexes(); + assert.ok(!indexes[1].collation); + yield User.collection.drop(); + }); + }); + it('should do a dryRun feat-10316', function() { + return co(function*() { + const userSchema = new mongoose.Schema({ username: String }, { password: String }, { email: String }); + const User = db.model('Upson', userSchema); + yield User.collection.createIndex({ age: 1 }); + yield User.collection.createIndex({ weight: 1 }); + yield User.init(); + userSchema.index({ password: 1 }); + userSchema.index({ email: 1 }); + const result = yield User.diffIndexes(); + assert.deepStrictEqual(result.toDrop, ['age_1', 'weight_1']); + assert.deepStrictEqual(result.toCreate, [{ password: 1 }, { email: 1 }]); + }); + }); }); }); diff --git a/test/model.populate.test.js b/test/model.populate.test.js index 4e93ee4984c..e1621e6007b 100644 --- a/test/model.populate.test.js +++ b/test/model.populate.test.js @@ -4500,6 +4500,57 @@ describe('model: populate:', function() { catch(done); }); + it('in embedded array with sort (gh-10552)', function() { + const AppMenuItemSchema = new Schema({ + appId: 'ObjectId', + moduleId: Number, + title: String, + parent: { + type: mongoose.ObjectId, + ref: 'AppMenuItem' + }, + order: Number + }); + + const moduleSchema = new Schema({ + _id: Number, + title: { type: String }, + hidden: { type: Boolean } + }); + + moduleSchema.virtual('menu', { + ref: 'Test1', + localField: '_id', + foreignField: 'moduleId', + options: { sort: { title: 1 } } + }); + + const appSchema = new Schema({ + modules: [moduleSchema] + }); + + const App = db.model('Test', appSchema); + const AppMenuItem = db.model('Test1', AppMenuItemSchema); + + return co(function*() { + let app = yield App.create({ modules: [{ _id: 1, title: 'File' }, { _id: 2, title: 'Preferences' }] }); + yield AppMenuItem.create([ + { title: 'Save', moduleId: 1 }, + { title: 'Save As', moduleId: 1 }, + { title: 'Undo', moduleId: 2 }, + { title: 'Redo', moduleId: 2 } + ]); + + app = yield App.findById(app).populate('modules.menu'); + app = app.toObject({ virtuals: true }); + + assert.equal(app.modules.length, 2); + assert.equal(app.modules[0].menu.length, 2); + assert.deepEqual(app.modules[0].menu.map(i => i.title), ['Save', 'Save As']); + assert.deepEqual(app.modules[1].menu.map(i => i.title), ['Redo', 'Undo']); + }); + }); + it('justOne option (gh-4263)', function(done) { const PersonSchema = new Schema({ name: String, @@ -5049,7 +5100,7 @@ describe('model: populate:', function() { }); }); - it('with no results (gh-4284)', function(done) { + it('with no results XYZ (gh-4284)', function(done) { const PersonSchema = new Schema({ name: String, authored: [Number] @@ -9869,4 +9920,686 @@ describe('model: populate:', function() { assert.deepEqual(findCallOptions[0].virtuals, ['foo']); }); }); + + it('gh-9833', function() { + const Books = db.model('books', new Schema({ name: String, tags: [{ type: Schema.Types.ObjectId, ref: 'tags' }] })); + const Tags = db.model('tags', new Schema({ author: Schema.Types.ObjectId })); + const Authors = db.model('authors', new Schema({ name: String })); + + return co(function*() { + const anAuthor = new Authors({ name: 'Author1' }); + yield anAuthor.save(); + + const aTag = new Tags({ author: anAuthor.id }); + yield aTag.save(); + + const aBook = new Books({ name: 'Book1', tags: [aTag.id] }); + yield aBook.save(); + + const aggregateOptions = [ + { $match: { + name: { $in: [aBook.name] } + } }, + { $lookup: { + from: 'tags', + localField: 'tags', + foreignField: '_id', + as: 'tags' + } } + ]; + const books = yield Books.aggregate(aggregateOptions).exec(); + + const populateOptions = [{ + path: 'tags.author', + model: 'authors', + select: '_id name' + }]; + + const populatedBooks = yield Books.populate(books, populateOptions); + assert.ok(!Array.isArray(populatedBooks[0].tags[0].author)); + }); + }); + + it('sets not-found values to null for paths that are not in the schema (gh-9913)', function() { + const Books = db.model('books', new Schema({ name: String, tags: [{ type: 'ObjectId', ref: 'tags' }] })); + const Tags = db.model('tags', new Schema({ authors: [{ author: 'ObjectId' }] })); + const Authors = db.model('authors', new Schema({ name: String })); + + return co(function*() { + const anAuthor = new Authors({ name: 'Author1' }); + yield anAuthor.save(); + + const aTag = new Tags({ authors: [{ author: anAuthor.id }, { author: new mongoose.Types.ObjectId() }] }); + yield aTag.save(); + + const aBook = new Books({ name: 'Book1', tags: [aTag.id] }); + yield aBook.save(); + + const aggregateOptions = [ + { $match: { + name: { $in: [aBook.name] } + } }, + { $lookup: { + from: 'tags', + localField: 'tags', + foreignField: '_id', + as: 'tags' + } } + ]; + const books = yield Books.aggregate(aggregateOptions).exec(); + + const populateOptions = [{ + path: 'tags.authors.author', + model: 'authors', + select: '_id name' + }]; + + const populatedBooks = yield Books.populate(books, populateOptions); + assert.strictEqual(populatedBooks[0].tags[0].authors[0].author.name, 'Author1'); + assert.strictEqual(populatedBooks[0].tags[0].authors[1].author, null); + }); + }); + + it('handles perDocumentLimit where multiple documents reference the same populated doc (gh-9906)', function() { + const postSchema = new Schema({ + title: String, + commentsIds: [{ type: Schema.ObjectId, ref: 'Comment' }] + }); + const Post = db.model('Post', postSchema); + + const commentSchema = new Schema({ content: String }); + const Comment = db.model('Comment', commentSchema); + + return co(function*() { + const commonComment = new Comment({ content: 'Im used in two posts' }); + yield commonComment.save(); + + const comments = yield Comment.create([ + { content: 'Nice first post' }, + { content: 'Nice second post' } + ]); + + let posts = yield Post.create([ + { title: 'First post', commentsIds: [commonComment, comments[0]] }, + { title: 'Second post', commentsIds: [commonComment, comments[1]] } + ]); + + posts = yield Post.find(). + sort({ title: 1 }). + populate({ path: 'commentsIds', perDocumentLimit: 2, sort: { content: 1 } }); + assert.equal(posts.length, 2); + assert.ok(!Array.isArray(posts[0].commentsIds[0])); + + assert.deepEqual(posts[0].toObject().commentsIds.map(c => c.content), ['Im used in two posts', 'Nice first post']); + assert.deepEqual(posts[1].toObject().commentsIds.map(c => c.content), ['Im used in two posts', 'Nice second post']); + }); + }); + + it('supports `transform` option (gh-3375)', function() { + const parentSchema = new Schema({ + name: String, + children: [{ type: 'ObjectId', ref: 'Child' }], + child: { type: 'ObjectId', ref: 'Child' } + }); + const Parent = db.model('Parent', parentSchema); + + const Child = db.model('Child', Schema({ name: String })); + + return co(function*() { + const children = yield Child.create([{ name: 'Luke' }, { name: 'Leia' }]); + let p = yield Parent.create({ + name: 'Anakin', + children: children, + child: children[0]._id + }); + + let called = []; + function transform(doc, id) { + called.push({ + doc: doc, + id: id + }); + + return id; + } + + // Populate array of ids + p = yield Parent.findById(p).populate({ + path: 'children', + transform: transform + }); + + assert.equal(called.length, 2); + assert.equal(called[0].doc.name, 'Luke'); + assert.equal(called[0].id.toHexString(), children[0]._id.toHexString()); + + assert.equal(called[1].doc.name, 'Leia'); + assert.equal(called[1].id.toHexString(), children[1]._id.toHexString()); + + // Populate single id + called = []; + p = yield Parent.findById(p).populate({ + path: 'child', + transform: transform + }); + + assert.equal(called.length, 1); + assert.equal(called[0].doc.name, 'Luke'); + assert.equal(called[0].id.toHexString(), children[0]._id.toHexString()); + + // Push a nonexistent id + const newId = new mongoose.Types.ObjectId(); + yield Parent.updateOne({ _id: p._id }, { $push: { children: newId } }); + + called = []; + p = yield Parent.findById(p).populate({ + path: 'children', + transform: transform + }); + assert.equal(called.length, 3); + assert.strictEqual(called[2].doc, null); + assert.equal(called[2].id.toHexString(), newId.toHexString()); + + assert.equal(p.children[2].toHexString(), newId.toHexString()); + + // Populate 2 docs with same id + yield Parent.updateOne({ _id: p._id }, { $set: { children: [children[0], children[0]] } }); + called = []; + + p = yield Parent.findById(p).populate({ + path: 'children', + transform: transform + }); + assert.equal(called.length, 2); + assert.equal(called[0].id.toHexString(), children[0]._id.toHexString()); + assert.equal(called[1].id.toHexString(), children[0]._id.toHexString()); + + // Populate single id that points to nonexistent doc + yield Parent.updateOne({ _id: p._id }, { $set: { child: newId } }); + called = []; + p = yield Parent.findById(p).populate({ + path: 'child', + transform: transform + }); + + assert.equal(called.length, 1); + assert.strictEqual(called[0].doc, null); + assert.equal(called[0].id.toHexString(), newId.toHexString()); + }); + }); + + it('transform to primitive (gh-10064)', function() { + const Child = db.model('Child', mongoose.Schema({ name: String })); + const Parent = db.model('Parent', mongoose.Schema({ + child: { type: 'ObjectId', ref: 'Child' }, + children: [{ type: 'ObjectId', ref: 'Child' }] + })); + + return co(function*() { + const children = yield Child.create([{ name: 'Luke' }, { name: 'Leia' }]); + + let doc = yield Parent.create({ children, child: children[0] }); + doc = yield Parent.findById(doc).populate([ + { + path: 'child', + transform: getName + }, + { + path: 'children', + options: { retainNullValues: true }, + transform: getName + } + ]); + + function getName(doc) { + return doc == null ? null : doc.name; + } + + assert.equal(doc.child, 'Luke'); + assert.deepEqual(doc.toObject().children.sort().reverse(), ['Luke', 'Leia']); + }); + }); + + it('transform with virtual populate, justOne = true (gh-3375)', function() { + const parentSchema = new Schema({ + name: String + }); + parentSchema.virtual('child', { + ref: 'Child', + localField: '_id', + foreignField: 'parentId', + justOne: true + }); + const Parent = db.model('Parent', parentSchema); + + const Child = db.model('Child', Schema({ name: String, parentId: 'ObjectId' })); + + return co(function*() { + let p = yield Parent.create({ name: 'Anakin' }); + yield Child.create({ name: 'Luke', parentId: p._id }); + + const called = []; + + p = yield Parent.findById(p).populate({ + path: 'child', + transform: function(doc, id) { + called.push({ + doc: doc, + id: id + }); + + return id; + } + }); + + assert.equal(called.length, 1); + assert.strictEqual(called[0].doc.parentId.toHexString(), p._id.toHexString()); + assert.equal(called[0].id.toHexString(), p._id.toHexString()); + }); + }); + + it('transform with virtual populate, justOne = false (gh-3375)', function() { + const parentSchema = new Schema({ + name: String + }); + parentSchema.virtual('children', { + ref: 'Child', + localField: '_id', + foreignField: 'parentId', + justOne: false + }); + const Parent = db.model('Parent', parentSchema); + + const Child = db.model('Child', Schema({ name: String, parentId: 'ObjectId' })); + + return co(function*() { + let p = yield Parent.create({ name: 'Anakin' }); + yield Child.create([ + { name: 'Luke', parentId: p._id }, + { name: 'Leia', parentId: p._id } + ]); + + const called = []; + + p = yield Parent.findById(p).populate({ + path: 'children', + transform: function(doc, id) { + called.push({ + doc: doc, + id: id + }); + + return id; + } + }); + + assert.equal(called.length, 2); + assert.deepEqual(called.map(c => c.doc.name).sort(), ['Leia', 'Luke']); + + assert.strictEqual(called[0].doc.parentId.toHexString(), p._id.toHexString()); + assert.equal(called[0].id.toHexString(), p._id.toHexString()); + + assert.strictEqual(called[1].doc.parentId.toHexString(), p._id.toHexString()); + assert.equal(called[1].id.toHexString(), p._id.toHexString()); + }); + }); + + it('supports populating dotted subpath of a populated doc that has the same id as a populated doc (gh-10005)', function() { + const ModelASchema = new mongoose.Schema({ + _modelB: { + type: mongoose.Types.ObjectId, + ref: 'Test1' + }, + _rootModel: { + type: mongoose.Types.ObjectId, + ref: 'Test' + } + }); + + const ModelBSchema = new mongoose.Schema({ + _rootModel: { + type: mongoose.Types.ObjectId, + ref: 'Test' + } + }); + + const RootModelSchema = new mongoose.Schema({ name: String }); + + return co(function*() { + const ModelA = db.model('Test2', ModelASchema); + const ModelB = db.model('Test1', ModelBSchema); + const RootModel = db.model('Test', RootModelSchema); + + const rootModel = new RootModel({ name: 'my name' }); + const modelB = new ModelB({ _rootModel: rootModel }); + const modelA = new ModelA({ _rootModel: rootModel, _modelB: modelB }); + + yield Promise.all([rootModel.save(), modelB.save(), modelA.save()]); + + const modelA1 = yield ModelA.findById(modelA._id); + yield modelA1.populate('_modelB _rootModel').execPopulate(); + yield modelA1.populate('_modelB._rootModel').execPopulate(); + + assert.equal(modelA1._modelB._rootModel.name, 'my name'); + }); + }); + + it('prevents already populated fields from becoming null gh-10068', function() { + return co(function*() { + const Books = db.model('books', new Schema({ name: String, contributors: [{ author: Schema.Types.ObjectId }] })); + const Authors = db.model('authors', new Schema({ name: String })); + + const anAuthor = new Authors({ name: 'Author1' }); + yield anAuthor.save(); + + const aBook1 = new Books({ name: 'Book1', contributors: [{ author: anAuthor.id }] }); + yield aBook1.save(); + const aBook2 = new Books({ name: 'Book2', contributors: [{ author: anAuthor.id }] }); + yield aBook2.save(); + + const populateOptions = [{ + path: 'contributors.author', + model: 'authors', + select: '_id name' + }]; + const populatedBooks = (yield Books.find().populate(populateOptions)).map(aBook => { + aBook = aBook.toObject(); + if (!aBook._id.equals(aBook1.id)) { + aBook.contributors[0].author = aBook.contributors[0].author._id; + } + return aBook; + }); + + const populatedBooksAgain = yield Books.populate(populatedBooks, populateOptions); + + assert.equal(populatedBooksAgain[0].contributors[0].author.name, 'Author1'); + }); + }); + + it('populates lean subdoc with `_id` property (gh-10069)', function() { + const Books = db.model('Book', new Schema({ name: String, author: Schema.Types.ObjectId })); + const Authors = db.model('Person', new Schema({ name: String })); + + return co(function*() { + const anAuthor = new Authors({ name: 'Author1' }); + yield anAuthor.save(); + + const aBook1 = new Books({ name: 'Book1', author: anAuthor.id }); + yield aBook1.save(); + const aBook2 = new Books({ name: 'Book2', author: anAuthor.id }); + yield aBook2.save(); + + const populateOptions = [{ + path: 'author', + model: 'Person' + }]; + + const books = (yield Books.find().lean()).map(aBook => { + if (!aBook._id.equals(aBook1.id)) { + aBook.author = { _id: aBook.author }; + } + return aBook; + }); + + const populatedBooks = yield Books.populate(books, populateOptions); + assert.equal(populatedBooks.length, 2); + assert.equal(populatedBooks[0].author.name, 'Author1'); + assert.equal(populatedBooks[1].author.name, 'Author1'); + }); + }); + + it('handles virtual populate when foreignField is an array with duplicates (gh-10117)', function() { + const bookSchema = new Schema({ name: String, author: String }); + bookSchema.virtual('authors', { + ref: 'Person', + localField: 'author', + foreignField: 'aliases', + justOne: false + }); + const Book = db.model('Book', bookSchema); + const Author = db.model('Person', new Schema({ aliases: [String] })); + + return co(function*() { + yield Author.create({ aliases: ['author1', 'author2', 'author1'] }); + + const book = yield Book.create({ name: 'Book1', author: 'author1' }); + + const fromDb = yield Book.findById(book).populate('authors'); + assert.equal(fromDb.authors.length, 1); + assert.deepEqual(fromDb.toObject({ virtuals: true }).authors[0].aliases, ['author1', 'author2', 'author1']); + }); + }); + + it('handles virtual populate with `$elemMatch` in custom match when `foreignField` is an array (gh-10117)', function() { + const User = db.model('User', mongoose.Schema({ + name: String, + access: [{ role: String, deletedAt: Date, organization: 'ObjectId' }] + })); + + const organizationSchema = mongoose.Schema({ name: String }); + organizationSchema.virtual('administrators', { + ref: 'User', + localField: '_id', + foreignField: 'access.organization' + }); + const Organization = db.model('Organization', organizationSchema); + + return co(function*() { + const org = yield Organization.create({ name: 'test org' }); + yield User.create([ + { name: 'user1', access: [{ role: 'admin', organization: org._id }] }, + { name: 'user2', access: [{ role: 'admin', organization: null }] }, + { + name: 'user3', + access: [ + // Shouldn't end up in result because first entry matches on `_id` but + // not custom `$elemMatch`, and 2nd entry matches on custom `$elemMatch` + // but not on `_id`. + { role: 'admin', organization: org._id, deletedAt: new Date() }, + { role: 'admin', organization: null } + ] + } + ]); + + const res = yield Organization.findById(org).populate({ + path: 'administrators', + match: { + access: { $elemMatch: { role: 'admin', deletedAt: { $exists: false } } } + } + }); + assert.equal(res.administrators.length, 1); + assert.equal(res.administrators[0].name, 'user1'); + }); + }); + + it('populates immutable array paths (gh-10159)', function() { + const Cat = db.model('Cat', { + name: String, + friends: [{ + immutable: true, + type: mongoose.ObjectId, + ref: 'Cat' + }] + }); + + return co(function*() { + const friend = yield Cat.create({ name: 'Zildjian' }); + const myCat = yield Cat.create({ name: 'Lord Fluffles', friends: [friend.id] }); + + yield myCat.populate('friends').execPopulate(); + + assert.equal(myCat.friends[0].name, 'Zildjian'); + }); + }); + + it('populates paths under mixed schematypes where some documents have non-object properties (gh-10191)', function() { + const schema = mongoose.Schema({ + name: String, + params: [{ + key: String, + value: 'Mixed' + }] + }); + const Test = db.model('Test', schema); + const User = db.model('User', mongoose.Schema({ name: String })); + + return co(function*() { + const user = yield User.create({ name: 'test' }); + + yield Test.create([ + { name: 'test1', params: [{ key: 'textContext', value: 'asd' }] }, + { name: 'test2', params: [{ key: 'logic', value: { optionLabels: [user._id] } }] } + ]); + + const res = yield Test.find().sort({ name: 1 }).populate({ + path: 'params.value.optionLabels', + model: User + }); + + assert.equal(res[0].name, 'test1'); + assert.equal(res[0].params.length, 1); + assert.equal(res[0].params[0].value, 'asd'); + + assert.equal(res[1].name, 'test2'); + assert.equal(res[1].params.length, 1); + assert.equal(res[1].params[0].value.optionLabels.length, 1); + assert.equal(res[1].params[0].value.optionLabels[0].name, 'test'); + }); + }); + + it('populates embedded discriminator with tied value (gh-10231)', function() { + const GuestSchema = new Schema({ + dummy: String + }); + + const ActivitySchema = new Schema({ + title: String, + kind: { required: true, type: String, enum: ['TALK'] } }, + { discriminatorKey: 'kind' }); + + const ActivityTalkSchema = new Schema({ + speakers: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Guest' }] + }); + + ActivityTalkSchema.add(ActivitySchema); + + ActivityTalkSchema.paths.kind.options.$skipDiscriminatorCheck = true; + + const ProgrammeSchema = new Schema({ + activities: [{ required: true, type: ActivitySchema }] + }); + + ProgrammeSchema.path('activities').discriminator('ActivityTalk', ActivityTalkSchema, 'TALK'); + + const GuestModel = db.model('Guest', GuestSchema); + const ProgrammeModel = db.model('Test', ProgrammeSchema); + + return co(function*() { + const guest1 = yield GuestModel.create({ dummy: '1' }); + const guest2 = yield GuestModel.create({ dummy: '2' }); + + const programme1 = yield ProgrammeModel.create({ + activities: [ + { title: 'hello', kind: 'TALK', speakers: [guest1, guest2] } + ] + }); + + const event = yield ProgrammeModel.findOne({ _id: programme1._id }).orFail().exec(); + yield event.populate({ path: 'activities.speakers' }).execPopulate(); + assert.equal(event.activities.length, 1); + assert.equal(event.activities[0].speakers.length, 2); + assert.equal(event.activities[0].speakers[0].dummy, '1'); + assert.equal(event.activities[0].speakers[1].dummy, '2'); + }); + }); + + it('supports populating an array of immutable elements (gh-10264)', function() { + const Cat = db.model('Cat', { + _id: String, + name: String, + friends: [{ + immutable: true, + ref: 'Cat', + type: String + }] + }); + + return co(function*() { + const friend = new Cat({ _id: 'garfield', name: 'Garfield' }); + yield friend.save(); + + const myCat = new Cat({ _id: 'arlene', name: 'Arlene', friends: [friend.id] }); + yield myCat.save(); + + yield myCat.populate('friends').execPopulate(); + assert.equal(myCat.friends[0].name, 'Garfield'); + }); + }); + + it('populates nested path in schema using `Model.populate()` static (gh-10335)', function() { + const Books = db.model('Book', new Schema({ + name: String, + author: { _id: Schema.Types.ObjectId, name: String } + })); + const Authors = db.model('Author', new Schema({ name: String })); + + return co(function*() { + const anAuthor = new Authors({ name: 'Author1' }); + yield anAuthor.save(); + + const aBook1 = new Books({ name: 'Book1', author: { _id: anAuthor.id } }); + yield aBook1.save(); + + const books = (yield Books.find().lean()).map(aBook => { + aBook.author = aBook.author._id; + return aBook; + }); + + const populatedBooks = yield Books.populate(books, [{ + path: 'author', + model: 'Author', + select: '_id name' + }]); + + assert.equal(populatedBooks.length, 1); + assert.equal(populatedBooks[0].author.name, 'Author1'); + }); + }); + + it('avoids setting empty array on lean document when populate result is undefined (gh-10599)', function() { + return co(function*() { + const ImageSchema = Schema({ imageName: String }, { _id: false }); + const TextSchema = Schema({ + textName: String, + attached: [{ type: 'ObjectId', ref: 'Test2' }] + }, { _id: false }); + + const ItemSchema = Schema({ objectType: String }, { + discriminatorKey: 'objectType', + _id: false + }); + + const ExampleSchema = Schema({ test: String, list: [ItemSchema] }); + + ExampleSchema.path('list').discriminator('Image', ImageSchema); + ExampleSchema.path('list').discriminator('Text', TextSchema); + const Example = db.model('Test', ExampleSchema); + const Another = db.model('Test2', Schema({ test: 'String' })); + + yield Another.create({ _id: '61254490ea89de0004c8f2a0', test: 'test' }); + + const example = yield Example.create({ + test: 'example', + list: [ + { imageName: 'an image', objectType: 'Image' }, + { textName: 'this is a text', attached: ['61254490ea89de0004c8f2a0'], objectType: 'Text' } + ] + }); + const query = Example.findById(example._id).populate('list.attached').lean(); + + const result = yield query.exec(); + assert.strictEqual(result.list[0].attached, void 0); + assert.equal(result.list[1].attached[0].test, 'test'); + }); + }); }); diff --git a/test/model.querying.test.js b/test/model.querying.test.js index 79e6bfad05d..4608f799b97 100644 --- a/test/model.querying.test.js +++ b/test/model.querying.test.js @@ -866,10 +866,10 @@ describe('model: querying:', function() { assert.equal(nes1.length, 1); NE.find({ b: { $ne: [1] } }, function(err) { - assert.equal(err.message, 'Cast to ObjectId failed for value "[ 1 ]" at path "b" for model "Test"'); + assert.equal(err.message, 'Cast to ObjectId failed for value "[ 1 ]" (type Array) at path "b" for model "Test"'); NE.find({ b: { $ne: 4 } }, function(err) { - assert.equal(err.message, 'Cast to ObjectId failed for value "4" at path "b" for model "Test"'); + assert.equal(err.message, 'Cast to ObjectId failed for value "4" (type number) at path "b" for model "Test"'); NE.find({ b: id3, ids: { $ne: id4 } }, function(err, nes4) { assert.ifError(err); @@ -1807,7 +1807,7 @@ describe('model: querying:', function() { Test.findOne({ block: /buffer/i }, function(err) { assert.equal(err.message, 'Cast to Buffer failed for value ' + - '"/buffer/i" at path "block" for model "Test"'); + '"/buffer/i" (type RegExp) at path "block" for model "Test"'); Test.findOne({ block: [195, 188, 98, 101, 114] }, function(err, rb) { assert.ifError(err); assert.equal(rb.block.toString('utf8'), 'über'); diff --git a/test/model.test.js b/test/model.test.js index 59bfc680911..78359cc455f 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -22,6 +22,7 @@ const EmbeddedDocument = mongoose.Types.Embedded; const MongooseError = mongoose.Error; describe('Model', function() { + let db; let Comments; let BlogPost; @@ -3825,6 +3826,47 @@ describe('Model', function() { }); }); }); + it('is saved object with proper defaults', function() { + const schema = new Schema({ + foo: { + x: { type: String }, + y: { type: String } + }, + boo: { + x: { type: Boolean, default: false } + }, + bee: { + x: { type: Boolean, default: false }, + y: { type: Boolean, default: false } + } + }); + const Test = db.model('Test', schema); + + const doc = new Test({ + foo: { x: 'a', y: 'b' }, + bee: {}, + boo: {} + }); + + return co(function*() { + yield doc.save(); + assert.equal(doc.bee.x, false); + assert.equal(doc.bee.y, false); + assert.equal(doc.boo.x, false); + + doc.bee = undefined; + doc.boo = undefined; + + yield doc.save(); + + const docAfterUnsetting = yield Test.findById(doc._id); + + assert.equal(docAfterUnsetting.bee.x, false); + assert.equal(docAfterUnsetting.bee.y, false); + assert.equal(docAfterUnsetting.boo.x, false); + }); + }); + }); it('path is cast to correct value when retreived from db', function(done) { @@ -4774,6 +4816,31 @@ describe('Model', function() { }); }); + it('insertMany() returns only inserted docs with `ordered = true`', function() { + const schema = new Schema({ + name: { type: String, required: true, unique: true } + }); + const Movie = db.model('Movie', schema); + + const arr = [ + { name: 'The Phantom Menace' }, + { name: 'The Empire Strikes Back' }, + { name: 'The Phantom Menace' }, + { name: 'Jingle All The Way' } + ]; + const opts = { ordered: true }; + return co(function*() { + yield Movie.init(); + const err = yield Movie.insertMany(arr, opts).then(() => err, err => err); + assert.ok(err); + assert.ok(err.insertedDocs); + + assert.equal(err.insertedDocs.length, 2); + assert.strictEqual(err.insertedDocs[0].name, 'The Phantom Menace'); + assert.strictEqual(err.insertedDocs[1].name, 'The Empire Strikes Back'); + }); + }); + it('insertMany() validation error with ordered true and rawResult true when all documents are invalid', function(done) { const schema = new Schema({ name: { type: String, required: true } @@ -5780,6 +5847,50 @@ describe('Model', function() { assert.equal(people[3].age, 30); }); }); + + it('insertOne and replaceOne should not throw an error when set `timestamps: false` in schmea (gh-10048)', function() { + const schema = new Schema({ name: String }, { timestamps: false }); + const Model = db.model('Test', schema); + + return co(function*() { + yield Model.create({ name: 'test' }); + + yield Model.bulkWrite([ + { + insertOne: { + document: { name: 'insertOne-test' } + } + }, + { + replaceOne: { + filter: { name: 'test' }, + replacement: { name: 'replaceOne-test' } + } + } + ]); + + for (const name of ['insertOne-test', 'replaceOne-test']) { + const doc = yield Model.findOne({ name }); + assert.strictEqual(doc.createdAt, undefined); + assert.strictEqual(doc.updatedAt, undefined); + } + }); + }); + + it('casts objects with null prototype (gh-10512)', function() { + const schema = Schema({ + _id: String, + someArray: [{ message: String }] + }); + const Test = db.model('Test', schema); + + return Test.bulkWrite([{ + updateOne: { + filter: {}, + update: { $set: { 'someArray.1': { __proto__: null, message: 'test' } } } + } + }]); + }); }); it('insertMany with Decimal (gh-5190)', function(done) { @@ -6681,6 +6792,42 @@ describe('Model', function() { }); }); + it('Model.validate(...) uses document instance as context by default (gh-10132)', function() { + const userSchema = new Schema({ + name: { + type: String, + required: function() { + return this.nameRequired; + } + }, + nameRequired: Boolean + }); + + const User = db.model('User', userSchema); + return co(function*() { + const user = new User({ name: 'test', nameRequired: false }); + const err = yield User.validate(user).catch(err => err); + + assert.ifError(err); + }); + }); + it('Model.validate(...) uses object as context by default (gh-10346)', () => { + return co(function* () { + const userSchema = new mongoose.Schema({ + name: { type: String, required: true }, + age: { type: Number, required() {return this && this.name === 'John';} } + }); + + const User = db.model('User', userSchema); + + const err1 = yield User.validate({ name: 'John' }).then(() => null, err => err); + assert.ok(err1); + + const err2 = yield User.validate({ name: 'Sam' }).then(() => null, err => err); + assert.ok(err2 === null); + }); + }); + it('sets correct `Document#op` with `save()` (gh-8439)', function() { const schema = Schema({ name: String }); const ops = []; @@ -7083,4 +7230,415 @@ describe('Model', function() { }); }); }); + + describe('buildBulkWriteOperations() (gh-9673)', () => { + it('builds write operations', () => { + return co(function*() { + + const userSchema = new Schema({ + name: { type: String } + }); + + const User = db.model('User', userSchema); + + const users = [ + new User({ name: 'Hafez1_gh-9673-1' }), + new User({ name: 'Hafez2_gh-9673-1' }), + new User({ name: 'I am the third name' }) + ]; + + yield users[2].save(); + users[2].name = 'I am the updated third name'; + + const writeOperations = User.buildBulkWriteOperations(users); + + const desiredWriteOperations = [ + { insertOne: { document: users[0] } }, + { insertOne: { document: users[1] } }, + { updateOne: { filter: { _id: users[2]._id }, update: { $set: { name: 'I am the updated third name' } } } } + ]; + + assert.deepEqual( + writeOperations, + desiredWriteOperations + ); + }); + }); + + it('throws an error when one document is invalid', () => { + const userSchema = new Schema({ + name: { type: String, minLength: 5 } + }); + + const User = db.model('User', userSchema); + + const users = [ + new User({ name: 'a' }), + new User({ name: 'Hafez2_gh-9673-1' }), + new User({ name: 'b' }) + ]; + + let err; + try { + User.buildBulkWriteOperations(users); + } catch (error) { + err = error; + } + + + assert.ok(err); + }); + + it('throws an error if documents is not an array', function() { + const userSchema = new Schema({ + name: { type: String } + }); + + const User = db.model('User', userSchema); + + + assert.throws( + function() { + User.buildBulkWriteOperations(null); + }, + /bulkSave expects an array of documents to be passed/ + ); + }); + it('throws an error if one element is not a document', function() { + const userSchema = new Schema({ + name: { type: String } + }); + + const User = db.model('User', userSchema); + + + assert.throws( + function() { + User.buildBulkWriteOperations([ + new User({ name: 'Hafez' }), + { name: 'I am not a document' } + ]); + }, + /documents\.1 was not a mongoose document/ + ); + }); + it('skips validation when given `skipValidation` true', () => { + const userSchema = new Schema({ + name: { type: String, minLength: 5 } + }); + + const User = db.model('User', userSchema); + + const users = [ + new User({ name: 'a' }), + new User({ name: 'Hafez2_gh-9673-1' }), + new User({ name: 'b' }) + ]; + + const writeOperations = User.buildBulkWriteOperations(users, { skipValidation: true }); + + assert.equal(writeOperations.length, 3); + }); + }); + + describe('bulkSave() (gh-9673)', function() { + it('saves new documents', function() { + return co(function* () { + const userSchema = new Schema({ + name: { type: String } + }); + + const User = db.model('User', userSchema); + + + yield User.bulkSave([ + new User({ name: 'Hafez1_gh-9673-1' }), + new User({ name: 'Hafez2_gh-9673-1' }) + ]); + + const users = yield User.find().sort('name'); + + assert.deepEqual( + users.map(user => user.name), + [ + 'Hafez1_gh-9673-1', + 'Hafez2_gh-9673-1' + ] + ); + }); + }); + + it('updates documents', function() { + return co(function* () { + const userSchema = new Schema({ + name: { type: String } + }); + + const User = db.model('User', userSchema); + + + yield User.insertMany([ + new User({ name: 'Hafez1_gh-9673-2' }), + new User({ name: 'Hafez2_gh-9673-2' }), + new User({ name: 'Hafez3_gh-9673-2' }) + ]); + + const users = yield User.find().sort('name'); + + users[0].name = 'Hafez1_gh-9673-2-updated'; + users[1].name = 'Hafez2_gh-9673-2-updated'; + + yield User.bulkSave(users); + + const usersAfterUpdate = yield User.find().sort('name'); + + assert.deepEqual( + usersAfterUpdate.map(user => user.name), + [ + 'Hafez1_gh-9673-2-updated', + 'Hafez2_gh-9673-2-updated', + 'Hafez3_gh-9673-2' + ] + ); + }); + }); + + it('returns writeResult on success', () => { + return co(function* () { + const userSchema = new Schema({ + name: { type: String } + }); + + const User = db.model('User', userSchema); + + + yield User.insertMany([ + new User({ name: 'Hafez1_gh-9673-2' }), + new User({ name: 'Hafez2_gh-9673-2' }) + ]); + + const users = yield User.find().sort('name'); + + users[0].name = 'Hafez1_gh-9673-2-updated'; + users[1].name = 'Hafez2_gh-9673-2-updated'; + + const writeResult = yield User.bulkSave(users); + assert.ok(writeResult.result.ok); + }); + }); + it('throws an error on failure', () => { + return co(function* () { + const userSchema = new Schema({ + name: { type: String, unique: true } + }); + + const User = db.model('User', userSchema); + yield User.init(); + + yield User.insertMany([ + new User({ name: 'Hafez1_gh-9673-2' }), + new User({ name: 'Hafez2_gh-9673-2' }) + ]); + + const users = yield User.find().sort('name'); + + users[0].name = 'duplicate-name'; + users[1].name = 'duplicate-name'; + + const err = yield User.bulkSave(users).then(() => null, err => err); + assert.ok(err); + }); + }); + it('changes document state from `isNew` `false` to `true`', () => { + return co(function* () { + const userSchema = new Schema({ + name: { type: String } + }); + + const User = db.model('User', userSchema); + yield User.init(); + + const user1 = new User({ name: 'Hafez1_gh-9673-3' }); + const user2 = new User({ name: 'Hafez1_gh-9673-3' }); + + assert.equal(user1.isNew, true); + assert.equal(user2.isNew, true); + + yield User.bulkSave([user1, user2]); + + assert.equal(user1.isNew, false); + assert.equal(user2.isNew, false); + }); + }); + it('sets `isNew` to false when a document succeeds and `isNew` does not change when some fail', () => { + return co(function* () { + const userSchema = new Schema({ + name: { type: String, unique: true } + }); + + const User = db.model('User', userSchema); + yield User.init(); + + const user1 = new User({ name: 'Hafez1_gh-9673-4' }); + const user2 = new User({ name: 'Hafez1_gh-9673-4' }); + + const err = yield User.bulkSave([user1, user2]).then(() => null, err => err); + + assert.ok(err); + assert.equal(user1.isNew, false); + assert.equal(user2.isNew, true); + }); + }); + it('changes documents state for successful writes', () => { + return co(function* () { + const userSchema = new Schema({ + name: { type: String, unique: true }, + age: Number + }); + + const User = db.model('User', userSchema); + yield User.init(); + + const user1 = new User({ name: 'Sam', age: 26 }); + const user2 = new User({ name: 'Sam', age: 27 }); + + assert.equal(user1.isNew, true); + assert.equal(user2.isNew, true); + + const err = yield User.bulkSave([user1, user2]).then(() => null, err => err); + + assert.ok(err); + assert.deepEqual(user1.getChanges(), {}); + assert.deepEqual(user2.getChanges(), { $set: { age: 27, name: 'Sam' } }); + }); + }); + it('triggers pre/post-save hooks', () => { + return co(function* () { + const userSchema = new Schema({ + name: String, + age: Number + }); + + let preSaveCallCount = 0; + let postSaveCallCount = 0; + + userSchema.pre('save', function() { + preSaveCallCount++; + }); + userSchema.post('save', function() { + postSaveCallCount++; + }); + + const User = db.model('User', userSchema); + + const user1 = new User({ name: 'Sam1' }); + const user2 = new User({ name: 'Sam2' }); + + + yield User.bulkSave([user1, user2]); + assert.equal(preSaveCallCount, 2); + assert.equal(postSaveCallCount, 2); + }); + }); + it('calls pre-save before actually saving', () => { + return co(function* () { + const userSchema = new Schema({ name: String }); + + + userSchema.pre('save', function() { + this.name = 'name from pre-save'; + }); + + const User = db.model('User', userSchema); + + const user1 = new User({ name: 'Sam1' }); + const user2 = new User({ name: 'Sam2' }); + + + yield User.bulkSave([user1, user2]); + + const usersFromDatabase = yield User.find({ _id: { $in: [user1._id, user2._id] } }).sort('_id'); + assert.equal(usersFromDatabase[0].name, 'name from pre-save'); + assert.equal(usersFromDatabase[1].name, 'name from pre-save'); + }); + }); + it('works if some document is not modified (gh-10437)', () => { + const userSchema = new Schema({ + name: String + }); + + const User = db.model('User', userSchema); + + return co(function*() { + const user = yield User.create({ name: 'Hafez' }); + + const err = yield User.bulkSave([user]).then(() => null, err => err); + assert.ok(err == null); + }); + }); + }); + + describe('Setting the explain flag', function() { + it('should give an object back rather than a boolean (gh-8275)', function() { + return co(function*() { + const MyModel = db.model('Character', mongoose.Schema({ + name: String, + age: Number, + rank: String + })); + + yield MyModel.create([ + { name: 'Jean-Luc Picard', age: 59, rank: 'Captain' }, + { name: 'William Riker', age: 29, rank: 'Commander' }, + { name: 'Deanna Troi', age: 28, rank: 'Lieutenant Commander' }, + { name: 'Geordi La Forge', age: 29, rank: 'Lieutenant' }, + { name: 'Worf', age: 24, rank: 'Lieutenant' } + ]); + const res = yield MyModel.exists({}, { explain: true }); + + assert.equal(typeof res, 'object'); + }); + }); + }); + + it('saves all error object properties to paths with type `Mixed` (gh-10126)', () => { + return co(function*() { + const userSchema = new Schema({ err: Schema.Types.Mixed }); + + const User = db.model('User', userSchema); + + const err = new Error('I am a bad error'); + err.metadata = { reasons: ['Cloudflare is down', 'DNS'] }; + + const user = yield User.create({ err }); + const userFromDB = yield User.findOne({ _id: user._id }); + + assertErrorProperties(user); + assertErrorProperties(userFromDB); + + + function assertErrorProperties(user) { + assert.equal(user.err.message, 'I am a bad error'); + assert.ok(user.err.stack); + assert.deepEqual(user.err.metadata, { reasons: ['Cloudflare is down', 'DNS'] }); + } + }); + }); + + it('supports skipping defaults on a find operation gh-7287', function() { + const betaSchema = new Schema({ + name: { type: String, default: 'foo' }, + age: { type: Number }, + _id: { type: Number } + }); + + const Beta = db.model('Beta', betaSchema); + return co(function*() { + yield Beta.collection.insertOne({ age: 21, _id: 1 }); + const test = yield Beta.findOne({ _id: 1 }).setOptions({ defaults: false }); + assert.ok(!test.name); + }); + }); }); + + diff --git a/test/model.update.test.js b/test/model.update.test.js index 5e3d7601a81..39f6ca81a6e 100644 --- a/test/model.update.test.js +++ b/test/model.update.test.js @@ -2123,26 +2123,6 @@ describe('model: update:', function() { }); }); - it('update handles casting with mongoose-long (gh-4283)', function(done) { - require('mongoose-long')(mongoose); - - const Model = db.model('Test', { - number: { type: mongoose.Types.Long } - }); - - Model.create({ number: mongoose.mongo.Long.fromString('0') }, function(error) { - assert.ifError(error); - Model.update({}, { $inc: { number: mongoose.mongo.Long.fromString('2147483648') } }, function(error) { - assert.ifError(error); - Model.findOne({ number: { $type: 18 } }, function(error, doc) { - assert.ifError(error); - assert.ok(doc); - done(); - }); - }); - }); - }); - it('single nested with runValidators (gh-4420)', function(done) { const FileSchema = new Schema({ name: String @@ -3578,4 +3558,116 @@ describe('model: updateOne: ', function() { assert.equal(fromDb.children[0].name, 'Luke Skywalker'); }); }); + + describe('converts dot separated paths to nested structure (gh-10200)', () => { + it('works with new Model(...)', () => { + const Payment = getPaymentModel(); + const paymentPOJO = getPaymentWithDotSeparatedPaths(); + const payment = new Payment(paymentPOJO); + assertDocumentStructure(payment.toObject()); + }); + it('works with Model.create(...)', () => { + return co(function*() { + const Payment = getPaymentModel(); + const paymentPOJO = getPaymentWithDotSeparatedPaths(); + const payment = yield Payment.create(paymentPOJO); + assertDocumentStructure(payment); + }); + }); + it('works with Model.updateOne(...)', () => { + return co(function*() { + const User = getPaymentModel(); + const userPOJO = getPaymentWithDotSeparatedPaths(); + + const emptyUser = yield User.create({}); + yield User.updateOne({ _id: emptyUser._id }, userPOJO); + const user = yield User.findOne({ _id: emptyUser._id }).lean(); + + assertDocumentStructure(user); + }); + }); + it('works with Model.bulkWrite(...)', () => { + return co(function*() { + const Payment = getPaymentModel(); + const paymentPOJO = getPaymentWithDotSeparatedPaths(); + + const emptyPayment = yield Payment.create({}); + + yield Payment.bulkWrite([ + { updateOne: { filter: { _id: emptyPayment._id }, update: paymentPOJO } } + ]); + const payment = yield Payment.findOne({ _id: emptyPayment._id }).lean(); + + assertDocumentStructure(payment); + }); + }); + + + function getPaymentModel() { + const paymentSchema = new Schema({ + paymentFor: String, + externalServiceResponse: { + id: String, + resultDetails: { + clearingInstituteName: String, + transaction: { + receipt: String, + authorizationCode: String, + acquirer: { settlementDate: String } + }, + response: { acquirerCode: String, acquirerMessage: String }, + authorizationResponse: { stan: String }, + sourceOfFunds: { provided: { card: { issuer: String } } } + } + } + }); + + const Payment = db.model('Payment', paymentSchema); + return Payment; + } + + function getPaymentWithDotSeparatedPaths() { + return { + paymentFor: 'order', + externalServiceResponse: { + id: '1', + resultDetails: { + clearingInstituteName: 'Our local bank', + 'authorizationResponse.stan': '123456', + 'transaction.receipt': 'I am a transaction receipt', + 'transaction.authorizationCode': 'ABCDEF', + 'transaction.acquirer.settlementDate': 'February 2021', + 'sourceOfFunds.provided.card.issuer': 'Big bank corporation', + nonExistentField: 'I should not be present' + } + } + }; + } + + function assertDocumentStructure(payment) { + assert.equal(payment.paymentFor, 'order'); + assert.equal(payment.externalServiceResponse.id, '1'); + assert.equal(payment.externalServiceResponse.resultDetails.clearingInstituteName, 'Our local bank'); + assert.deepEqual( + payment.externalServiceResponse.resultDetails.authorizationResponse, + { stan: '123456' } + ); + assert.deepEqual( + payment.externalServiceResponse.resultDetails.transaction, + { + receipt: 'I am a transaction receipt', + authorizationCode: 'ABCDEF', + acquirer: { settlementDate: 'February 2021' } + } + ); + assert.deepEqual( + payment.externalServiceResponse.resultDetails.sourceOfFunds, + { provided: { card: { issuer: 'Big bank corporation' } } } + ); + assert.deepEqual( + payment.externalServiceResponse.resultDetails.nonExistentField, + undefined + ); + } + }); }); \ No newline at end of file diff --git a/test/query.cursor.test.js b/test/query.cursor.test.js index 5fde51466cd..9ca94eda435 100644 --- a/test/query.cursor.test.js +++ b/test/query.cursor.test.js @@ -644,6 +644,130 @@ describe('QueryCursor', function() { }, 20); }); + it('closing query cursor emits `close` event only once with stream pause/resume (gh-10876)', function(done) { + const User = db.model('User', new Schema({ name: String })); + + User.create({ name: 'First' }, { name: 'Second' }) + .then(() => { + const cursor = User.find().cursor(); + cursor.on('data', () => { + cursor.pause(); + setTimeout(() => cursor.resume(), 50); + }); + + let closeEventTriggeredCount = 0; + cursor.on('close', () => closeEventTriggeredCount++); + setTimeout(() => { + assert.equal(closeEventTriggeredCount, 1); + done(); + }, 200); + }); + }); + + it('closing aggregation cursor emits `close` event only once with stream pause/resume (gh-10876)', function(done) { + const User = db.model('User', new Schema({ name: String })); + + User.create({ name: 'First' }, { name: 'Second' }) + .then(() => { + const cursor = User.aggregate([{ $match: {} }]).cursor().exec(); + cursor.on('data', () => { + cursor.pause(); + setTimeout(() => cursor.resume(), 50); + }); + + let closeEventTriggeredCount = 0; + cursor.on('close', () => closeEventTriggeredCount++); + + + setTimeout(() => { + assert.equal(closeEventTriggeredCount, 1); + done(); + }, 200); + }); + }); + + it('query cursor emit end event (gh-10902)', function(done) { + const User = db.model('User', new Schema({ name: String })); + + User.create({ name: 'First' }, { name: 'Second' }) + .then(() => { + const cursor = User.find({}).cursor(); + cursor.on('data', () => { + cursor.pause(); + setTimeout(() => cursor.resume(), 50); + }); + + let endEventTriggeredCount = 0; + cursor.on('end', () => endEventTriggeredCount++); + + setTimeout(() => { + assert.equal(endEventTriggeredCount, 1); + done(); + }, 200); + }); + }); + + it('aggregate cursor emit end event (gh-10902)', function(done) { + const User = db.model('User', new Schema({ name: String })); + + User.create({ name: 'First' }, { name: 'Second' }) + .then(() => { + const cursor = User.aggregate([{ $match: {} }]).cursor().exec(); + cursor.on('data', () => { + cursor.pause(); + setTimeout(() => cursor.resume(), 50); + }); + + let endEventTriggeredCount = 0; + cursor.on('end', () => endEventTriggeredCount++); + + setTimeout(() => { + assert.equal(endEventTriggeredCount, 1); + done(); + }, 200); + }); + }); + + it('query cursor emit end event before close event (gh-10902)', function(done) { + const User = db.model('User', new Schema({ name: String })); + + User.create({ name: 'First' }, { name: 'Second' }) + .then(() => { + const cursor = User.find({}).cursor(); + cursor.on('data', () => { + cursor.pause(); + setTimeout(() => cursor.resume(), 50); + }); + + let endEventTriggeredCount = 0; + cursor.on('end', () => endEventTriggeredCount++); + cursor.on('close', () => { + assert.equal(endEventTriggeredCount, 1); + done(); + }); + }); + }); + + it('aggregate cursor emit end event before close event (gh-10902)', function(done) { + const User = db.model('User', new Schema({ name: String })); + + User.create({ name: 'First' }, { name: 'Second' }) + .then(() => { + const cursor = User.aggregate([{ $match: {} }]).cursor().exec(); + cursor.on('data', () => { + cursor.pause(); + setTimeout(() => cursor.resume(), 50); + }); + + let endEventTriggeredCount = 0; + cursor.on('end', () => endEventTriggeredCount++); + cursor.on('close', () => { + assert.equal(endEventTriggeredCount, 1); + done(); + }); + }); + }); + it('passes document index as the second argument for query cursor (gh-8972)', function() { return co(function *() { const User = db.model('User', Schema({ order: Number })); @@ -692,8 +816,8 @@ describe('QueryCursor', function() { it('post hooks (gh-9435)', function() { const schema = new mongoose.Schema({ name: String }); - schema.post('find', function(doc) { - doc.name = doc.name.toUpperCase(); + schema.post('find', function(docs) { + docs.forEach(doc => { doc.name = doc.name.toUpperCase(); }); }); const Movie = db.model('Movie', schema); @@ -711,4 +835,26 @@ describe('QueryCursor', function() { assert.deepEqual(arr, ['KICKBOXER', 'IP MAN', 'ENTER THE DRAGON']); }); }); + + it('reports CastError with noCursorTimeout set (gh-10150)', function() { + const schema = new mongoose.Schema({ name: String }); + const Movie = db.model('Movie', schema); + + return co(function*() { + yield Movie.deleteMany({}); + yield Movie.create([ + { name: 'Kickboxer' }, + { name: 'Ip Man' }, + { name: 'Enter the Dragon' } + ]); + + const arr = []; + const err = yield Movie.find({ name: { lt: 'foo' } }).cursor(). + addCursorFlag('noCursorTimeout', true). + eachAsync(doc => arr.push(doc.name)). + then(() => null, err => err); + assert.ok(err); + assert.equal(err.name, 'CastError'); + }); + }); }); diff --git a/test/query.test.js b/test/query.test.js index e7be2f856de..8762239a910 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -1302,8 +1302,10 @@ describe('Query', function() { } }); assert.deepEqual(options, { - w: 'majority', - j: true + writeConcern: { + w: 'majority', + j: true + } }); done(); }); @@ -2402,28 +2404,15 @@ describe('Query', function() { }); it('throw on sync exceptions in callbacks (gh-6178)', function(done) { - const async = require('async'); const schema = new Schema({}); const Test = db.model('Test', schema); process.once('uncaughtException', err => { - assert.equal(err.message, 'woops'); + assert.equal(err.message, 'Oops!'); done(); }); - async.waterfall([ - function(cb) { - Test.create({}, cb); - }, - function(res, cb) { - Test.find({}, function() { cb(); }); - }, - function() { - throw new Error('woops'); - } - ], function() { - assert.ok(false); - }); + Test.find({}, function() { throw new Error('Oops!'); }); }); }); @@ -3670,4 +3659,159 @@ describe('Query', function() { assert.equal(err.path, '$nor.0'); }); }); + + it('handles push with array filters (gh-9977)', function() { + const questionSchema = new Schema({ + question_type: { type: String, enum: ['mcq', 'essay'] } + }, { discriminatorKey: 'question_type', strict: 'throw' }); + + const quizSchema = new Schema({ + quiz_title: String, + questions: [questionSchema] + }, { strict: 'throw' }); + const Quiz = db.model('Test', quizSchema); + + const mcqQuestionSchema = new Schema({ + text: String, + choices: [{ choice_text: String, is_correct: Boolean }] + }, { strict: 'throw' }); + + quizSchema.path('questions').discriminator('mcq', mcqQuestionSchema); + + const id1 = new mongoose.Types.ObjectId(); + const id2 = new mongoose.Types.ObjectId(); + + return co(function*() { + let quiz = yield Quiz.create({ + quiz_title: 'quiz', + questions: [ + { + _id: id1, + question_type: 'mcq', + text: 'A or B?', + choices: [ + { choice_text: 'A', is_correct: false }, + { choice_text: 'B', is_correct: true } + ] + }, + { + _id: id2, + question_type: 'mcq' + } + ] + }); + + const filter = { questions: { $elemMatch: { _id: id2, question_type: 'mcq' } } }; + yield Quiz.updateOne(filter, { + $push: { + 'questions.$.choices': { + choice_text: 'choice 1', + is_correct: false + } + } + }); + + quiz = yield Quiz.findById(quiz); + assert.equal(quiz.questions[1].choices.length, 1); + assert.equal(quiz.questions[1].choices[0].choice_text, 'choice 1'); + + yield Quiz.updateOne({ questions: { $elemMatch: { _id: id2 } } }, { + $push: { + 'questions.$[q].choices': { + choice_text: 'choice 3', + is_correct: false + } + } + }, { arrayFilters: [{ 'q.question_type': 'mcq' }] }); + + quiz = yield Quiz.findById(quiz); + assert.equal(quiz.questions[1].choices.length, 2); + assert.equal(quiz.questions[1].choices[1].choice_text, 'choice 3'); + }); + }); + + it('Query#pre() (gh-9784)', function() { + const Question = db.model('Test', Schema({ answer: Number })); + return co(function*() { + const q1 = Question.find({ answer: 42 }); + const called = []; + q1.pre(function middleware() { + called.push(this.getFilter()); + }); + yield q1.exec(); + assert.equal(called.length, 1); + assert.deepEqual(called[0], { answer: 42 }); + + yield Question.find({ answer: 42 }); + assert.equal(called.length, 1); + }); + }); + + it('applies schema-level `select` on arrays (gh-10029)', function() { + const testSchema = new mongoose.Schema({ + doesntpopulate: { + type: [mongoose.Schema.Types.ObjectId], + select: false + }, + populatescorrectly: [{ + type: mongoose.Schema.Types.ObjectId, + select: false + }] + }); + const Test = db.model('Test', testSchema); + + const q = Test.find(); + q._applyPaths(); + + assert.deepEqual(q._fields, { doesntpopulate: 0, populatescorrectly: 0 }); + }); + + it('sets `writeConcern` option correctly (gh-10009)', function() { + const testSchema = new mongoose.Schema({ + name: String + }); + const Test = db.model('Test', testSchema); + + const q = Test.find(); + q.writeConcern({ w: 'majority', wtimeout: 1000 }); + + assert.deepEqual(q.options.writeConcern, { w: 'majority', wtimeout: 1000 }); + }); + it('no longer has the deprecation warning message with writeConcern gh-10083', function() { + const MySchema = new mongoose.Schema( + { + _id: { type: Number, required: true }, + op: { type: String, required: true }, + size: { type: Number, required: true }, + totalSize: { type: Number, required: true } + }, + { + versionKey: false, + writeConcern: { + w: 'majority', + j: true, + wtimeout: 15000 + } + } + ); + const Test = db.model('Test', MySchema); // pops up on creation of model + return co(function*() { + const entry = yield Test.create({ _id: 12345678, op: 'help', size: 54, totalSize: 104 }); + yield entry.save(); + }); + }); + + it('sanitizeProjection option (gh-10243)', function() { + const MySchema = Schema({ name: String, email: String }); + const Test = db.model('Test', MySchema); + + let q = Test.find().select({ email: '$name' }); + assert.deepEqual(q._fields, { email: '$name' }); + + q = Test.find().setOptions({ sanitizeProjection: true }).select({ email: '$name' }); + assert.deepEqual(q._fields, { email: 1 }); + + q = Test.find().select({ email: '$name' }).setOptions({ sanitizeProjection: true }); + assert.deepEqual(q._fields, { email: 1 }); + }); }); diff --git a/test/schema.test.js b/test/schema.test.js index 3c688226e56..d2bff3ce34e 100644 --- a/test/schema.test.js +++ b/test/schema.test.js @@ -441,7 +441,7 @@ describe('schema', function() { threw = true; assert.equal(error.name, 'CastError'); assert.equal(error.message, - 'Cast to [Number] failed for value "[["abcd"]]" at path "nums.0"'); + 'Cast to [[Number]] failed for value "[["abcd"]]" (type string) at path "nums.0"'); } assert.ok(threw); @@ -1309,6 +1309,31 @@ describe('schema', function() { done(); }); + + it('overwrites existing paths (gh-10203)', function() { + const baseSchema = new Schema({ + username: { + type: String, + required: false + } + }); + + const userSchema = new Schema({ + email: { + type: String, + required: true + }, + username: { + type: String, + required: true + } + }); + + const realSchema = baseSchema.clone(); + realSchema.add(userSchema); + + assert.ok(realSchema.path('username').isRequired); + }); }); it('debugging msgs', function(done) { @@ -1436,12 +1461,6 @@ describe('schema', function() { }); }, /`collection` may not be used as a schema pathname/); - assert.throws(function() { - new Schema({ - schema: String - }); - }, /`schema` may not be used as a schema pathname/); - assert.throws(function() { new Schema({ isNew: String @@ -2566,4 +2585,111 @@ describe('schema', function() { assert.equal(schema.path('tags').caster.instance, 'String'); assert.equal(schema.path('subdocs').casterConstructor.schema.path('name').instance, 'String'); }); + + it('handles loadClass with inheritted getters (gh-9975)', function() { + class User { + get displayAs() { + return null; + } + } + + class TechnicalUser extends User { + get displayAs() { + return this.name; + } + } + + const schema = new Schema({ name: String }).loadClass(TechnicalUser); + + assert.equal(schema.virtuals.displayAs.applyGetters(null, { name: 'test' }), 'test'); + }); + + it('loadClass with static getter (gh-10436)', function() { + const schema = new mongoose.Schema({ + firstName: String, + lastName: String + }); + + class UserClass extends mongoose.Model { + get fullName() { + return `${this.firstName} ${this.lastName}`; + } + + static get greeting() { + return 'Hello World'; + } + } + + const User = mongoose.model(UserClass, schema); + + assert.equal(User.greeting, 'Hello World'); + }); + + it('supports setting `ref` on array SchemaType (gh-10029)', function() { + const testSchema = new mongoose.Schema({ + doesntpopulate: { + type: [mongoose.Schema.Types.ObjectId], + ref: 'features' + }, + populatescorrectly: [{ + type: mongoose.Schema.Types.ObjectId, + ref: 'features' + }] + }); + + assert.equal(testSchema.path('doesntpopulate.$').options.ref, 'features'); + assert.equal(testSchema.path('populatescorrectly.$').options.ref, 'features'); + }); + + it('path() gets single nested paths within document arrays (gh-10164)', function() { + const schema = mongoose.Schema({ + field1: [mongoose.Schema({ + field2: mongoose.Schema({ + field3: Boolean + }) + })] + }); + + assert.equal(schema.path('field1').instance, 'Array'); + assert.equal(schema.path('field1.field2').instance, 'Embedded'); + assert.equal(schema.path('field1.field2.field3').instance, 'Boolean'); + }); + + it('supports creating nested paths underneath document arrays (gh-10193)', function() { + const DynamicTextMatchFeaturesSchema = new Schema({ css: { color: String } }); + + const ElementSchema = new Schema({ + image: { type: String }, + possibleElements: [{ + textMatchFeatures: { + dynamic: DynamicTextMatchFeaturesSchema + } + }] + }); + + assert.ok(ElementSchema.path('possibleElements').schema.path('textMatchFeatures.dynamic').schema.nested['css']); + }); + + it('propagates map `ref` down to individual map elements (gh-10329)', function() { + const TestSchema = new mongoose.Schema({ + testprop: { + type: Map, + of: Number, + ref: 'OtherModel' + } + }); + + assert.equal(TestSchema.path('testprop.$*').instance, 'Number'); + assert.equal(TestSchema.path('testprop.$*').options.ref, 'OtherModel'); + }); + + it('disallows setting special properties with `add()` or constructor (gh-12085)', function() { + const maliciousPayload = '{"__proto__.toString": "Number"}'; + + assert.throws(() => { + mongoose.Schema(JSON.parse(maliciousPayload)); + }, /__proto__/); + + assert.ok({}.toString()); + }); }); diff --git a/test/schema.validation.test.js b/test/schema.validation.test.js index 1e773fea9ee..52432acb8bc 100644 --- a/test/schema.validation.test.js +++ b/test/schema.validation.test.js @@ -1118,7 +1118,7 @@ describe('schema', function() { bad.validate(function(error) { assert.ok(error); const errorMessage = 'foods: Cast to Object failed for value ' + - '"waffles" at path "foods"'; + '"waffles" (type string) at path "foods"'; assert.ok(error.toString().indexOf(errorMessage) !== -1, error.toString()); done(); }); @@ -1192,7 +1192,7 @@ describe('schema', function() { const Breakfast = mongoose.model('gh2832', breakfast, 'gh2832'); Breakfast.create({ description: undefined }, function(error) { assert.ok(error); - const errorMessage = 'ValidationError: description: Cast to String failed for value "undefined" at path "description"'; + const errorMessage = 'ValidationError: description: Cast to String failed for value "undefined" (type undefined) at path "description"'; assert.equal(errorMessage, error.toString()); assert.ok(error.errors.description); assert.equal(error.errors.description.reason.toString(), 'Error: oops'); diff --git a/test/services.query.test.js b/test/services.query.test.js deleted file mode 100644 index a79008893c2..00000000000 --- a/test/services.query.test.js +++ /dev/null @@ -1,82 +0,0 @@ -'use strict'; - -require('./common'); - -const Query = require('../lib/query'); -const Schema = require('../lib/schema'); -const assert = require('assert'); -const selectPopulatedFields = require('../lib/helpers/query/selectPopulatedFields'); - -describe('Query helpers', function() { - describe('selectPopulatedFields', function() { - it('handles nested populate if parent key is projected in (gh-5669)', function(done) { - const schema = new Schema({ - nested: { - key1: String, - key2: String - } - }); - - const q = new Query({}); - q.schema = schema; - - assert.strictEqual(q._fields, void 0); - - q.select('nested'); - q.populate('nested.key1'); - assert.deepEqual(q._fields, { nested: 1 }); - - selectPopulatedFields(q); - - assert.deepEqual(q._fields, { nested: 1 }); - - done(); - }); - - it('handles nested populate if parent key is projected out (gh-5669)', function(done) { - const schema = new Schema({ - nested: { - key1: String, - key2: String - } - }); - - const q = new Query({}); - q.schema = schema; - - assert.strictEqual(q._fields, void 0); - - q.select('-nested'); - q.populate('nested.key1'); - assert.deepEqual(q._fields, { nested: 0 }); - - selectPopulatedFields(q); - - assert.deepEqual(q._fields, { nested: 0 }); - - done(); - }); - - it('handle explicitly excluded paths (gh-7383)', function(done) { - const schema = new Schema({ - name: String, - other: String - }); - - const q = new Query({}); - q.schema = schema; - - assert.strictEqual(q._fields, void 0); - - q.select({ name: 1, other: 0 }); - q.populate('other'); - assert.deepEqual(q._fields, { name: 1, other: 0 }); - - selectPopulatedFields(q); - - assert.deepEqual(q._fields, { name: 1 }); - - done(); - }); - }); -}); diff --git a/test/timestamps.test.js b/test/timestamps.test.js index d37558cd3d2..799d023030f 100644 --- a/test/timestamps.test.js +++ b/test/timestamps.test.js @@ -582,7 +582,7 @@ describe('timestamps', function() { }); }); - it('should have fields when create with findOneAndUpdate', function(done) { + it('sets timestamps on findOneAndUpdate', function(done) { Cat.findOneAndUpdate({ name: 'notexistname' }, { $set: {} }, { upsert: true, new: true }, function(err, doc) { assert.ok(doc.createdAt); assert.ok(doc.updatedAt); @@ -591,6 +591,14 @@ describe('timestamps', function() { }); }); + it('sets timestamps on findOneAndReplace (gh-9951)', function() { + return Cat.findOneAndReplace({ name: 'notexistname' }, {}, { upsert: true, new: true }).then(doc => { + assert.ok(doc.createdAt); + assert.ok(doc.updatedAt); + assert.ok(doc.createdAt.getTime() === doc.updatedAt.getTime()); + }); + }); + it('should change updatedAt when save', function(done) { Cat.findOne({ name: 'newcat' }, function(err, doc) { const old = doc.updatedAt; @@ -867,4 +875,48 @@ describe('timestamps', function() { assert.ok(fromDb.content.a.updatedAt.valueOf() > ts.valueOf()); }); }); + + it('sets createdAt when using $push/$addToSet on path with positional operator (gh-10447)', function() { + const userSchema = new Schema({ + email: String + }, { timestamps: true }); + const eventSchema = new Schema({ + users: [userSchema], + message: String + }, { timestamps: true }); + + const churchSchema = new Schema({ + events: [eventSchema] + }, { timestamps: true }); + + const Church = db.model('Test', churchSchema); + + return co(function*() { + yield Church.create({ + events: [{ + churchId: 1, + message: 'test', + users: [{ + churchId: 1, + email: 'test@google.com' + }] + }] + }); + + const church = yield Church.findOneAndUpdate({ + events: { $elemMatch: { users: { $not: { $elemMatch: { email: 'test2@google.com' } } } } } + }, { + $addToSet: { + 'events.$.users': { churchId: 1, email: 'test2@google.com' } + } + }, { + new: true + }); + + assert.equal(church.events.length, 1); + assert.equal(church.events[0].users.length, 2); + assert.equal(church.events[0].users[1].email, 'test2@google.com'); + assert.ok(church.events[0].users[1].createdAt); + }); + }); }); diff --git a/test/types.buffer.test.js b/test/types.buffer.test.js index 308379431cd..df05c26582b 100644 --- a/test/types.buffer.test.js +++ b/test/types.buffer.test.js @@ -91,7 +91,7 @@ describe('types.buffer', function() { assert.equal(err.name, 'ValidationError'); assert.equal(err.errors.required.name, 'CastError'); assert.equal(err.errors.required.kind, 'Buffer'); - assert.equal(err.errors.required.message, 'Cast to Buffer failed for value "{ x: [ 20 ] }" at path "required"'); + assert.equal(err.errors.required.message, 'Cast to Buffer failed for value "{ x: [ 20 ] }" (type Object) at path "required"'); assert.deepEqual(err.errors.required.value, { x: [20] }); t.required = Buffer.from('hello'); diff --git a/test/types.documentarray.test.js b/test/types.documentarray.test.js index 22de14b7323..1390f7e07eb 100644 --- a/test/types.documentarray.test.js +++ b/test/types.documentarray.test.js @@ -707,4 +707,28 @@ describe('types.documentarray', function() { then(() => Model.findById(doc)). then(doc => assert.deepEqual(doc.toObject().subDocArray, [{ name: 'foo' }])); }); + + it('keeps atomics after setting (gh-10272)', function() { + const MyChildSchema = new Schema({ + name: String + }); + + const MyModelSchema = new Schema({ + children: [MyChildSchema] + }); + + const Test = db.model('Test', MyModelSchema); + + const doc = new Test(); + doc.init({ + children: [{ name: 'John' }, { name: 'Jane' }] + }); + + doc.children = [{ name: 'John' }]; + doc.children = doc.children.concat([]); + doc.children.push({ name: 'Mary' }); + + assert.ok(doc.children.$atomics().$set); + assert.deepEqual(doc.children.$atomics().$set.map(v => v.name), ['John', 'Mary']); + }); }); diff --git a/test/types.map.test.js b/test/types.map.test.js index d5a7772c1e6..13ff1c4c1f0 100644 --- a/test/types.map.test.js +++ b/test/types.map.test.js @@ -424,12 +424,12 @@ describe('Map', function() { const foo = yield Scene.create({ name: 'foo' }); let event = yield Event.create({ scenes: { foo: foo._id } }); - event = yield Event.findOne().populate('scenes'); + event = yield Event.findOne().populate('scenes.$*'); const bar = yield Scene.create({ name: 'bar' }); event.scenes.set('bar', bar); yield event.save(); - event = yield Event.findOne().populate('scenes'); + event = yield Event.findOne().populate('scenes.$*'); assert.ok(event.scenes.has('bar')); assert.equal(event.scenes.get('bar').name, 'bar'); }); @@ -465,9 +465,16 @@ describe('Map', function() { } }); - const doc = yield Test.findOne().populate('books.$*.author'); + let doc = yield Test.findOne().populate('books.$*.author'); assert.equal(doc.books.get('key1').author.name, 'Ian Fleming'); + + doc.books.set('key2', { title: 'Live and Let Die', author: person._id }); + yield doc.save(); + + doc = yield Test.findOne().populate('books.$*.author'); + + assert.equal(doc.books.get('key2').author.name, 'Ian Fleming'); }); }); }); @@ -976,4 +983,56 @@ describe('Map', function() { assert.ifError(doc.validateSync()); }); }); + + it('tracks changes correctly (gh-9811)', function() { + const SubSchema = Schema({ + myValue: { + type: String + } + }, { _id: false }); + const schema = Schema({ + myMap: { + type: Map, + of: { + type: SubSchema + } + // required: true + } + }, { minimize: false, collection: 'test' }); + const Model = db.model('Test', schema); + return co(function*() { + const doc = yield Model.create({ + myMap: new Map() + }); + doc.myMap.set('abc', { myValue: 'some value' }); + const changes = doc.getChanges(); + assert.ok(!changes.$unset); + assert.deepEqual(changes, { $set: { 'myMap.abc': { myValue: 'some value' } } }); + }); + }); + + it('handles map of arrays (gh-9813)', function() { + const BudgetSchema = new mongoose.Schema({ + budgeted: { + type: Map, + of: [Number] + } + }); + + const Budget = db.model('Test', BudgetSchema); + + return co(function*() { + const _id = yield Budget.create({ + budgeted: new Map([['2020', [100, 200, 300]]]) + }).then(doc => doc._id); + + const doc = yield Budget.findById(_id); + doc.budgeted.get('2020').set(2, 10); + assert.deepEqual(doc.getChanges(), { $set: { 'budgeted.2020.2': 10 } }); + yield doc.save(); + + const res = yield Budget.findOne(); + assert.deepEqual(res.toObject().budgeted.get('2020'), [100, 200, 10]); + }); + }); }); diff --git a/test/typescript/base.ts b/test/typescript/base.ts index a663ef05e1b..4c7c9963b03 100644 --- a/test/typescript/base.ts +++ b/test/typescript/base.ts @@ -4,3 +4,5 @@ Object.values(mongoose.models).forEach(model => { model.modelName; model.findOne(); }); + +mongoose.pluralize(null); diff --git a/test/typescript/create.ts b/test/typescript/create.ts index 98cfa653003..cb76ce45172 100644 --- a/test/typescript/create.ts +++ b/test/typescript/create.ts @@ -13,9 +13,16 @@ Test.create({ _id: '0'.repeat(24), name: 'test' }).then((doc: ITest) => console. Test.create([{ name: 'test' }], { validateBeforeSave: false }).then((docs: ITest[]) => console.log(docs[0].name)); -Test.create({ name: 'test' }, { name: 'test2' }).then((doc: ITest) => console.log(doc.name)); +Test.create({ name: 'test' }, { name: 'test2' }).then((docs: ITest[]) => console.log(docs[0].name)); Test.insertMany({ name: 'test' }).then((doc: ITest) => console.log(doc.name)); Test.insertMany([{ name: 'test' }], { session: null }).then((docs: ITest[]) => console.log(docs[0].name)); -Test.create([{ name: 'test' }], { validateBeforeSave: true }).then((docs: ITest[]) => console.log(docs[0].name)); \ No newline at end of file +Test.create([{ name: 'test' }], { validateBeforeSave: true }).then((docs: ITest[]) => console.log(docs[0].name)); + +(async() => { + const [t1] = await Test.create([{ name: 'test' }]); + const [t2, t3, t4] = await Test.create({ name: 'test' }, { name: 'test' }, { name: 'test' }); + (await Test.create([{ name: 'test' }]))[0]; + (await Test.create({ name: 'test' }))._id; +})(); diff --git a/test/typescript/document.ts b/test/typescript/document.ts new file mode 100644 index 00000000000..4df05d386ed --- /dev/null +++ b/test/typescript/document.ts @@ -0,0 +1,61 @@ +import { Schema, model, Model, Document, Error } from 'mongoose'; + +const schema: Schema = new Schema({ name: { type: 'String', required: true }, address: new Schema({ city: { type: String, required: true } }) }); + +interface ITestBase { + name?: string; +} + +interface ITest extends ITestBase, Document {} + +const Test = model('Test', schema); + +void async function main() { + const doc: ITest = await Test.findOne().orFail(); + + const p: Promise = doc.remove(); + await p; +}(); + + +void async function run() { + const user = new Test({ name: {}, address: {} }); + const error = user.validateSync(); + if (error != null) { + const _error: Error.ValidationError = error.errors.address as Error.ValidationError; + } +}(); + +(function() { + const test = new Test(); + test.validate({ pathsToSkip: ['hello'] }); + test.validate({ pathsToSkip: 'name age' }); + test.validateSync({ pathsToSkip: ['name', 'age'] }); + test.validateSync({ pathsToSkip: 'name age' }); +})(); + +function gh10526(arg1: Model) { + const t = new arg1({ name: 'hello' }); +} + +function testMethods(): void { + interface IUser { + first: string; + last: string; + } + + interface IUserMethods { + fullName(): string; + } + + type User = Model + + const schema = new Schema({ first: String, last: String }); + schema.methods.fullName = function(): string { + return this.first + ' ' + this.last; + }; + const UserModel = model('User', schema); + + const doc = new UserModel({ first: 'test', last: 'test' }); + doc.fullName().toUpperCase(); +} \ No newline at end of file diff --git a/test/typescript/generics.ts b/test/typescript/generics.ts new file mode 100644 index 00000000000..595590ea7fa --- /dev/null +++ b/test/typescript/generics.ts @@ -0,0 +1,15 @@ +import { Model, Document } from 'mongoose'; + +class Repository { + private readonly Model: Model + + findById(id:string): Promise { + return Model.findById(id).exec(); + } +} + +class Foo { + name: string +} + +type Test = Repository; \ No newline at end of file diff --git a/test/typescript/global.ts b/test/typescript/global.ts index 6aae459ddaa..8887190240b 100644 --- a/test/typescript/global.ts +++ b/test/typescript/global.ts @@ -11,4 +11,6 @@ m.STATES.connected; m.connect('mongodb://localhost:27017/test').then(() => { console.log('Connected!'); -}); \ No newline at end of file +}); + +mongoose.Promise = Promise; \ No newline at end of file diff --git a/test/typescript/leanDocuments.ts b/test/typescript/leanDocuments.ts index 53e72a89bc0..25690030c0b 100644 --- a/test/typescript/leanDocuments.ts +++ b/test/typescript/leanDocuments.ts @@ -7,11 +7,12 @@ class Subdoc extends Document { } interface ITestBase { + _id?: number; name?: string; mixed?: any; } -interface ITest extends ITestBase, Document { +interface ITest extends ITestBase, Document { subdoc?: Subdoc; testMethod: () => number; id: string; @@ -32,13 +33,12 @@ void async function main() { const pojo = doc.toObject(); await pojo.save(); - const _doc: LeanDocument = await Test.findOne().orFail().lean(); + const _doc: ITestBase = await Test.findOne().orFail().lean(); await _doc.save(); _doc.testMethod(); _doc.name = 'test'; _doc.mixed = 42; - _doc.id = 'test2'; console.log(_doc._id); const hydrated = Test.hydrate(_doc); @@ -48,4 +48,36 @@ void async function main() { _docs[0].mixed = 42; const _doc2: ITestBase = await Test.findOne().lean(); -}(); \ No newline at end of file +}(); + +function gh10345() { + (function() { + interface User { + name: string; + id: number; + } + + const UserModel = model('User', new Schema({ name: String, id: Number })); + + const doc = new UserModel({ name: 'test', id: 42 }); + + const leanDoc = doc.toObject(); + leanDoc.id = 43; + })(); + + (async function() { + interface User { + name: string; + } + + const UserModel = model('User', new Schema({ name: String })); + + const doc = new UserModel({ name: 'test' }); + + const leanDoc = doc.toObject(); + leanDoc.id = 43; + + const doc2 = await UserModel.findOne().orFail().lean(); + doc2.id = 43; + })(); +} \ No newline at end of file diff --git a/test/typescript/main.test.js b/test/typescript/main.test.js index dcd3f2a038c..7806e97fd82 100644 --- a/test/typescript/main.test.js +++ b/test/typescript/main.test.js @@ -48,11 +48,12 @@ describe('typescript syntax', function() { }); it('queries', function() { - const errors = runTest('queries.ts'); + const errors = runTest('queries.ts', { strict: true }); if (process.env.D && errors.length) { console.log(errors); } - assert.equal(errors.length, 0); + assert.equal(errors.length, 1); + assert.ok(errors[0].messageText.includes('notAQueryHelper'), errors[0].messageText); }); it('create', function() { @@ -118,10 +119,12 @@ describe('typescript syntax', function() { if (process.env.D && errors.length) { console.log(errors); } - assert.equal(errors.length, 3); + assert.equal(errors.length, 5); assert.ok(errors[0].messageText.includes('Property \'save\' does not exist'), errors[0].messageText); assert.ok(errors[1].messageText.includes('Property \'save\' does not exist'), errors[1].messageText); assert.ok(errors[2].messageText.includes('Property \'testMethod\' does not exist'), errors[2].messageText); + assert.ok(errors[3].messageText.includes('Property \'id\' does not exist'), errors[3].messageText); + assert.ok(errors[4].messageText.includes('Property \'id\' does not exist'), errors[4].messageText); }); it('doc array', function() { @@ -174,7 +177,42 @@ describe('typescript syntax', function() { }); it('schema', function() { - const errors = runTest('schema.ts'); + const errors = runTest('schema.ts', { strict: true }); + if (process.env.D && errors.length) { + console.log(errors); + } + assert.equal(errors.length, 1); + const messageText = errors[0].messageText.messageText; + assert.ok(/Type '.*StringConstructor.*' is not assignable to type.*number/.test(messageText), messageText); + }); + + it('document', function() { + const errors = runTest('document.ts', { strict: true }); + if (process.env.D && errors.length) { + console.log(errors); + } + assert.equal(errors.length, 0); + }); + + it('populate', function() { + const errors = runTest('populate.ts', { strict: true }); + if (process.env.D && errors.length) { + console.log(errors); + } + assert.equal(errors.length, 1); + assert.ok(errors[0].messageText.includes('Property \'save\' does not exist'), errors[0].messageText); + }); + + it('generics', function() { + const errors = runTest('generics.ts'); + if (process.env.D && errors.length) { + console.log(errors); + } + assert.equal(errors.length, 0); + }); + + it('virtuals', function() { + const errors = runTest('virtuals.ts'); if (process.env.D && errors.length) { console.log(errors); } diff --git a/test/typescript/middleware.ts b/test/typescript/middleware.ts index 5c941ea887a..432e7a37e7c 100644 --- a/test/typescript/middleware.ts +++ b/test/typescript/middleware.ts @@ -1,4 +1,4 @@ -import { Schema, model, Model, Document, Types, Query, Aggregate } from 'mongoose'; +import { Schema, model, Model, Document, SaveOptions, Query, Aggregate } from 'mongoose'; const schema: Schema = new Schema({ name: { type: 'String' } }); @@ -18,6 +18,15 @@ schema.post>('aggregate', async function(res: Array) { console.log('Pipeline', this.pipeline(), res[0]); }); +schema.pre(['save', 'validate'], { query: false, document: true }, async function applyChanges() { + await Test.findOne({}); +}); + +schema.pre('save', function(next, opts: SaveOptions) { + console.log(opts.session); + next(); +}); + interface ITest extends Document { name?: string; } @@ -30,9 +39,32 @@ schema.post('save', function() { console.log(this.name); }); -// eslint-disable-next-line @typescript-eslint/ban-types schema.post('save', function(err: Error, res: ITest, next: Function) { console.log(this.name, err.stack); }); +schema.pre>('insertMany', function() { + const name: string = this.name; + return Promise.resolve(); +}); + +schema.pre>('insertMany', { document: false, query: false }, function() { + console.log(this.name); +}); + +schema.pre>('insertMany', function(next) { + console.log(this.name); + next(); +}); + +schema.pre>('insertMany', function(next, doc: ITest) { + console.log(this.name, doc); + next(); +}); + +schema.pre>('insertMany', function(next, docs: Array) { + console.log(this.name, docs); + next(); +}); + const Test = model('Test', schema); diff --git a/test/typescript/models.ts b/test/typescript/models.ts index c65402c7829..d8b37b5dc4e 100644 --- a/test/typescript/models.ts +++ b/test/typescript/models.ts @@ -1,4 +1,4 @@ -import { Schema, Document, Model, connection } from 'mongoose'; +import { Schema, Document, Model, connection, model } from 'mongoose'; function conventionalSyntax(): void { interface ITest extends Document { @@ -14,6 +14,37 @@ function conventionalSyntax(): void { const bar = (SomeModel: Model) => console.log(SomeModel); bar(Test); + + const doc = new Test({ foo: '42' }); + console.log(doc.foo); + doc.save(); +} + +function rawDocSyntax(): void { + interface ITest { + foo: string; + } + + interface ITestMethods { + bar(): number; + } + + type TestModel = Model; + + const TestSchema = new Schema({ + foo: { type: String, required: true } + }); + + const Test = connection.model('Test', TestSchema); + + const bar = (SomeModel: Model) => console.log(SomeModel); + + bar(Test); + + const doc = new Test({ foo: '42' }); + console.log(doc.foo); + console.log(doc.bar()); + doc.save(); } function tAndDocSyntax(): void { @@ -49,6 +80,66 @@ function insertManyTest() { }); } +function schemaStaticsWithoutGenerics() { + const UserSchema = new Schema({}); + UserSchema.statics.static1 = function() { return ''; }; + + interface IUserDocument extends Document { + instanceField: string; + } + interface IUserModel extends Model { + static1: () => string; + } + + const UserModel: IUserModel = model('User', UserSchema); + UserModel.static1(); +} + +function gh10074() { + interface IDog { + breed: string; + name: string; + age: number; + } + + type IDogDocument = IDog & Document; + + const DogSchema = new Schema( + { + breed: { type: String }, + name: { type: String }, + age: { type: Number } + } + ); + + const Dog = model>('dog', DogSchema); + + const rex = new Dog({ + breed: 'test', + name: 'rex', + age: '50' + }); +} + +async function gh10359() { + interface Group { + groupId: string; + } + + interface User extends Group { + firstName: string; + lastName: string; + } + + async function foo(model: Model): Promise { + const doc: T | null = await model.findOne({ groupId: 'test' }).lean().exec(); + return doc; + } + + const UserModel = model('gh10359', new Schema({ firstName: String, lastName: String, groupId: String })); + const u: User | null = await foo(UserModel); +} + const ExpiresSchema = new Schema({ ttl: { type: Date, diff --git a/test/typescript/populate.ts b/test/typescript/populate.ts new file mode 100644 index 00000000000..89d95ac6d96 --- /dev/null +++ b/test/typescript/populate.ts @@ -0,0 +1,41 @@ +import { Schema, model, Document, PopulatedDoc } from 'mongoose'; +// Use the mongodb ObjectId to make instanceof calls possible +import { ObjectId } from 'mongodb'; + +interface Child { + name: string; +} + +const childSchema: Schema = new Schema({ name: String }); +const ChildModel = model('Child', childSchema); + +interface Parent { + child?: PopulatedDoc>, + name?: string +} + +const ParentModel = model('Parent', new Schema({ + child: { type: 'ObjectId', ref: 'Child' }, + name: String +})); + +ParentModel.findOne({}).populate('child').orFail().then((doc: Parent & Document) => { + const child = doc.child; + if (child == null || child instanceof ObjectId) { + throw new Error('should be populated'); + } else { + useChildDoc(child); + } + const lean = doc.toObject(); + const leanChild = lean.child; + if (leanChild == null || leanChild instanceof ObjectId) { + throw new Error('should be populated'); + } else { + const name = leanChild.name; + leanChild.save(); + } +}); + +function useChildDoc(child: Child): void { + console.log(child.name.trim()); +} \ No newline at end of file diff --git a/test/typescript/queries.ts b/test/typescript/queries.ts index 1c935e46d13..15c40232c08 100644 --- a/test/typescript/queries.ts +++ b/test/typescript/queries.ts @@ -1,20 +1,64 @@ -import { Schema, model, Document, Types } from 'mongoose'; +import { Schema, model, Document, Types, Query, Model, QueryWithHelpers, PopulatedDoc } from 'mongoose'; +import { ObjectId } from 'mongodb'; -const schema: Schema = new Schema({ name: { type: 'String' } }); +interface QueryHelpers { + _byName(this: QueryWithHelpers, name: string): QueryWithHelpers, ITest, QueryHelpers>; + byName(name: string): QueryWithHelpers, ITest, QueryHelpers>; +} + +const childSchema: Schema = new Schema({ name: String }); +const ChildModel = model('Child', childSchema); + +const schema: Schema> = new Schema({ + name: { type: 'String' }, + tags: [String], + child: { type: 'ObjectId', ref: 'Child' }, + docs: [{ _id: 'ObjectId', id: Number, tags: [String] }], + endDate: Date +}); + +schema.query._byName = function(name: string): QueryWithHelpers { + return this.find({ name }); +}; + +schema.query.byName = function(name: string): QueryWithHelpers { + this.notAQueryHelper(); + return this._byName(name); +}; + +interface Child { + name: string; +} +interface ISubdoc extends Document { + myId?: Types.ObjectId; + id?: number; + tags?: string[]; +} interface ITest extends Document { name?: string; age?: number; parent?: Types.ObjectId; + child?: PopulatedDoc>, + tags?: string[]; + docs?: ISubdoc[]; + endDate?: Date; } -const Test = model('Test', schema); +const Test = model>('Test', schema); + +Test.find({}, {}, { populate: { path: 'child', model: ChildModel, match: true } }).exec().then((res: Array) => console.log(res)); + +Test.find().byName('test').byName('test2').orFail().exec().then(console.log); Test.count({ name: /Test/ }).exec().then((res: number) => console.log(res)); +Test.findOne({ 'docs.id': 42 }).exec().then(console.log); +// ObjectId casting Test.find({ parent: new Types.ObjectId('0'.repeat(24)) }); Test.find({ parent: '0'.repeat(24) }); +// Operators Test.find({ name: { $in: ['Test'] } }).exec().then((res: Array) => console.log(res)); Test.find({ name: 'test' }, (err: Error, docs: ITest[]) => { @@ -36,6 +80,50 @@ Test.findOneAndUpdate({ name: 'test' }, { name: 'test2' }).exec().then((res: ITe Test.findOneAndUpdate({ name: 'test' }, { name: 'test2' }).then((res: ITest | null) => console.log(res)); Test.findOneAndUpdate({ name: 'test' }, { $set: { name: 'test2' } }).then((res: ITest | null) => console.log(res)); Test.findOneAndUpdate({ name: 'test' }, { $inc: { age: 2 } }).then((res: ITest | null) => console.log(res)); -Test.findOneAndUpdate({ name: 'test' }, { name: 'test3' }, { upsert: true }).then((res: ITest) => { res.name = 'test4'; }); +Test.findOneAndUpdate({ name: 'test' }, { name: 'test3' }, { upsert: true, new: true }).then((res: ITest) => { res.name = 'test4'; }); +Test.findOneAndUpdate({ name: 'test' }, { name: 'test3' }, { upsert: true, returnOriginal: false }).then((res: ITest) => { res.name = 'test4'; }); +Test.findOneAndUpdate({ name: 'test' }, { name: 'test3' }, { rawResult: true }).then((res: any) => { console.log(res.ok); }); +Test.findOneAndUpdate({ name: 'test' }, { name: 'test3' }, { new: true, upsert: true, rawResult: true }).then((res: any) => { console.log(res.ok); }); Test.findOneAndReplace({ name: 'test' }, { _id: new Types.ObjectId(), name: 'test2' }).exec().then((res: ITest | null) => console.log(res)); + +Test.findOneAndUpdate({ name: 'test' }, { $addToSet: { tags: 'each' } }); +Test.findOneAndUpdate({ name: 'test' }, { $push: { tags: 'each' } }); +Test.findOneAndUpdate({ name: 'test' }, { $pull: { docs: { 'nested.id': 1 } } }); + +Test.findOneAndUpdate({ name: 'test', 'docs.id': 1 }, { $pull: { 'docs.$.tags': 'foo' } }); + +const update = Math.random() > 0.5 ? { $unset: { 'docs.0': 1 } } : { age: 55 }; +Test.findOneAndUpdate({ name: 'test' }, update); + +Test.findOneAndUpdate({ name: 'test' }, { $currentDate: { endDate: true } }); +Test.findOneAndUpdate({ name: 'test' }, [{ $set: { endDate: true } }]); + +Test.findByIdAndUpdate({ name: 'test' }, { name: 'test2' }, (err, doc) => console.log(doc)); + +Test.findOneAndUpdate({ name: 'test' }, { 'docs.0.myId': '0'.repeat(24) }); + +const query: Query = Test.findOne(); +query instanceof Query; + +// Chaining +Test.findOne().where({ name: 'test' }); +Test.where().find({ name: 'test' }); + +// Super generic query +function testGenericQuery(): void { + interface CommonInterface extends Document { + something: string; + content: T; + } + + async function findSomething(model: Model>): Promise> { + return model.findOne({ something: 'test' }).orFail().exec(); + } +} + +function eachAsync(): void { + Test.find().cursor().eachAsync((doc: ITest) => console.log(doc.name)); + + Test.find().cursor().eachAsync((docs: ITest[]) => console.log(docs[0].name), { batchSize: 2 }); +} \ No newline at end of file diff --git a/test/typescript/schema.ts b/test/typescript/schema.ts index 6c9b10488ae..f11b3ae952c 100644 --- a/test/typescript/schema.ts +++ b/test/typescript/schema.ts @@ -1,16 +1,173 @@ -import { Schema } from 'mongoose'; +import { Schema, Document, SchemaDefinition, Model, DocumentDefinition } from 'mongoose'; -const schema: Schema = new Schema({ - name: String, - enumWithNull: { +enum Genre { + Action, + Adventure, + Comedy +} + +interface Actor { + name: string, + age: number +} +const actorSchema = + new Schema, Actor>({ name: { type: String }, age: { type: Number } }); + +interface Movie { + title?: string, + featuredIn?: string, + rating?: number, + genre?: string, + actionIntensity?: number, + status?: string, + actors: Actor[] +} + +const movieSchema = new Schema, Model>, Movie>({ + title: { + type: String, + index: 'text' + }, + featuredIn: { type: String, - enum: ['Test', null], + enum: ['Favorites', null], default: null }, - numberWithMax: { + rating: { type: Number, required: [true, 'Required'], min: [0, 'MinValue'], - max: [24, 'MaxValue'] + max: [5, 'MaxValue'] + }, + genre: { + type: String, + enum: Genre, + required: true + }, + actionIntensity: { + type: Number, + required: [ + function(this: { genre: Genre }) { + return this.genre === Genre.Action; + }, + 'Action intensity required for action genre' + ] + }, + status: { + type: String, + enum: { + values: ['Announced', 'Released'], + message: 'Invalid value for `status`' + } + }, + actors: { + type: [actorSchema], + default: undefined } }); + +movieSchema.index({ status: 1, 'actors.name': 1 }); +movieSchema.index({ title: 'text' }, { + weights: { title: 10 } +}); +movieSchema.index({ rating: -1 }); +movieSchema.index({ title: 1 }, { unique: true }); + +// Using `SchemaDefinition` +interface IProfile { age: number; } +interface ProfileDoc extends Document, IProfile {} +const ProfileSchemaDef: SchemaDefinition = { age: Number }; +export const ProfileSchema = new Schema, ProfileDoc>(ProfileSchemaDef); + +interface IUser { + email: string; + profile: ProfileDoc; +} + +interface UserDoc extends Document, IUser {} + +const ProfileSchemaDef2: SchemaDefinition = { + age: Schema.Types.Number +}; + +const ProfileSchema2: Schema> = new Schema(ProfileSchemaDef2); + +const UserSchemaDef: SchemaDefinition = { + email: String, + profile: ProfileSchema2 +}; + +async function gh9857() { + interface User { + name: number; + active: boolean; + points: number; + } + + type UserDocument = Document; + type UserSchemaDefinition = SchemaDefinition; + type UserModel = Model; + + const schemaDefinition: UserSchemaDefinition = { + name: { type: String }, + active: { type: Boolean }, + points: Number + }; + + const schema = new Schema(schemaDefinition); +} + +function gh10261() { + interface ValuesEntity { + values: string[]; + } + + const type: ReadonlyArray = [String]; + const colorEntitySchemaDefinition: SchemaDefinition> = { + values: { + type: type, + required: true + } + }; +} + +function gh10287() { + interface SubSchema { + testProp: string; + } + + const subSchema = new Schema, SubSchema>({ + testProp: Schema.Types.String + }); + + interface MainSchema { + subProp: SubSchema + } + + const mainSchema1 = new Schema, MainSchema>({ + subProp: subSchema + }); + + const mainSchema2 = new Schema, MainSchema>({ + subProp: { + type: subSchema + } + }); +} + +function gh10370() { + const movieSchema = new Schema, Movie>({ + actors: { + type: [actorSchema] + } + }); +} + +function gh10409() { + interface Something { + field: Date; + } + const someSchema = new Schema, Something>({ + field: { type: Date } + }); +} \ No newline at end of file diff --git a/test/typescript/subdocuments.ts b/test/typescript/subdocuments.ts index a1b71f4e0aa..ed0db7eef10 100644 --- a/test/typescript/subdocuments.ts +++ b/test/typescript/subdocuments.ts @@ -1,4 +1,4 @@ -import { Schema, model, Document } from 'mongoose'; +import { Schema, model, Model, Document } from 'mongoose'; const childSchema: Schema = new Schema({ name: String }); @@ -26,3 +26,30 @@ const doc: ITest = new Test({}); doc.child1 = { name: 'test1' }; doc.child2 = { name: 'test2' }; + +function gh10674() { + type Foo = { + bar: string + schedule: { + date: string; + hours: number; + }[]; + }; + + type FooModel = Model; + + const FooSchema = new Schema( + { + bar: { type: String }, + schedule: { + type: [ + { + date: { type: String, required: true }, + hours: { type: Number, required: true } + } + ], + required: true + } + } + ); +} diff --git a/test/typescript/virtuals.ts b/test/typescript/virtuals.ts new file mode 100644 index 00000000000..7f391212fd0 --- /dev/null +++ b/test/typescript/virtuals.ts @@ -0,0 +1,63 @@ +import { Document, Model, Schema, model } from 'mongoose'; + +interface IPerson { + _id: number; + firstName: string; + lastName: string; + + fullName: string; +} + +interface IPet { + name: string; + isDeleted: boolean; + ownerId: number; + + owner: IPerson; +} + +const personSchema = new Schema, IPerson>({ + _id: { type: Number, required: true }, + firstName: { type: String, required: true }, + lastName: { type: String, required: true } +}); + +const petSchema = new Schema, IPet>({ + name: { type: String, required: true }, + ownerId: { type: Number, required: true }, + isDeleted: { type: Boolean, default: false } +}); + +// Virtual getters and setters +personSchema.virtual('fullName') + .get(function(value, virtual, doc) { + return `${this.firstName} ${this.lastName}`; + }) + .set(function(value, virtual, doc) { + const splittedName = value.split(' '); + this.firstName = splittedName[0]; + this.lastName = splittedName[1]; + }); + +// Populated virtuals +petSchema.virtual('owner', { + ref: 'Person', + localField: 'ownerId', + foreignField: '_id', + justOne: true, + autopopulate: true, + options: { + match: { isDeleted: false } + } +}); + +const Person = model('Person', personSchema); +const Pet = model('Pet', petSchema); + +(async() => { + const person = await Person.create({ _id: 1, firstName: 'John', lastName: 'Wick' }); + await Pet.create({ name: 'Andy', ownerId: person._id }); + + const pet = await Pet.findOne().orFail().populate('owner'); + console.log(pet.owner.fullName); // John Wick +})(); diff --git a/test/utils.test.js b/test/utils.test.js index 2c9a83ebf53..44cd7b202ab 100644 --- a/test/utils.test.js +++ b/test/utils.test.js @@ -365,4 +365,47 @@ describe('utils', function() { assert.deepEqual(out, o); }); }); + describe('errorToPOJO(...)', () => { + it('converts an error to a POJO', () => { + // Arrange + const err = new Error('Something bad happened.'); + err.metadata = { hello: 'world' }; + + // Act + const pojoError = utils.errorToPOJO(err); + + // Assert + assert.equal(pojoError.message, 'Something bad happened.'); + assert.ok(pojoError.stack); + assert.deepEqual(pojoError.metadata, { hello: 'world' }); + }); + it('throws an error when argument is not an error object', () => { + let errorWhenConverting; + try { + utils.errorToPOJO({ message: 'I am a POJO', stack: 'Does not matter' }); + } catch (_err) { + errorWhenConverting = _err; + } + assert.equal(errorWhenConverting.message, '`error` must be `instanceof Error`.'); + }); + it('works with classes that extend `Error`', () => { + // Arrange + class OperationalError extends Error { + constructor(message) { + super(message); + } + } + + const err = new OperationalError('Something bad happened.'); + err.metadata = { hello: 'world' }; + + // Act + const pojoError = utils.errorToPOJO(err); + + // Assert + assert.equal(pojoError.message, 'Something bad happened.'); + assert.ok(pojoError.stack); + assert.deepEqual(pojoError.metadata, { hello: 'world' }); + }); + }); }); diff --git a/test/versioning.test.js b/test/versioning.test.js index 7c4efd9af24..3380015d167 100644 --- a/test/versioning.test.js +++ b/test/versioning.test.js @@ -540,6 +540,24 @@ describe('versioning', function() { }); }); + it('should persist correctly when optimisticConcurrency is true gh-10128', function() { + const thingSchema = new Schema({ price: Number }, { optimisticConcurrency: true }); + const Thing = db.model('Thing', thingSchema); + return co(function*() { + const thing = yield Thing.create({ price: 1 }); + yield thing.save(); + assert.equal(thing.__v, 0); + const thing_1 = yield Thing.findById(thing.id); + const thing_2 = yield Thing.findById(thing.id); + thing_1.set({ price: 2 }); + yield thing_1.save(); + assert.equal(thing_1.__v, 1); + thing_2.set({ price: 1 }); + const err = yield thing_2.save().then(() => null, err => err); + assert.equal(err.name, 'DocumentNotFoundError'); + }); + }); + describe('versioning is off', function() { it('when { safe: false } is set (gh-1520)', function(done) { const schema1 = new Schema({ title: String }, { safe: false }); diff --git a/website.js b/website.js index 6687a41e802..1df3e091f34 100644 --- a/website.js +++ b/website.js @@ -4,6 +4,7 @@ Error.stackTraceLimit = Infinity; const acquit = require('acquit'); const fs = require('fs'); +const path = require('path'); const pug = require('pug'); const pkg = require('./package'); const linktype = require('./docs/helpers/linktype'); @@ -32,7 +33,13 @@ const tests = [ ...acquit.parse(fs.readFileSync('./test/es-next/findoneandupdate.test.es6.js').toString()), ...acquit.parse(fs.readFileSync('./test/docs/custom-casting.test.js').toString()), ...acquit.parse(fs.readFileSync('./test/es-next/getters-setters.test.es6.js').toString()), - ...acquit.parse(fs.readFileSync('./test/es-next/virtuals.test.es6.js').toString()) + ...acquit.parse(fs.readFileSync('./test/es-next/virtuals.test.es6.js').toString()), + ...acquit.parse(fs.readFileSync('./test/docs/defaults.test.js').toString()), + ...acquit.parse(fs.readFileSync('./test/docs/discriminators.test.js').toString()), + ...acquit.parse(fs.readFileSync('./test/es-next/promises.test.es6.js').toString()), + ...acquit.parse(fs.readFileSync('./test/docs/schematypes.test.js').toString()), + ...acquit.parse(fs.readFileSync('./test/docs/validation.test.js').toString()), + ...acquit.parse(fs.readFileSync('./test/docs/schemas.test.js').toString()) ]; function getVersion() { @@ -61,12 +68,17 @@ pkg.version = getVersion(); pkg.latest4x = getLatestLegacyVersion('4.'); pkg.latest38x = getLatestLegacyVersion('3.8'); +// Create api dir if it doesn't already exist +try { + fs.mkdirSync('./docs/api'); +} catch (err) {} // eslint-disable-line no-empty + require('./docs/splitApiDocs'); -const filemap = Object.assign({}, require('./docs/source'), require('./docs/tutorials')); +const filemap = Object.assign({}, require('./docs/source'), require('./docs/tutorials'), require('./docs/typescript')); const files = Object.keys(filemap); -const wrapMarkdown = md => ` -extends ../layout +const wrapMarkdown = (md, baseLayout) => ` +extends ${baseLayout} append style link(rel="stylesheet", href="/docs/css/inlinecpc.css") @@ -114,7 +126,7 @@ function pugify(filename, options, newfile) { const lines = contents.split('\n'); lines.splice(2, 0, cpc); contents = lines.join('\n'); - contents = wrapMarkdown(contents); + contents = wrapMarkdown(contents, path.relative(path.dirname(filename), path.join(__dirname, 'docs/layout'))); newfile = filename.replace('.md', '.html'); } @@ -160,12 +172,3 @@ files.forEach(function(file) { }); } }); - -const _acquit = require('./docs/source/acquit'); -const acquitFiles = Object.keys(_acquit); -acquitFiles.forEach(function(file) { - const filename = __dirname + '/docs/acquit.pug'; - _acquit[file].editLink = 'https://github.com/Automattic/mongoose/blob/master/' + - _acquit[file].input.replace(process.cwd(), ''); - pugify(filename, _acquit[file], __dirname + '/docs/' + file); -});