├── CNAME ├── .npmrc ├── types ├── .gitignore ├── augmentations.d.ts ├── callback.d.ts ├── virtuals.d.ts ├── validation.d.ts └── helpers.d.ts ├── benchmarks ├── typescript │ └── simple │ │ ├── .npmrc │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── index.ts ├── insertManySimple.js └── createDeepNestedDocArray.js ├── lib ├── drivers │ ├── SPEC.md │ └── node-mongodb-native │ │ ├── bulkWriteResult.js │ │ └── index.js ├── helpers │ ├── timers.js │ ├── query │ │ ├── validOps.js │ │ ├── isOperator.js │ │ ├── trusted.js │ │ ├── sanitizeProjection.js │ │ ├── hasDollarKeys.js │ │ ├── handleReadPreferenceAliases.js │ │ ├── applyGlobalOption.js │ │ ├── sanitizeFilter.js │ │ └── castFilterPath.js │ ├── specialProperties.js │ ├── populate │ │ ├── leanPopulateMap.js │ │ ├── skipPopulateValue.js │ │ ├── validateRef.js │ │ ├── removeDeselectedForeignField.js │ │ ├── lookupLocalFields.js │ │ └── setPopulatedVirtualValue.js │ ├── firstKey.js │ ├── isPromise.js │ ├── schema │ │ ├── addAutoId.js │ │ ├── cleanPositionalOperators.js │ │ ├── applyBuiltinPlugins.js │ │ ├── handleTimestampOption.js │ │ ├── handleIdOption.js │ │ ├── applyReadConcern.js │ │ ├── idGetter.js │ │ ├── getKeysInSchemaOrder.js │ │ ├── merge.js │ │ ├── getSubdocumentStrictValue.js │ │ └── getPath.js │ ├── isAsyncFunction.js │ ├── getFunctionName.js │ ├── once.js │ ├── isBsonType.js │ ├── projection │ │ ├── isNestedProjection.js │ │ ├── isSubpath.js │ │ ├── isDefiningProjection.js │ │ ├── isPathSelectedInclusive.js │ │ ├── parseProjection.js │ │ ├── isPathExcluded.js │ │ ├── isExclusive.js │ │ ├── hasIncludedChildren.js │ │ └── isInclusive.js │ ├── model │ │ ├── applyStatics.js │ │ ├── decorateBulkWriteResult.js │ │ ├── pushNestedArrayPaths.js │ │ └── applyStaticHooks.js │ ├── isObject.js │ ├── indexes │ │ ├── isTextIndex.js │ │ ├── applySchemaCollation.js │ │ ├── isDefaultIdIndex.js │ │ ├── isTimeseriesIndex.js │ │ ├── decorateDiscriminatorIndexOptions.js │ │ └── isIndexSpecEqual.js │ ├── getDefaultBulkwriteResult.js │ ├── getConstructorName.js │ ├── topology │ │ ├── allServersUnknown.js │ │ ├── isSSLError.js │ │ └── isAtlas.js │ ├── path │ │ ├── parentPaths.js │ │ └── setDottedPath.js │ ├── isPOJO.js │ ├── error │ │ └── combinePathErrors.js │ ├── omitUndefined.js │ ├── discriminator │ │ ├── areDiscriminatorValuesEqual.js │ │ ├── checkEmbeddedDiscriminatorKeyProjection.js │ │ ├── getDiscriminatorByValue.js │ │ ├── getSchemaDiscriminatorByValue.js │ │ ├── getConstructor.js │ │ └── applyEmbeddedDiscriminators.js │ ├── each.js │ ├── isSimpleValidator.js │ ├── immediate.js │ ├── isMongooseObject.js │ ├── createJSONSchemaTypeDefinition.js │ ├── minimize.js │ ├── parallelLimit.js │ ├── arrayDepth.js │ ├── update │ │ ├── updatedPathsByArrayFilter.js │ │ ├── decorateUpdateWithVersionKey.js │ │ ├── modifiedPaths.js │ │ └── removeUnusedArrayFilters.js │ ├── document │ │ ├── handleSpreadDoc.js │ │ ├── getDeepestSubdocumentForPath.js │ │ └── cleanModifiedSubpaths.js │ ├── timestamps │ │ └── setDocumentTimestamps.js │ ├── printJestWarning.js │ └── symbols.js ├── options │ ├── propertyOptions.js │ ├── saveOptions.js │ ├── schemaUnionOptions.js │ ├── schemaBufferOptions.js │ ├── populateOptions.js │ └── schemaMapOptions.js ├── schema │ ├── symbols.js │ ├── operators │ │ ├── exists.js │ │ ├── type.js │ │ ├── helpers.js │ │ ├── bitwise.js │ │ └── text.js │ └── index.js ├── types │ ├── array │ │ └── isMongooseArray.js │ ├── documentArray │ │ └── isMongooseDocumentArray.js │ ├── uuid.js │ ├── double.js │ ├── decimal128.js │ ├── index.js │ └── objectid.js ├── driver.js ├── plugins │ ├── index.js │ └── validateBeforeSave.js ├── error │ ├── mongooseError.js │ ├── browserMissingSchema.js │ ├── overwriteModel.js │ ├── createCollectionsError.js │ ├── syncIndexes.js │ ├── parallelSave.js │ ├── missingSchema.js │ ├── parallelValidate.js │ ├── invalidSchemaOption.js │ ├── objectExpected.js │ ├── strictPopulate.js │ ├── strict.js │ ├── objectParameter.js │ ├── version.js │ ├── eachAsyncMultiError.js │ ├── bulkWriteError.js │ ├── notFound.js │ └── divergentArray.js ├── modifiedPathsSnapshot.js ├── options.js ├── index.js ├── connectionState.js ├── cast │ ├── objectid.js │ ├── boolean.js │ ├── int32.js │ ├── number.js │ ├── uuid.js │ ├── decimal128.js │ ├── string.js │ ├── date.js │ └── bigint.js ├── validOptions.js └── constants.js ├── History.md ├── docs ├── images │ ├── dk.png │ ├── mixmax.png │ ├── asyncawait.png │ ├── link_64x64.png │ ├── square_bg.png │ ├── fortunegames.jpg │ ├── favicon │ │ ├── favicon.ico │ │ ├── apple-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── ms-icon-144x144.png │ │ ├── ms-icon-150x150.png │ │ ├── ms-icon-310x310.png │ │ ├── ms-icon-70x70.png │ │ ├── apple-icon-57x57.png │ │ ├── apple-icon-60x60.png │ │ ├── apple-icon-72x72.png │ │ ├── apple-icon-76x76.png │ │ ├── android-icon-144x144.png │ │ ├── android-icon-192x192.png │ │ ├── android-icon-36x36.png │ │ ├── android-icon-48x48.png │ │ ├── android-icon-72x72.png │ │ ├── android-icon-96x96.png │ │ ├── apple-icon-114x114.png │ │ ├── apple-icon-120x120.png │ │ ├── apple-icon-144x144.png │ │ ├── apple-icon-152x152.png │ │ ├── apple-icon-180x180.png │ │ ├── apple-icon-precomposed.png │ │ ├── browserconfig.xml │ │ └── manifest.json │ ├── logo-poprey-com-white-100.png │ ├── localize-mongoose-ad-banner-2x.jpg │ ├── tidelift.svg │ ├── search.svg │ └── pencil.svg ├── deprecations.md ├── source │ ├── home.js │ ├── typescript.js │ ├── tutorials.js │ └── utils.js ├── api.md ├── browser.md ├── advanced_schemas.md ├── incompatible_packages.md ├── css │ ├── carbonads.css │ ├── copy-code.css │ └── inlinecpc.css ├── includes │ └── native.pug ├── sponsors.pug ├── search.pug ├── js │ ├── redirect-old-api.js │ ├── navbar-search.js │ ├── theme-toggle.js │ └── mobile-navbar-toggle.js ├── customschematypes.md ├── tutorials │ └── custom-casting.md └── check-version.md ├── .github ├── codeql │ └── codeql-config.yml ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── other.yml │ └── feature.yml ├── dependabot.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── stale.yml │ ├── publish.yml │ ├── benchmark.yml │ ├── tidelift-alignment.yml │ └── tsd.yml ├── migrating_to_5.md ├── SECURITY.md ├── test ├── types │ ├── objectid.test.ts │ ├── esm.test.ts │ ├── sanitizeFilter.test.ts │ ├── connect.test.ts │ ├── generics.test.ts │ ├── mongo.test.ts │ ├── collection.test.ts │ ├── methods.test.ts │ ├── helpers.test.ts │ ├── Errors.test.ts │ ├── check-types-filename.js │ ├── utility.test.ts │ └── queryhelpers.test.ts ├── isolated │ └── project-has-error.toJSON.js ├── subdirs.test.js ├── esm.test.js ├── helpers │ ├── projection.isExclusive.test.js │ ├── projection.isInclusive.test.js │ ├── update.applyTimestampsToUpdate.test.js │ ├── update.removeUnusedArrayFilters.test.js │ ├── projection.test.js │ ├── getFunctionName.test.js │ ├── common.test.js │ ├── applyWriteConcern.test.js │ ├── schema.cleanPositionalOperators.test.js │ ├── update.applyTimestampsToChildren.test.js │ ├── indexes.isIndexSpecEqual.js │ ├── isAsyncFunction.test.js │ ├── query.selectPopulatedFields.test.js │ ├── getModelsMapForPopulate.test.js │ ├── isObject.test.js │ ├── get.test.js │ └── schema.getPath.test.js ├── virtualtype.test.js ├── schemaname.test.js ├── deno.mjs ├── schema.string.test.js ├── schema.number.test.js ├── schema.boolean.test.js ├── parallelLimit.test.js ├── types.decimal128.test.js ├── schema.mixed.test.js ├── multiple-require-instance.test.js └── types.embeddeddocument.test.js ├── lgtm.yml ├── tsconfig.json ├── scripts ├── create-tarball.js ├── tsc-diagnostics-check.js └── static.js ├── .mocharc.yml ├── tools ├── auth.js ├── repl.js └── sharded.js ├── .npmignore ├── release-items.md ├── LICENSE.md └── .gitignore /CNAME: -------------------------------------------------------------------------------- 1 | mongoosejs.com 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /types/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !**/*.d.ts 3 | -------------------------------------------------------------------------------- /benchmarks/typescript/simple/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /lib/drivers/SPEC.md: -------------------------------------------------------------------------------- 1 | 2 | # Driver Spec 3 | 4 | TODO 5 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | This file has been renamed [CHANGELOG.md](./CHANGELOG.md) 2 | -------------------------------------------------------------------------------- /lib/helpers/timers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.setTimeout = setTimeout; 4 | -------------------------------------------------------------------------------- /benchmarks/typescript/simple/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "./*.ts" 4 | ] 5 | } -------------------------------------------------------------------------------- /docs/images/dk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/dk.png -------------------------------------------------------------------------------- /docs/images/mixmax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/mixmax.png -------------------------------------------------------------------------------- /docs/images/asyncawait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/asyncawait.png -------------------------------------------------------------------------------- /docs/images/link_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/link_64x64.png -------------------------------------------------------------------------------- /docs/images/square_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/square_bg.png -------------------------------------------------------------------------------- /docs/images/fortunegames.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/fortunegames.jpg -------------------------------------------------------------------------------- /docs/deprecations.md: -------------------------------------------------------------------------------- 1 | # Deprecation Warnings 2 | 3 | There are no current deprecation warnings for Mongoose 9.x. 4 | -------------------------------------------------------------------------------- /docs/images/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/favicon.ico -------------------------------------------------------------------------------- /.github/codeql/codeql-config.yml: -------------------------------------------------------------------------------- 1 | name: "codeql config" 2 | 3 | languages: 4 | - 'javascript' 5 | paths: 6 | - lib 7 | -------------------------------------------------------------------------------- /docs/images/favicon/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/apple-icon.png -------------------------------------------------------------------------------- /migrating_to_5.md: -------------------------------------------------------------------------------- 1 | This guide has moved to the [Mongoose docs site](https://mongoosejs.com/docs/migrating_to_5.html). 2 | -------------------------------------------------------------------------------- /lib/helpers/query/validOps.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('../../constants').queryMiddlewareFunctions; 4 | -------------------------------------------------------------------------------- /lib/helpers/specialProperties.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = new Set(['__proto__', 'constructor', 'prototype']); 4 | -------------------------------------------------------------------------------- /docs/images/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /docs/images/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /docs/images/favicon/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/favicon-96x96.png -------------------------------------------------------------------------------- /docs/images/favicon/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/ms-icon-144x144.png -------------------------------------------------------------------------------- /docs/images/favicon/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/ms-icon-150x150.png -------------------------------------------------------------------------------- /docs/images/favicon/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/ms-icon-310x310.png -------------------------------------------------------------------------------- /docs/images/favicon/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/ms-icon-70x70.png -------------------------------------------------------------------------------- /lib/helpers/populate/leanPopulateMap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * ignore 5 | */ 6 | 7 | module.exports = new WeakMap(); 8 | -------------------------------------------------------------------------------- /docs/images/favicon/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/apple-icon-57x57.png -------------------------------------------------------------------------------- /docs/images/favicon/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/apple-icon-60x60.png -------------------------------------------------------------------------------- /docs/images/favicon/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/apple-icon-72x72.png -------------------------------------------------------------------------------- /docs/images/favicon/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/apple-icon-76x76.png -------------------------------------------------------------------------------- /docs/images/logo-poprey-com-white-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/logo-poprey-com-white-100.png -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Please follow the instructions on [Tidelift's security page](https://tidelift.com/docs/security) to report a security issue. 2 | -------------------------------------------------------------------------------- /docs/images/favicon/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/android-icon-144x144.png -------------------------------------------------------------------------------- /docs/images/favicon/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/android-icon-192x192.png -------------------------------------------------------------------------------- /docs/images/favicon/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/android-icon-36x36.png -------------------------------------------------------------------------------- /docs/images/favicon/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/android-icon-48x48.png -------------------------------------------------------------------------------- /docs/images/favicon/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/android-icon-72x72.png -------------------------------------------------------------------------------- /docs/images/favicon/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/android-icon-96x96.png -------------------------------------------------------------------------------- /docs/images/favicon/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/apple-icon-114x114.png -------------------------------------------------------------------------------- /docs/images/favicon/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/apple-icon-120x120.png -------------------------------------------------------------------------------- /docs/images/favicon/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/apple-icon-144x144.png -------------------------------------------------------------------------------- /docs/images/favicon/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/apple-icon-152x152.png -------------------------------------------------------------------------------- /docs/images/favicon/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/apple-icon-180x180.png -------------------------------------------------------------------------------- /docs/images/favicon/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/favicon/apple-icon-precomposed.png -------------------------------------------------------------------------------- /docs/images/localize-mongoose-ad-banner-2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/mongoose/HEAD/docs/images/localize-mongoose-ad-banner-2x.jpg -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: mongoosejs 4 | open_collective: mongoose 5 | tidelift: npm/mongoose 6 | -------------------------------------------------------------------------------- /docs/source/home.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const pkg = require('./../../package.json'); 3 | 4 | module.exports = { 5 | package: pkg, 6 | title: 'ODM' 7 | }; 8 | -------------------------------------------------------------------------------- /lib/helpers/firstKey.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function firstKey(obj) { 4 | if (obj == null) { 5 | return null; 6 | } 7 | return Object.keys(obj)[0]; 8 | }; 9 | -------------------------------------------------------------------------------- /lib/options/propertyOptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = Object.freeze({ 4 | enumerable: true, 5 | configurable: true, 6 | writable: true, 7 | value: void 0 8 | }); 9 | -------------------------------------------------------------------------------- /test/types/objectid.test.ts: -------------------------------------------------------------------------------- 1 | import { Types } from 'mongoose'; 2 | 3 | const oid = new Types.ObjectId(); 4 | oid.toHexString(); 5 | oid._id; 6 | 7 | (new Types.ObjectId()).toHexString(); 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | documentation: 3 | - name: 📘 Documentation 4 | url: https://mongoosejs.com/docs/guide.html 5 | about: Mongoose Docs 6 | -------------------------------------------------------------------------------- /docs/source/typescript.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { mapSubDoc } = require('./utils'); 3 | 4 | mapSubDoc('typescript', { 5 | title: 'Mongoose:', 6 | markdown: true 7 | }, module.exports); 8 | -------------------------------------------------------------------------------- /lib/drivers/node-mongodb-native/bulkWriteResult.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BulkWriteResult = require('mongodb/lib/bulk/common').BulkWriteResult; 4 | 5 | module.exports = BulkWriteResult; 6 | -------------------------------------------------------------------------------- /lib/schema/symbols.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.schemaMixedSymbol = Symbol.for('mongoose:schema_mixed'); 4 | 5 | exports.builtInMiddleware = Symbol.for('mongoose:built-in-middleware'); 6 | -------------------------------------------------------------------------------- /lib/types/array/isMongooseArray.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.isMongooseArray = function(mongooseArray) { 4 | return Array.isArray(mongooseArray) && mongooseArray.isMongooseArray; 5 | }; 6 | -------------------------------------------------------------------------------- /lgtm.yml: -------------------------------------------------------------------------------- 1 | path_classifiers: 2 | src: 3 | - lib 4 | types: 5 | - types 6 | test: 7 | - test 8 | docs: 9 | - docs 10 | queries: 11 | - exclude: "*" 12 | - include: lib -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "strictNullChecks": true, 5 | "paths": { 6 | "mongoose" : ["./types/index.d.ts"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /docs/source/tutorials.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { mapSubDoc } = require('./utils'); 3 | 4 | mapSubDoc('tutorials', { 5 | title: 'Mongoose Tutorials:', 6 | acquit: true, 7 | markdown: true 8 | }, module.exports); 9 | -------------------------------------------------------------------------------- /lib/helpers/isPromise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | function isPromise(val) { 3 | return !!val && (typeof val === 'object' || typeof val === 'function') && typeof val.then === 'function'; 4 | } 5 | 6 | module.exports = isPromise; 7 | -------------------------------------------------------------------------------- /lib/helpers/schema/addAutoId.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function addAutoId(schema) { 4 | const _obj = { _id: { auto: true } }; 5 | _obj._id[schema.options.typeKey] = 'ObjectId'; 6 | schema.add(_obj); 7 | }; 8 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # Redirecting 2 | 3 | 4 | 5 | Redirecting to proper API page, please wait 6 | 7 | 8 | -------------------------------------------------------------------------------- /scripts/create-tarball.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { execSync } = require('child_process'); 4 | const { name, version } = require('../package.json'); 5 | 6 | execSync('npm pack'); 7 | execSync(`mv ${name}-${version}.tgz ${name}.tgz`); 8 | -------------------------------------------------------------------------------- /benchmarks/typescript/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "mongoose": "file:../../../mongoose.tgz", 4 | "typescript": "5.8.x" 5 | }, 6 | "scripts": { 7 | "benchmark": "tsc --extendedDiagnostics" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/driver.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * ignore 5 | */ 6 | 7 | let driver = null; 8 | 9 | module.exports.get = function() { 10 | return driver; 11 | }; 12 | 13 | module.exports.set = function(v) { 14 | driver = v; 15 | }; 16 | -------------------------------------------------------------------------------- /lib/helpers/isAsyncFunction.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function isAsyncFunction(v) { 4 | return ( 5 | typeof v === 'function' && 6 | v.constructor && 7 | v.constructor.name === 'AsyncFunction' 8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /lib/types/documentArray/isMongooseDocumentArray.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.isMongooseDocumentArray = function(mongooseDocumentArray) { 4 | return Array.isArray(mongooseDocumentArray) && mongooseDocumentArray.isMongooseDocumentArray; 5 | }; 6 | -------------------------------------------------------------------------------- /.mocharc.yml: -------------------------------------------------------------------------------- 1 | reporter: spec # better to identify failing / slow tests than "dot" 2 | ui: bdd # explicitly setting, even though it is mocha default 3 | require: 4 | - test/mocha-fixtures.js 5 | extension: 6 | - test.js 7 | watch-files: 8 | - test/**/*.js 9 | -------------------------------------------------------------------------------- /lib/helpers/getFunctionName.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const functionNameRE = /^function\s*([^\s(]+)/; 4 | 5 | module.exports = function(fn) { 6 | return ( 7 | fn.name || 8 | (fn.toString().trim().match(functionNameRE) || [])[1] 9 | ); 10 | }; 11 | -------------------------------------------------------------------------------- /lib/plugins/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.saveSubdocs = require('./saveSubdocs'); 4 | exports.sharding = require('./sharding'); 5 | exports.trackTransaction = require('./trackTransaction'); 6 | exports.validateBeforeSave = require('./validateBeforeSave'); 7 | -------------------------------------------------------------------------------- /lib/types/uuid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * UUID type constructor 3 | * 4 | * #### Example: 5 | * 6 | * const id = new mongoose.Types.UUID(); 7 | * 8 | * @constructor UUID 9 | */ 10 | 11 | 'use strict'; 12 | 13 | module.exports = require('mongodb/lib/bson').UUID; 14 | -------------------------------------------------------------------------------- /test/isolated/project-has-error.toJSON.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | Object.defineProperty(Error.prototype, 'toJSON', { 3 | enumerable: false, 4 | configurable: false, 5 | writable: false, 6 | value: () => ({ from: 'Error' }) 7 | }); 8 | 9 | require('../../'); 10 | -------------------------------------------------------------------------------- /lib/helpers/once.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function once(fn) { 4 | let called = false; 5 | return function() { 6 | if (called) { 7 | return; 8 | } 9 | called = true; 10 | return fn.apply(null, arguments); 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /lib/error/mongooseError.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * ignore 5 | */ 6 | 7 | class MongooseError extends Error { } 8 | 9 | Object.defineProperty(MongooseError.prototype, 'name', { 10 | value: 'MongooseError' 11 | }); 12 | 13 | module.exports = MongooseError; 14 | -------------------------------------------------------------------------------- /lib/helpers/populate/skipPopulateValue.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function SkipPopulateValue(val) { 4 | if (!(this instanceof SkipPopulateValue)) { 5 | return new SkipPopulateValue(val); 6 | } 7 | 8 | this.val = val; 9 | return this; 10 | }; 11 | -------------------------------------------------------------------------------- /lib/helpers/query/isOperator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const specialKeys = new Set([ 4 | '$ref', 5 | '$id', 6 | '$db' 7 | ]); 8 | 9 | module.exports = function isOperator(path) { 10 | return ( 11 | path[0] === '$' && 12 | !specialKeys.has(path) 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /lib/types/double.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Double type constructor 3 | * 4 | * #### Example: 5 | * 6 | * const pi = new mongoose.Types.Double(3.1415); 7 | * 8 | * @constructor Double 9 | */ 10 | 11 | 'use strict'; 12 | 13 | module.exports = require('mongodb/lib/bson').Double; 14 | -------------------------------------------------------------------------------- /lib/schema/operators/exists.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const castBoolean = require('../../cast/boolean'); 4 | 5 | /*! 6 | * ignore 7 | */ 8 | 9 | module.exports = function(val) { 10 | const path = this != null ? this.path : null; 11 | return castBoolean(val, path); 12 | }; 13 | -------------------------------------------------------------------------------- /test/types/esm.test.ts: -------------------------------------------------------------------------------- 1 | import type * as mongoose from 'mongoose'; 2 | import mongooseESM from 'mongoose'; 3 | import * as mongooseDefault from 'mongoose'; 4 | import { expectType } from 'tsd'; 5 | 6 | expectType(mongooseESM); 7 | expectType(mongooseDefault); 8 | -------------------------------------------------------------------------------- /test/types/sanitizeFilter.test.ts: -------------------------------------------------------------------------------- 1 | import { QueryFilter, sanitizeFilter } from 'mongoose'; 2 | import { expectType } from 'tsd'; 3 | 4 | const data = { username: 'val', pwd: { $ne: null } }; 5 | type Data = typeof data; 6 | 7 | expectType>(sanitizeFilter(data)); 8 | -------------------------------------------------------------------------------- /lib/modifiedPathsSnapshot.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = class ModifiedPathsSnapshot { 4 | constructor(subdocSnapshot, activePaths, version) { 5 | this.subdocSnapshot = subdocSnapshot; 6 | this.activePaths = activePaths; 7 | this.version = version; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /lib/helpers/isBsonType.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Get the bson type, if it exists 5 | * @api private 6 | */ 7 | 8 | function isBsonType(obj, typename) { 9 | return ( 10 | obj != null && 11 | obj._bsontype === typename 12 | ); 13 | } 14 | 15 | module.exports = isBsonType; 16 | -------------------------------------------------------------------------------- /lib/helpers/projection/isNestedProjection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function isNestedProjection(val) { 4 | if (val == null || typeof val !== 'object') { 5 | return false; 6 | } 7 | return val.$slice == null && val.$elemMatch == null && val.$meta == null && val.$ == null; 8 | }; 9 | -------------------------------------------------------------------------------- /lib/types/decimal128.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Decimal128 type constructor 3 | * 4 | * #### Example: 5 | * 6 | * const id = new mongoose.Types.Decimal128('3.1415'); 7 | * 8 | * @constructor Decimal128 9 | */ 10 | 11 | 'use strict'; 12 | 13 | module.exports = require('mongodb/lib/bson').Decimal128; 14 | -------------------------------------------------------------------------------- /docs/images/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /types/augmentations.d.ts: -------------------------------------------------------------------------------- 1 | // this import is required so that types get merged instead of completely overwritten 2 | import 'bson'; 3 | 4 | declare module 'bson' { 5 | interface ObjectId { 6 | /** Mongoose automatically adds a conveniency "_id" getter on the base ObjectId class */ 7 | _id: this; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /types/callback.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'mongoose' { 2 | type CallbackError = NativeError | null; 3 | 4 | type Callback = (error: CallbackError, result: T) => void; 5 | 6 | type CallbackWithoutResult = (error: CallbackError) => void; 7 | type CallbackWithoutResultAndOptionalError = (error?: CallbackError) => void; 8 | } 9 | -------------------------------------------------------------------------------- /lib/drivers/node-mongodb-native/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Module exports. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | exports.BulkWriteResult = require('./bulkWriteResult'); 8 | exports.Collection = require('./collection'); 9 | exports.Connection = require('./connection'); 10 | exports.ClientEncryption = require('mongodb').ClientEncryption; 11 | -------------------------------------------------------------------------------- /docs/browser.md: -------------------------------------------------------------------------------- 1 | # Mongoose in the Browser 2 | 3 | As of Mongoose 9, [Mongoose's browser build is now in the `@mongoosejs/browser` npm package](https://github.com/mongoosejs/mongoose-browser). 4 | The documentation has been moved to the [`@mongoosejs/browser` README](https://github.com/mongoosejs/mongoose-browser?tab=readme-ov-file#mongoosejsbrowser). 5 | -------------------------------------------------------------------------------- /lib/helpers/model/applyStatics.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Register statics for this model 5 | * @param {Model} model 6 | * @param {Schema} schema 7 | * @api private 8 | */ 9 | module.exports = function applyStatics(model, schema) { 10 | for (const i in schema.statics) { 11 | model[i] = schema.statics[i]; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /lib/helpers/query/trusted.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const trustedSymbol = Symbol('mongoose#trustedSymbol'); 4 | 5 | exports.trustedSymbol = trustedSymbol; 6 | 7 | exports.trusted = function trusted(obj) { 8 | if (obj == null || typeof obj !== 'object') { 9 | return obj; 10 | } 11 | obj[trustedSymbol] = true; 12 | return obj; 13 | }; 14 | -------------------------------------------------------------------------------- /test/subdirs.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | 5 | let files = fs.readdirSync(`${__dirname}/docs`); 6 | 7 | for (const file of files) { 8 | require(`./docs/${file}`); 9 | } 10 | 11 | files = fs.readdirSync(`${__dirname}/helpers`); 12 | 13 | for (const file of files) { 14 | require(`./helpers/${file}`); 15 | } 16 | -------------------------------------------------------------------------------- /lib/options/saveOptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const clone = require('../helpers/clone'); 4 | 5 | class SaveOptions { 6 | constructor(obj) { 7 | if (obj == null) { 8 | return; 9 | } 10 | Object.assign(this, clone(obj)); 11 | } 12 | } 13 | 14 | SaveOptions.prototype.__subdocs = null; 15 | 16 | module.exports = SaveOptions; 17 | -------------------------------------------------------------------------------- /lib/helpers/model/decorateBulkWriteResult.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function decorateBulkWriteResult(resultOrError, validationErrors, results) { 4 | resultOrError.mongoose = resultOrError.mongoose || {}; 5 | resultOrError.mongoose.validationErrors = validationErrors; 6 | resultOrError.mongoose.results = results; 7 | return resultOrError; 8 | }; 9 | -------------------------------------------------------------------------------- /lib/options.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * ignore 5 | */ 6 | 7 | exports.internalToObjectOptions = { 8 | transform: false, 9 | virtuals: false, 10 | getters: false, 11 | _skipDepopulateTopLevel: true, 12 | depopulate: true, 13 | flattenDecimals: false, 14 | useProjection: false, 15 | versionKey: true, 16 | flattenObjectIds: false 17 | }; 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | open-pull-requests-limit: 100 8 | target-branch: master 9 | - package-ecosystem: github-actions 10 | directory: "/" 11 | schedule: 12 | interval: "monthly" 13 | open-pull-requests-limit: 100 14 | target-branch: master 15 | -------------------------------------------------------------------------------- /lib/helpers/projection/isSubpath.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Determines if `path2` is a subpath of or equal to `path1` 5 | * 6 | * @param {string} path1 7 | * @param {string} path2 8 | * @return {Boolean} 9 | * @api private 10 | */ 11 | 12 | module.exports = function isSubpath(path1, path2) { 13 | return path1 === path2 || path2.startsWith(path1 + '.'); 14 | }; 15 | -------------------------------------------------------------------------------- /lib/helpers/isObject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Determines if `arg` is an object. 5 | * 6 | * @param {Object|Array|String|Function|RegExp|any} arg 7 | * @api private 8 | * @return {Boolean} 9 | */ 10 | 11 | module.exports = function(arg) { 12 | return ( 13 | Buffer.isBuffer(arg) || 14 | Object.prototype.toString.call(arg) === '[object Object]' 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /lib/helpers/query/sanitizeProjection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function sanitizeProjection(projection) { 4 | if (projection == null) { 5 | return; 6 | } 7 | 8 | const keys = Object.keys(projection); 9 | for (let i = 0; i < keys.length; ++i) { 10 | if (typeof projection[keys[i]] === 'string') { 11 | projection[keys[i]] = 1; 12 | } 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * Module dependencies. 5 | */ 6 | 7 | const mongodbDriver = require('./drivers/node-mongodb-native'); 8 | 9 | require('./driver').set(mongodbDriver); 10 | 11 | const mongoose = require('./mongoose'); 12 | 13 | mongoose.setDriver(mongodbDriver); 14 | 15 | mongoose.Mongoose.prototype.mongo = require('mongodb'); 16 | 17 | module.exports = mongoose; 18 | -------------------------------------------------------------------------------- /test/esm.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('../'); 4 | const assert = require('assert'); 5 | 6 | describe('esm:', function() { 7 | it('should have default export', function() { 8 | assert.deepEqual(mongoose, mongoose.default); 9 | }); 10 | it('should have mongoose export', function() { 11 | assert.deepEqual(mongoose, mongoose.mongoose); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/types/connect.test.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { connect } from 'mongoose'; 2 | import { expectType } from 'tsd'; 3 | 4 | // Promise 5 | expectType>(connect('mongodb://127.0.0.1:27017/test')); 6 | expectType>(connect('mongodb://127.0.0.1:27017/test', {})); 7 | expectType>(connect('mongodb://127.0.0.1:27017/test', { bufferCommands: true })); 8 | -------------------------------------------------------------------------------- /test/types/generics.test.ts: -------------------------------------------------------------------------------- 1 | import { Model, Document } from 'mongoose'; 2 | 3 | class Repository { 4 | private readonly M: Model; 5 | 6 | constructor(MM: Model) { 7 | this.M = MM; 8 | } 9 | 10 | findById(id: string): Promise { 11 | return this.M.findById(id).orFail().exec(); 12 | } 13 | } 14 | 15 | class Foo { 16 | name: string; 17 | } 18 | 19 | type Test = Repository; 20 | -------------------------------------------------------------------------------- /lib/helpers/indexes/isTextIndex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Returns `true` if the given index options have a `text` option. 5 | */ 6 | 7 | module.exports = function isTextIndex(indexKeys) { 8 | let isTextIndex = false; 9 | for (const key of Object.keys(indexKeys)) { 10 | if (indexKeys[key] === 'text') { 11 | isTextIndex = true; 12 | } 13 | } 14 | 15 | return isTextIndex; 16 | }; 17 | -------------------------------------------------------------------------------- /lib/helpers/getDefaultBulkwriteResult.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function getDefaultBulkwriteResult() { 4 | return { 5 | ok: 1, 6 | nInserted: 0, 7 | nUpserted: 0, 8 | nMatched: 0, 9 | nModified: 0, 10 | nRemoved: 0, 11 | upserted: [], 12 | writeErrors: [], 13 | insertedIds: [], 14 | writeConcernErrors: [] 15 | }; 16 | } 17 | 18 | module.exports = getDefaultBulkwriteResult; 19 | -------------------------------------------------------------------------------- /lib/helpers/schema/cleanPositionalOperators.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * For consistency's sake, we replace positional operator `$` and array filters 5 | * `$[]` and `$[foo]` with `0` when looking up schema paths. 6 | */ 7 | 8 | module.exports = function cleanPositionalOperators(path) { 9 | return path. 10 | replace(/\.\$(\[[^\]]*\])?(?=\.)/g, '.0'). 11 | replace(/\.\$(\[[^\]]*\])?$/g, '.0'); 12 | }; 13 | -------------------------------------------------------------------------------- /lib/helpers/getConstructorName.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * If `val` is an object, returns constructor name, if possible. Otherwise returns undefined. 5 | * @api private 6 | */ 7 | 8 | module.exports = function getConstructorName(val) { 9 | if (val == null) { 10 | return void 0; 11 | } 12 | if (typeof val.constructor !== 'function') { 13 | return void 0; 14 | } 15 | return val.constructor.name; 16 | }; 17 | -------------------------------------------------------------------------------- /lib/helpers/schema/applyBuiltinPlugins.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const builtinPlugins = require('../../plugins'); 4 | 5 | module.exports = function applyBuiltinPlugins(schema) { 6 | for (const plugin of Object.values(builtinPlugins)) { 7 | plugin(schema, { deduplicate: true }); 8 | } 9 | schema.plugins = Object.values(builtinPlugins). 10 | map(fn => ({ fn, opts: { deduplicate: true } })). 11 | concat(schema.plugins); 12 | }; 13 | -------------------------------------------------------------------------------- /lib/helpers/indexes/applySchemaCollation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isTextIndex = require('./isTextIndex'); 4 | 5 | module.exports = function applySchemaCollation(indexKeys, indexOptions, schemaOptions) { 6 | if (isTextIndex(indexKeys)) { 7 | return; 8 | } 9 | 10 | if (Object.hasOwn(schemaOptions, 'collation') && !Object.hasOwn(indexOptions, 'collation')) { 11 | indexOptions.collation = schemaOptions.collation; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /lib/helpers/model/pushNestedArrayPaths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function pushNestedArrayPaths(paths, nestedArray, path) { 4 | if (nestedArray == null) { 5 | return; 6 | } 7 | 8 | for (let i = 0; i < nestedArray.length; ++i) { 9 | if (Array.isArray(nestedArray[i])) { 10 | pushNestedArrayPaths(paths, nestedArray[i], path + '.' + i); 11 | } else { 12 | paths.push(path + '.' + i); 13 | } 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /lib/helpers/query/hasDollarKeys.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * ignore 5 | */ 6 | 7 | module.exports = function hasDollarKeys(obj) { 8 | 9 | if (typeof obj !== 'object' || obj === null) { 10 | return false; 11 | } 12 | 13 | const keys = Object.keys(obj); 14 | const len = keys.length; 15 | 16 | for (let i = 0; i < len; ++i) { 17 | if (keys[i][0] === '$') { 18 | return true; 19 | } 20 | } 21 | 22 | return false; 23 | }; 24 | -------------------------------------------------------------------------------- /lib/helpers/topology/allServersUnknown.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getConstructorName = require('../getConstructorName'); 4 | 5 | module.exports = function allServersUnknown(topologyDescription) { 6 | if (getConstructorName(topologyDescription) !== 'TopologyDescription') { 7 | return false; 8 | } 9 | 10 | const servers = Array.from(topologyDescription.servers.values()); 11 | return servers.length > 0 && servers.every(server => server.type === 'Unknown'); 12 | }; 13 | -------------------------------------------------------------------------------- /lib/helpers/path/parentPaths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dotRE = /\./g; 4 | module.exports = function parentPaths(path) { 5 | if (path.indexOf('.') === -1) { 6 | return [path]; 7 | } 8 | const pieces = path.split(dotRE); 9 | const len = pieces.length; 10 | const ret = new Array(len); 11 | let cur = ''; 12 | for (let i = 0; i < len; ++i) { 13 | cur += (cur.length !== 0) ? '.' + pieces[i] : pieces[i]; 14 | ret[i] = cur; 15 | } 16 | 17 | return ret; 18 | }; 19 | -------------------------------------------------------------------------------- /test/types/mongo.test.ts: -------------------------------------------------------------------------------- 1 | import * as mongoose from 'mongoose'; 2 | import { expectType } from 'tsd'; 3 | import * as bson from 'bson'; 4 | 5 | function gh12537() { 6 | const schema = new mongoose.Schema({ test: String }); 7 | const model = mongoose.model('Test', schema); 8 | 9 | const doc = new model({}); 10 | 11 | const v = new bson.ObjectId('somehex'); 12 | expectType(v._id.toHexString()); 13 | 14 | doc._id = new bson.ObjectId('somehex'); 15 | } 16 | 17 | gh12537(); 18 | -------------------------------------------------------------------------------- /lib/helpers/isPOJO.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function isPOJO(arg) { 4 | if (arg == null || typeof arg !== 'object') { 5 | return false; 6 | } 7 | const proto = Object.getPrototypeOf(arg); 8 | // Prototype may be null if you used `Object.create(null)` 9 | // Checking `proto`'s constructor is safe because `getPrototypeOf()` 10 | // explicitly crosses the boundary from object data to object metadata 11 | return !proto || proto.constructor.name === 'Object'; 12 | }; 13 | -------------------------------------------------------------------------------- /lib/helpers/populate/validateRef.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const MongooseError = require('../../error/mongooseError'); 4 | const util = require('util'); 5 | 6 | module.exports = validateRef; 7 | 8 | function validateRef(ref, path) { 9 | if (typeof ref === 'string') { 10 | return; 11 | } 12 | 13 | if (typeof ref === 'function') { 14 | return; 15 | } 16 | 17 | throw new MongooseError('Invalid ref at path "' + path + '". Got ' + 18 | util.inspect(ref, { depth: 0 })); 19 | } 20 | -------------------------------------------------------------------------------- /lib/helpers/schema/handleTimestampOption.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = handleTimestampOption; 4 | 5 | /*! 6 | * ignore 7 | */ 8 | 9 | function handleTimestampOption(arg, prop) { 10 | if (arg == null) { 11 | return null; 12 | } 13 | 14 | if (typeof arg === 'boolean') { 15 | return prop; 16 | } 17 | if (typeof arg[prop] === 'boolean') { 18 | return arg[prop] ? prop : null; 19 | } 20 | if (!(prop in arg)) { 21 | return prop; 22 | } 23 | return arg[prop]; 24 | } 25 | -------------------------------------------------------------------------------- /lib/helpers/error/combinePathErrors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * ignore 5 | */ 6 | 7 | module.exports = function combinePathErrors(err) { 8 | const keys = Object.keys(err.errors || {}); 9 | const len = keys.length; 10 | const msgs = []; 11 | let key; 12 | 13 | for (let i = 0; i < len; ++i) { 14 | key = keys[i]; 15 | if (err === err.errors[key]) { 16 | continue; 17 | } 18 | msgs.push(key + ': ' + err.errors[key].message); 19 | } 20 | 21 | return msgs.join(', '); 22 | }; 23 | -------------------------------------------------------------------------------- /lib/helpers/omitUndefined.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function omitUndefined(val) { 4 | if (val == null || typeof val !== 'object') { 5 | return val; 6 | } 7 | if (Array.isArray(val)) { 8 | for (let i = val.length - 1; i >= 0; --i) { 9 | if (val[i] === undefined) { 10 | val.splice(i, 1); 11 | } 12 | } 13 | } 14 | for (const key of Object.keys(val)) { 15 | if (val[key] === void 0) { 16 | delete val[key]; 17 | } 18 | } 19 | return val; 20 | }; 21 | -------------------------------------------------------------------------------- /test/helpers/projection.isExclusive.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | require('../common'); // required for side-effect setup (so that the default driver is set-up) 6 | const isExclusive = require('../../lib/helpers/projection/isExclusive'); 7 | 8 | describe('isExclusive', function() { 9 | it('handles $elemMatch (gh-14893)', function() { 10 | assert.strictEqual(isExclusive({ field: { $elemMatch: { test: new Date('2024-06-01') } }, otherProp: 1 }), false); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/helpers/projection.isInclusive.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | require('../common'); // required for side-effect setup (so that the default driver is set-up) 6 | const isInclusive = require('../../lib/helpers/projection/isInclusive'); 7 | 8 | describe('isInclusive', function() { 9 | it('handles $elemMatch (gh-14893)', function() { 10 | assert.strictEqual(isInclusive({ field: { $elemMatch: { test: new Date('2024-06-01') } }, otherProp: 1 }), true); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /scripts/tsc-diagnostics-check.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | 5 | const stdin = fs.readFileSync(0).toString('utf8'); 6 | const maxInstantiations = isNaN(process.argv[2]) ? 400000 : parseInt(process.argv[2], 10); 7 | 8 | console.log(stdin); 9 | 10 | const numInstantiations = parseInt(stdin.match(/Instantiations:\s+(\d+)/)[1], 10); 11 | if (numInstantiations > maxInstantiations) { 12 | throw new Error(`Instantiations ${numInstantiations} > max ${maxInstantiations}`); 13 | } 14 | 15 | process.exit(0); 16 | -------------------------------------------------------------------------------- /lib/helpers/discriminator/areDiscriminatorValuesEqual.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isBsonType = require('../isBsonType'); 4 | 5 | module.exports = function areDiscriminatorValuesEqual(a, b) { 6 | if (typeof a === 'string' && typeof b === 'string') { 7 | return a === b; 8 | } 9 | if (typeof a === 'number' && typeof b === 'number') { 10 | return a === b; 11 | } 12 | if (isBsonType(a, 'ObjectId') && isBsonType(b, 'ObjectId')) { 13 | return a.toString() === b.toString(); 14 | } 15 | return false; 16 | }; 17 | -------------------------------------------------------------------------------- /lib/helpers/schema/handleIdOption.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const addAutoId = require('./addAutoId'); 4 | 5 | module.exports = function handleIdOption(schema, options) { 6 | if (options == null || options._id == null) { 7 | return schema; 8 | } 9 | 10 | schema = schema.clone(); 11 | if (!options._id) { 12 | schema.remove('_id'); 13 | schema.options._id = false; 14 | } else if (!schema.paths['_id']) { 15 | addAutoId(schema); 16 | schema.options._id = true; 17 | } 18 | 19 | return schema; 20 | }; 21 | -------------------------------------------------------------------------------- /benchmarks/typescript/simple/index.ts: -------------------------------------------------------------------------------- 1 | import { Schema, Model, model } from 'mongoose'; 2 | 3 | interface User { 4 | name: string; 5 | email: string; 6 | avatar?: string; 7 | } 8 | 9 | interface UserModelInterface extends Model { 10 | fetchUser(name: string): Promise; 11 | } 12 | 13 | const schema = new Schema({ 14 | name: { type: String, required: true }, 15 | email: { type: String, required: true }, 16 | avatar: String 17 | }); 18 | 19 | const UserModel = model('User', schema); 20 | -------------------------------------------------------------------------------- /lib/helpers/query/handleReadPreferenceAliases.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function handleReadPreferenceAliases(pref) { 4 | switch (pref) { 5 | case 'p': 6 | pref = 'primary'; 7 | break; 8 | case 'pp': 9 | pref = 'primaryPreferred'; 10 | break; 11 | case 's': 12 | pref = 'secondary'; 13 | break; 14 | case 'sp': 15 | pref = 'secondaryPreferred'; 16 | break; 17 | case 'n': 18 | pref = 'nearest'; 19 | break; 20 | } 21 | 22 | return pref; 23 | }; 24 | -------------------------------------------------------------------------------- /lib/helpers/each.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function each(arr, cb, done) { 4 | if (arr.length === 0) { 5 | return done(); 6 | } 7 | 8 | let remaining = arr.length; 9 | let err = null; 10 | for (const v of arr) { 11 | cb(v, function(_err) { 12 | if (err != null) { 13 | return; 14 | } 15 | if (_err != null) { 16 | err = _err; 17 | return done(err); 18 | } 19 | 20 | if (--remaining <= 0) { 21 | return done(); 22 | } 23 | }); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /docs/advanced_schemas.md: -------------------------------------------------------------------------------- 1 | # Advanced Schemas 2 | 3 | ## Creating from ES6 Classes Using `loadClass()` 4 | 5 | Mongoose allows creating schemas from [ES6 classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). 6 | The `loadClass()` function lets you pull in methods, 7 | statics, and virtuals from an ES6 class. A class method maps to a schema 8 | method, a static method maps to a schema static, and getters/setters map 9 | to virtuals. 10 | 11 | ```acquit 12 | [require:Creating from ES6 Classes Using `loadClass\(\)`] 13 | ``` 14 | -------------------------------------------------------------------------------- /lib/helpers/projection/isDefiningProjection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * ignore 5 | */ 6 | 7 | module.exports = function isDefiningProjection(val) { 8 | if (val == null) { 9 | // `undefined` or `null` become exclusive projections 10 | return true; 11 | } 12 | if (typeof val === 'object') { 13 | // Only cases where a value does **not** define whether the whole projection 14 | // is inclusive or exclusive are `$meta` and `$slice`. 15 | return !('$meta' in val) && !('$slice' in val); 16 | } 17 | return true; 18 | }; 19 | -------------------------------------------------------------------------------- /lib/helpers/indexes/isDefaultIdIndex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const get = require('../get'); 4 | 5 | module.exports = function isDefaultIdIndex(index) { 6 | if (Array.isArray(index)) { 7 | // Mongoose syntax 8 | const keys = Object.keys(index[0]); 9 | return keys.length === 1 && keys[0] === '_id' && index[0]._id !== 'hashed'; 10 | } 11 | 12 | if (typeof index !== 'object') { 13 | return false; 14 | } 15 | 16 | const key = get(index, 'key', {}); 17 | return Object.keys(key).length === 1 && Object.hasOwn(key, '_id'); 18 | }; 19 | -------------------------------------------------------------------------------- /lib/schema/operators/type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * ignore 5 | */ 6 | 7 | module.exports = function(val) { 8 | if (Array.isArray(val)) { 9 | if (!val.every(v => typeof v === 'number' || typeof v === 'string')) { 10 | throw new Error('$type array values must be strings or numbers'); 11 | } 12 | return val; 13 | } 14 | 15 | if (typeof val !== 'number' && typeof val !== 'string') { 16 | throw new Error('$type parameter must be number, string, or array of numbers and strings'); 17 | } 18 | 19 | return val; 20 | }; 21 | -------------------------------------------------------------------------------- /lib/helpers/isSimpleValidator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Determines if `arg` is a flat object. 5 | * 6 | * @param {Object|Array|String|Function|RegExp|any} arg 7 | * @api private 8 | * @return {Boolean} 9 | */ 10 | 11 | module.exports = function isSimpleValidator(obj) { 12 | const keys = Object.keys(obj); 13 | let result = true; 14 | for (let i = 0, len = keys.length; i < len; ++i) { 15 | if (typeof obj[keys[i]] === 'object' && obj[keys[i]] !== null) { 16 | result = false; 17 | break; 18 | } 19 | } 20 | 21 | return result; 22 | }; 23 | -------------------------------------------------------------------------------- /docs/incompatible_packages.md: -------------------------------------------------------------------------------- 1 | # Incompatible packages 2 | 3 | The following npm packages are known to be incompatible with Mongoose: 4 | 5 | * [collections](https://www.npmjs.com/package/collections): polluted globals cause issues with the MongoDB Node driver's URL parser. See [GitHub issue 12671](https://github.com/Automattic/mongoose/issues/12671#issuecomment-1374942680). This affects any [package that depends on collections](https://www.npmjs.com/package/collections?activeTab=dependents). 6 | * [excel-export](https://www.npmjs.com/package/excel-export): has dependency on `collections` 7 | -------------------------------------------------------------------------------- /lib/helpers/immediate.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Centralize this so we can more easily work around issues with people 3 | * stubbing out `process.nextTick()` in tests using sinon: 4 | * https://github.com/sinonjs/lolex#automatically-incrementing-mocked-time 5 | * See gh-6074 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const nextTick = typeof process !== 'undefined' && typeof process.nextTick === 'function' ? 11 | process.nextTick.bind(process) : 12 | cb => setTimeout(cb, 0); // Fallback for browser build 13 | 14 | module.exports = function immediate(cb) { 15 | return nextTick(cb); 16 | }; 17 | -------------------------------------------------------------------------------- /docs/images/tidelift.svg: -------------------------------------------------------------------------------- 1 | Asset 1 -------------------------------------------------------------------------------- /test/types/collection.test.ts: -------------------------------------------------------------------------------- 1 | import { Schema, model, Document, connection, Collection } from 'mongoose'; 2 | 3 | const schema = new Schema({ name: { type: 'String' } }); 4 | 5 | interface ITest { 6 | name?: string; 7 | age?: number; 8 | } 9 | 10 | const Test = model('Test', schema); 11 | 12 | Test.collection.collectionName; 13 | Test.collection.findOne({}); 14 | Test.collection.findOneAndDelete({}); 15 | Test.collection.ensureIndex(); 16 | Test.collection.findAndModify(); 17 | Test.collection.getIndexes(); 18 | 19 | const coll: Collection = connection.collection('test'); 20 | -------------------------------------------------------------------------------- /lib/helpers/indexes/isTimeseriesIndex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Returns `true` if the given index matches the schema's `timestamps` options 5 | */ 6 | 7 | module.exports = function isTimeseriesIndex(dbIndex, schemaOptions) { 8 | if (schemaOptions.timeseries == null) { 9 | return false; 10 | } 11 | const { timeField, metaField } = schemaOptions.timeseries; 12 | if (typeof timeField !== 'string' || typeof metaField !== 'string') { 13 | return false; 14 | } 15 | return Object.keys(dbIndex.key).length === 2 && dbIndex.key[timeField] === 1 && dbIndex.key[metaField] === 1; 16 | }; 17 | -------------------------------------------------------------------------------- /lib/types/index.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Module exports. 4 | */ 5 | 6 | 'use strict'; 7 | 8 | exports.Array = require('./array'); 9 | exports.Buffer = require('./buffer'); 10 | 11 | exports.Document = // @deprecate 12 | exports.Embedded = require('./arraySubdocument'); 13 | 14 | exports.DocumentArray = require('./documentArray'); 15 | exports.Double = require('./double'); 16 | exports.Decimal128 = require('./decimal128'); 17 | exports.ObjectId = require('./objectid'); 18 | 19 | exports.Map = require('./map'); 20 | 21 | exports.Subdocument = require('./subdocument'); 22 | 23 | exports.UUID = require('./uuid'); 24 | -------------------------------------------------------------------------------- /lib/helpers/discriminator/checkEmbeddedDiscriminatorKeyProjection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function checkEmbeddedDiscriminatorKeyProjection(userProjection, path, schema, selected, addedPaths) { 4 | const userProjectedInPath = Object.keys(userProjection). 5 | reduce((cur, key) => cur || key.startsWith(path + '.'), false); 6 | const _discriminatorKey = path + '.' + schema.options.discriminatorKey; 7 | if (!userProjectedInPath && 8 | addedPaths.length === 1 && 9 | addedPaths[0] === _discriminatorKey) { 10 | selected.splice(selected.indexOf(_discriminatorKey), 1); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /test/virtualtype.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const start = require('./common'); 5 | 6 | describe('VirtualType', function() { 7 | describe('clone', function() { 8 | it('copies path and options correctly (gh-8587)', function() { 9 | const opts = { ref: 'User', localField: 'userId', foreignField: '_id' }; 10 | const virtual = new start.mongoose.VirtualType(Object.assign({}, opts), 'users'); 11 | 12 | const clone = virtual.clone(); 13 | assert.equal(clone.path, 'users'); 14 | assert.deepEqual(clone.options, opts); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/schemaname.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const start = require('./common'); 5 | const Schema = start.mongoose.Schema; 6 | 7 | describe('schemaname', function() { 8 | it('exists on constructor', function() { 9 | const schema = new Schema({ name: String }); 10 | const name = schema.path('name'); 11 | assert.equal(name.constructor.schemaName, 'String'); 12 | }); 13 | it('exists on instance', function() { 14 | const schema = new Schema({ name: String }); 15 | const name = schema.path('name'); 16 | assert.equal(name.schemaName, 'String'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /lib/error/browserMissingSchema.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Module dependencies. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const MongooseError = require('./mongooseError'); 8 | 9 | /** 10 | * MissingSchema Error constructor. 11 | */ 12 | 13 | class MissingSchemaError extends MongooseError { 14 | 15 | constructor() { 16 | super('Schema hasn\'t been registered for document.\n' 17 | + 'Use mongoose.Document(name, schema)'); 18 | } 19 | } 20 | 21 | Object.defineProperty(MissingSchemaError.prototype, 'name', { 22 | value: 'MongooseError' 23 | }); 24 | 25 | /*! 26 | * exports 27 | */ 28 | 29 | module.exports = MissingSchemaError; 30 | -------------------------------------------------------------------------------- /lib/error/overwriteModel.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Module dependencies. 4 | */ 5 | 6 | 'use strict'; 7 | 8 | const MongooseError = require('./mongooseError'); 9 | 10 | /** 11 | * OverwriteModel Error constructor. 12 | * @param {String} name 13 | * @api private 14 | */ 15 | 16 | class OverwriteModelError extends MongooseError { 17 | 18 | constructor(name) { 19 | super('Cannot overwrite `' + name + '` model once compiled.'); 20 | } 21 | } 22 | 23 | Object.defineProperty(OverwriteModelError.prototype, 'name', { 24 | value: 'OverwriteModelError' 25 | }); 26 | 27 | /*! 28 | * exports 29 | */ 30 | 31 | module.exports = OverwriteModelError; 32 | -------------------------------------------------------------------------------- /lib/error/createCollectionsError.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const MongooseError = require('./mongooseError'); 4 | 5 | /** 6 | * createCollections Error constructor 7 | * 8 | * @param {String} message 9 | * @param {String} errorsMap 10 | * @inherits MongooseError 11 | * @api private 12 | */ 13 | 14 | class CreateCollectionsError extends MongooseError { 15 | constructor(message, errorsMap) { 16 | super(message); 17 | this.errors = errorsMap; 18 | } 19 | } 20 | 21 | Object.defineProperty(CreateCollectionsError.prototype, 'name', { 22 | value: 'CreateCollectionsError' 23 | }); 24 | 25 | module.exports = CreateCollectionsError; 26 | 27 | -------------------------------------------------------------------------------- /lib/helpers/topology/isSSLError.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getConstructorName = require('../getConstructorName'); 4 | 5 | const nonSSLMessage = 'Client network socket disconnected before secure TLS ' + 6 | 'connection was established'; 7 | 8 | module.exports = function isSSLError(topologyDescription) { 9 | if (getConstructorName(topologyDescription) !== 'TopologyDescription') { 10 | return false; 11 | } 12 | 13 | const descriptions = Array.from(topologyDescription.servers.values()); 14 | return descriptions.length > 0 && 15 | descriptions.every(descr => descr.error && descr.error.message.indexOf(nonSSLMessage) !== -1); 16 | }; 17 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | **Summary** 6 | 7 | 8 | 9 | **Examples** 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib/error/syncIndexes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * Module dependencies. 5 | */ 6 | 7 | const MongooseError = require('./mongooseError'); 8 | 9 | /** 10 | * SyncIndexes Error constructor. 11 | * 12 | * @param {String} message 13 | * @param {String} errorsMap 14 | * @inherits MongooseError 15 | * @api private 16 | */ 17 | 18 | class SyncIndexesError extends MongooseError { 19 | constructor(message, errorsMap) { 20 | super(message); 21 | this.errors = errorsMap; 22 | } 23 | } 24 | 25 | Object.defineProperty(SyncIndexesError.prototype, 'name', { 26 | value: 'SyncIndexesError' 27 | }); 28 | 29 | 30 | module.exports = SyncIndexesError; 31 | -------------------------------------------------------------------------------- /lib/schema/operators/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * Module requirements. 5 | */ 6 | 7 | const SchemaNumber = require('../number'); 8 | 9 | /*! 10 | * ignore 11 | */ 12 | 13 | exports.castToNumber = castToNumber; 14 | exports.castArraysOfNumbers = castArraysOfNumbers; 15 | 16 | /*! 17 | * ignore 18 | */ 19 | 20 | function castToNumber(val) { 21 | return SchemaNumber.cast()(val); 22 | } 23 | 24 | function castArraysOfNumbers(arr, self) { 25 | arr.forEach(function(v, i) { 26 | if (Array.isArray(v)) { 27 | castArraysOfNumbers(v, self); 28 | } else { 29 | arr[i] = castToNumber.call(self, v); 30 | } 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /test/helpers/update.applyTimestampsToUpdate.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const applyTimestampsToUpdate = require('../../lib/helpers/update/applyTimestampsToUpdate'); 4 | const assert = require('assert'); 5 | 6 | describe('applyTimestampsToUpdate', function() { 7 | it('handles update pipelines (gh-11151)', function() { 8 | const update = [{ $set: { title: 'mongoose-with-aggregate-set' } }]; 9 | const now = new Date('2016-01-01'); 10 | const res = applyTimestampsToUpdate(now, 'created_at', 'updated_at', update, {}); 11 | 12 | assert.equal(res.length, 2); 13 | assert.equal(res[1].$set.updated_at.valueOf(), now.valueOf()); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /lib/helpers/isMongooseObject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isMongooseArray = require('../types/array/isMongooseArray').isMongooseArray; 4 | /** 5 | * Returns if `v` is a mongoose object that has a `toObject()` method we can use. 6 | * 7 | * This is for compatibility with libs like Date.js which do foolish things to Natives. 8 | * 9 | * @param {Any} v 10 | * @api private 11 | */ 12 | 13 | module.exports = function(v) { 14 | return ( 15 | v != null && ( 16 | isMongooseArray(v) || // Array or Document Array 17 | v.$__ != null || // Document 18 | v.isMongooseBuffer || // Buffer 19 | v.$isMongooseMap // Map 20 | ) 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /lib/connectionState.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Connection states 4 | */ 5 | 6 | 'use strict'; 7 | 8 | const STATES = module.exports = exports = Object.create(null); 9 | 10 | const disconnected = 'disconnected'; 11 | const connected = 'connected'; 12 | const connecting = 'connecting'; 13 | const disconnecting = 'disconnecting'; 14 | const uninitialized = 'uninitialized'; 15 | 16 | STATES[0] = disconnected; 17 | STATES[1] = connected; 18 | STATES[2] = connecting; 19 | STATES[3] = disconnecting; 20 | STATES[99] = uninitialized; 21 | 22 | STATES[disconnected] = 0; 23 | STATES[connected] = 1; 24 | STATES[connecting] = 2; 25 | STATES[disconnecting] = 3; 26 | STATES[uninitialized] = 99; 27 | -------------------------------------------------------------------------------- /lib/helpers/schema/applyReadConcern.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function applyReadConcern(schema, options) { 4 | if (options.readConcern !== undefined) { 5 | return; 6 | } 7 | 8 | // Don't apply default read concern to operations in transactions, 9 | // because you shouldn't set read concern on individual operations 10 | // within a transaction. 11 | // See: https://www.mongodb.com/docs/manual/reference/read-concern/ 12 | if (options && options.session && options.session.transaction) { 13 | return; 14 | } 15 | 16 | const level = schema.options?.readConcern?.level; 17 | if (level != null) { 18 | options.readConcern = { level }; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /test/helpers/update.removeUnusedArrayFilters.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const removeUnusedArrayFilters = require('../../lib/helpers/update/removeUnusedArrayFilters'); 5 | 6 | describe('removeUnusedArrayFilters', function() { 7 | it('respects `$or` (gh-10696)', function() { 8 | const update = { 9 | $set: { 10 | 'requests.$[i].status.aa': 'ON_GOING', 11 | 'requests.$[i].status.bb': 'ON_GOING' 12 | } 13 | }; 14 | const arrayFilters = [{ $or: [{ 'i.no': 1 }] }]; 15 | 16 | const ret = removeUnusedArrayFilters(update, arrayFilters); 17 | assert.deepEqual(ret, [{ $or: [{ 'i.no': 1 }] }]); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /lib/error/parallelSave.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * Module dependencies. 5 | */ 6 | 7 | const MongooseError = require('./mongooseError'); 8 | 9 | 10 | /** 11 | * ParallelSave Error constructor. 12 | * 13 | * @param {Document} doc 14 | * @api private 15 | */ 16 | 17 | class ParallelSaveError extends MongooseError { 18 | 19 | constructor(doc) { 20 | const msg = 'Can\'t save() the same doc multiple times in parallel. Document: '; 21 | super(msg + doc._doc._id); 22 | } 23 | } 24 | 25 | Object.defineProperty(ParallelSaveError.prototype, 'name', { 26 | value: 'ParallelSaveError' 27 | }); 28 | 29 | /*! 30 | * exports 31 | */ 32 | 33 | module.exports = ParallelSaveError; 34 | -------------------------------------------------------------------------------- /docs/css/carbonads.css: -------------------------------------------------------------------------------- 1 | /* CPM ads */ 2 | 3 | .carbonad{ 4 | margin-top:0!important; 5 | margin-bottom:-3rem!important 6 | } 7 | 8 | #carbonads { 9 | position:fixed; 10 | right: 0px; 11 | bottom: 0px; 12 | display:block; 13 | width:160px; 14 | padding:15px 15px 15px 150px; 15 | overflow:hidden; 16 | font-size:13px; 17 | line-height:1.4; 18 | text-align:left; 19 | background-color: #fafafa; 20 | } 21 | 22 | @media (max-width: 1170px) { 23 | #carbonads { 24 | display: none !important; 25 | } 26 | } 27 | 28 | #carbonads a{color:#333;text-decoration:none} 29 | 30 | .carbon-img{float:left;margin-left:-145px} 31 | 32 | .carbon-poweredby{display:block;color:#777!important} 33 | -------------------------------------------------------------------------------- /lib/helpers/createJSONSchemaTypeDefinition.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Handles creating `{ type: 'object' }` vs `{ bsonType: 'object' }` vs `{ bsonType: ['object', 'null'] }` 5 | * 6 | * @param {String} type 7 | * @param {String} bsonType 8 | * @param {Boolean} useBsonType 9 | * @param {Boolean} isRequired 10 | */ 11 | 12 | module.exports = function createJSONSchemaTypeArray(type, bsonType, useBsonType, isRequired) { 13 | if (useBsonType) { 14 | if (isRequired) { 15 | return { bsonType }; 16 | } 17 | return { bsonType: [bsonType, 'null'] }; 18 | } else { 19 | if (isRequired) { 20 | return { type }; 21 | } 22 | return { type: [type, 'null'] }; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /test/helpers/projection.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const isSubpath = require('../../lib/helpers/projection/isSubpath'); 5 | 6 | describe('isSubpath', function() { 7 | it('handles single-part paths', function(done) { 8 | assert.equal(isSubpath('a', 'a'), true); 9 | assert.equal(isSubpath('a', 'b'), false); 10 | 11 | done(); 12 | }); 13 | 14 | it('handles multi-part paths', function(done) { 15 | assert.equal(isSubpath('a.b.c', 'a.b.c'), true); 16 | assert.equal(isSubpath('a.c.b', 'a.b.c'), false); 17 | assert.equal(isSubpath('a', 'a.b.c'), true); 18 | assert.equal(isSubpath('a.b.c', 'a'), false); 19 | 20 | done(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/types/methods.test.ts: -------------------------------------------------------------------------------- 1 | import { Schema, Model, connection } from 'mongoose'; 2 | 3 | interface ITest { 4 | foo: string; 5 | } 6 | 7 | interface ITestMethods { 8 | getAnswer(): number; 9 | } 10 | 11 | type ITestModel = Model; 12 | 13 | const TestSchema = new Schema({ 14 | foo: { type: String, required: true } 15 | }); 16 | 17 | TestSchema.methods.getAnswer = function(): number { 18 | console.log(this.foo.trim()); 19 | return 42; 20 | }; 21 | 22 | const Test = connection.model('Test', TestSchema); 23 | 24 | Test.create({ foo: 'test' }); 25 | 26 | const doc = new Test({ foo: 'test' }); 27 | 28 | Math.floor(doc.getAnswer()); 29 | -------------------------------------------------------------------------------- /docs/includes/native.pug: -------------------------------------------------------------------------------- 1 | append style 2 | script(type="text/javascript" src="//m.servedby-buysellads.com/monetization.custom.js") 3 | link(rel="stylesheet", href=`${versions.versionedPath}/docs/css/inlinecpc.css`) 4 | 5 | #native-direct 6 | script. 7 | (function() { 8 | if (typeof _bsa !== 'undefined' && _bsa) { 9 | _bsa.init('custom', 'CK7DT53U', 'placement:mongoosejscom', 10 | { 11 | target: '#native-direct', 12 | template: ` 13 | 16 | 17 | ` 18 | } 19 | ); 20 | } 21 | })(); 22 | -------------------------------------------------------------------------------- /docs/sponsors.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | div 5 | h1 Mongoose Sponsors 6 | 7 | each sponsor in sponsors 8 | div(style='position: relative; margin-top: 1em;') 9 | div(style='position: absolute; width: 100px; height: 100px') 10 | a(href=sponsor.url) 11 | img(src=sponsor.logo) 12 | div(style='padding-left: 110px; min-height: 100px') 13 | h3(style='margin-top: 0.2em; margin-bottom: 0.25em;') 14 | a(href=sponsor.url) #{sponsor.name} 15 | div !{sponsor.description} 16 | 17 | 18 | h2 Add Your Own 19 | div. 20 | Want to feature your app on this page? 21 | Sponsor Mongoose on GitHub! 22 | -------------------------------------------------------------------------------- /lib/error/missingSchema.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Module dependencies. 4 | */ 5 | 6 | 'use strict'; 7 | 8 | const MongooseError = require('./mongooseError'); 9 | 10 | /** 11 | * MissingSchema Error constructor. 12 | * @param {String} name 13 | * @api private 14 | */ 15 | 16 | class MissingSchemaError extends MongooseError { 17 | 18 | constructor(name) { 19 | const msg = 'Schema hasn\'t been registered for model "' + name + '".\n' 20 | + 'Use mongoose.model(name, schema)'; 21 | super(msg); 22 | } 23 | } 24 | 25 | Object.defineProperty(MissingSchemaError.prototype, 'name', { 26 | value: 'MissingSchemaError' 27 | }); 28 | 29 | /*! 30 | * exports 31 | */ 32 | 33 | module.exports = MissingSchemaError; 34 | -------------------------------------------------------------------------------- /lib/error/parallelValidate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * Module dependencies. 5 | */ 6 | 7 | const MongooseError = require('./mongooseError'); 8 | 9 | 10 | /** 11 | * ParallelValidate Error constructor. 12 | * 13 | * @param {Document} doc 14 | * @api private 15 | */ 16 | 17 | class ParallelValidateError extends MongooseError { 18 | 19 | constructor(doc) { 20 | const msg = 'Can\'t validate() the same doc multiple times in parallel. Document: '; 21 | super(msg + doc._doc._id); 22 | } 23 | } 24 | 25 | Object.defineProperty(ParallelValidateError.prototype, 'name', { 26 | value: 'ParallelValidateError' 27 | }); 28 | 29 | /*! 30 | * exports 31 | */ 32 | 33 | module.exports = ParallelValidateError; 34 | -------------------------------------------------------------------------------- /lib/helpers/projection/isPathSelectedInclusive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * ignore 5 | */ 6 | 7 | module.exports = function isPathSelectedInclusive(fields, path) { 8 | const chunks = path.split('.'); 9 | let cur = ''; 10 | let j; 11 | let keys; 12 | let numKeys; 13 | for (let i = 0; i < chunks.length; ++i) { 14 | cur += cur.length ? '.' : '' + chunks[i]; 15 | if (fields[cur]) { 16 | keys = Object.keys(fields); 17 | numKeys = keys.length; 18 | for (j = 0; j < numKeys; ++j) { 19 | if (keys[i].indexOf(cur + '.') === 0 && keys[i].indexOf(path) !== 0) { 20 | continue; 21 | } 22 | } 23 | return true; 24 | } 25 | } 26 | 27 | return false; 28 | }; 29 | -------------------------------------------------------------------------------- /lib/cast/objectid.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isBsonType = require('../helpers/isBsonType'); 4 | const ObjectId = require('../types/objectid'); 5 | 6 | module.exports = function castObjectId(value) { 7 | if (value == null) { 8 | return value; 9 | } 10 | 11 | if (isBsonType(value, 'ObjectId')) { 12 | return value; 13 | } 14 | 15 | if (value._id) { 16 | if (isBsonType(value._id, 'ObjectId')) { 17 | return value._id; 18 | } 19 | if (value._id.toString instanceof Function) { 20 | return new ObjectId(value._id.toString()); 21 | } 22 | } 23 | 24 | if (value.toString instanceof Function) { 25 | return new ObjectId(value.toString()); 26 | } 27 | 28 | return new ObjectId(value); 29 | }; 30 | -------------------------------------------------------------------------------- /test/types/helpers.test.ts: -------------------------------------------------------------------------------- 1 | import * as mongoose from 'mongoose'; 2 | import { ObjectId } from 'mongodb'; 3 | import { expectType, expectError } from 'tsd'; 4 | 5 | expectType(mongoose.isObjectIdOrHexString(new ObjectId())); 6 | expectType(mongoose.isObjectIdOrHexString(new mongoose.Types.ObjectId())); 7 | expectType(mongoose.isObjectIdOrHexString('string')); 8 | expectType(mongoose.isObjectIdOrHexString(new Error())); 9 | 10 | expectType(mongoose.isValidObjectId(new ObjectId())); 11 | expectType(mongoose.isValidObjectId(new mongoose.Types.ObjectId())); 12 | expectType(mongoose.isValidObjectId('12345')); 13 | expectError(mongoose.isValidObjectId()); 14 | 15 | expectType(mongoose.now()); 16 | -------------------------------------------------------------------------------- /tools/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Server = require('mongodb-topology-manager').Server; 4 | const mongodb = require('mongodb'); 5 | 6 | run().catch(error => { 7 | console.error(error); 8 | process.exit(-1); 9 | }); 10 | 11 | async function run() { 12 | // Create new instance 13 | const server = new Server('mongod', { 14 | auth: null, 15 | dbpath: '/data/db/27017' 16 | }); 17 | 18 | // Purge the directory 19 | await server.purge(); 20 | 21 | // Start process 22 | await server.start(); 23 | 24 | const db = await mongodb.MongoClient.connect('mongodb://127.0.0.1:27017/admin'); 25 | 26 | await db.addUser('passwordIsTaco', 'taco', { 27 | roles: ['dbOwner'] 28 | }); 29 | 30 | console.log('done'); 31 | } 32 | -------------------------------------------------------------------------------- /lib/error/invalidSchemaOption.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Module dependencies. 4 | */ 5 | 6 | 'use strict'; 7 | 8 | const MongooseError = require('./mongooseError'); 9 | 10 | /** 11 | * InvalidSchemaOption Error constructor. 12 | * @param {String} name 13 | * @api private 14 | */ 15 | 16 | class InvalidSchemaOptionError extends MongooseError { 17 | 18 | constructor(name, option) { 19 | const msg = `Cannot create use schema for property "${name}" because the schema has the ${option} option enabled.`; 20 | super(msg); 21 | } 22 | } 23 | 24 | Object.defineProperty(InvalidSchemaOptionError.prototype, 'name', { 25 | value: 'InvalidSchemaOptionError' 26 | }); 27 | 28 | /*! 29 | * exports 30 | */ 31 | 32 | module.exports = InvalidSchemaOptionError; 33 | -------------------------------------------------------------------------------- /lib/helpers/path/setDottedPath.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const specialProperties = require('../specialProperties'); 4 | 5 | 6 | module.exports = function setDottedPath(obj, path, val) { 7 | if (path.indexOf('.') === -1) { 8 | if (specialProperties.has(path)) { 9 | return; 10 | } 11 | 12 | obj[path] = val; 13 | return; 14 | } 15 | const parts = path.split('.'); 16 | 17 | const last = parts.pop(); 18 | let cur = obj; 19 | for (const part of parts) { 20 | if (specialProperties.has(part)) { 21 | continue; 22 | } 23 | if (cur[part] == null) { 24 | cur[part] = {}; 25 | } 26 | 27 | cur = cur[part]; 28 | } 29 | 30 | if (!specialProperties.has(last)) { 31 | cur[last] = val; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /lib/helpers/schema/idGetter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * ignore 5 | */ 6 | 7 | module.exports = function addIdGetter(schema) { 8 | // ensure the documents receive an id getter unless disabled 9 | const autoIdGetter = !schema.paths['id'] && 10 | schema.paths['_id'] && 11 | schema.options.id; 12 | if (!autoIdGetter) { 13 | return schema; 14 | } 15 | if (schema.aliases && schema.aliases.id) { 16 | return schema; 17 | } 18 | schema.virtual('id').get(idGetter); 19 | 20 | return schema; 21 | }; 22 | 23 | /** 24 | * Returns this documents _id cast to a string. 25 | * @api private 26 | */ 27 | 28 | function idGetter() { 29 | if (this._id != null) { 30 | return this._id.toString(); 31 | } 32 | 33 | return null; 34 | } 35 | -------------------------------------------------------------------------------- /lib/helpers/indexes/decorateDiscriminatorIndexOptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function decorateDiscriminatorIndexOptions(schema, indexOptions) { 4 | // If the model is a discriminator and has an index, add a 5 | // partialFilterExpression by default so the index will only apply 6 | // to that discriminator. 7 | const discriminatorName = schema.discriminatorMapping && schema.discriminatorMapping.value; 8 | if (discriminatorName && !('sparse' in indexOptions)) { 9 | const discriminatorKey = schema.options.discriminatorKey; 10 | indexOptions.partialFilterExpression = indexOptions.partialFilterExpression || {}; 11 | indexOptions.partialFilterExpression[discriminatorKey] = discriminatorName; 12 | } 13 | return indexOptions; 14 | }; 15 | -------------------------------------------------------------------------------- /docs/search.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | append style 4 | style. 5 | .title { 6 | font-size: 1.5em; 7 | } 8 | 9 | ul { 10 | list-style-type: none; 11 | padding: 0px; 12 | } 13 | 14 | .url a { 15 | font-size: 0.75em; 16 | color: #777; 17 | margin-top: 0.5em; 18 | } 19 | 20 | p { 21 | margin-top: 0.5em; 22 | } 23 | 24 | block content 25 | div 26 | h1 Search 27 | 28 | include includes/native 29 | 30 | div.search 31 | input#search-input(type="text", placeholder="Search") 32 | button#search-button 33 | img(src=`${versions.versionedPath}/docs/images/search.svg`) 34 | div#results 35 | 36 | script(type="text/javascript" src=`${versions.versionedPath}/docs/js/search.js`) 37 | -------------------------------------------------------------------------------- /lib/helpers/schema/getKeysInSchemaOrder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const get = require('../get'); 4 | 5 | module.exports = function getKeysInSchemaOrder(schema, val, path) { 6 | const schemaKeys = path != null ? Object.keys(get(schema.tree, path, {})) : Object.keys(schema.tree); 7 | const valKeys = new Set(Object.keys(val)); 8 | 9 | let keys; 10 | if (valKeys.size > 1) { 11 | keys = new Set(); 12 | for (const key of schemaKeys) { 13 | if (valKeys.has(key)) { 14 | keys.add(key); 15 | } 16 | } 17 | for (const key of valKeys) { 18 | if (!keys.has(key)) { 19 | keys.add(key); 20 | } 21 | } 22 | keys = Array.from(keys); 23 | } else { 24 | keys = Array.from(valKeys); 25 | } 26 | 27 | return keys; 28 | }; 29 | -------------------------------------------------------------------------------- /lib/error/objectExpected.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Module dependencies. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const MongooseError = require('./mongooseError'); 8 | 9 | /** 10 | * Strict mode error constructor 11 | * 12 | * @param {string} type 13 | * @param {string} value 14 | * @api private 15 | */ 16 | 17 | class ObjectExpectedError extends MongooseError { 18 | 19 | constructor(path, val) { 20 | const typeDescription = Array.isArray(val) ? 'array' : 'primitive value'; 21 | super('Tried to set nested object field `' + path + 22 | `\` to ${typeDescription} \`` + val + '`'); 23 | this.path = path; 24 | } 25 | } 26 | 27 | Object.defineProperty(ObjectExpectedError.prototype, 'name', { 28 | value: 'ObjectExpectedError' 29 | }); 30 | 31 | module.exports = ObjectExpectedError; 32 | -------------------------------------------------------------------------------- /lib/error/strictPopulate.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Module dependencies. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const MongooseError = require('./mongooseError'); 8 | 9 | /** 10 | * Strict mode error constructor 11 | * 12 | * @param {String} path 13 | * @param {String} [msg] 14 | * @inherits MongooseError 15 | * @api private 16 | */ 17 | 18 | class StrictPopulateError extends MongooseError { 19 | 20 | constructor(path, msg) { 21 | msg = msg || 'Cannot populate path `' + path + '` because it is not in your schema. ' + 'Set the `strictPopulate` option to false to override.'; 22 | super(msg); 23 | this.path = path; 24 | } 25 | } 26 | 27 | Object.defineProperty(StrictPopulateError.prototype, 'name', { 28 | value: 'StrictPopulateError' 29 | }); 30 | 31 | module.exports = StrictPopulateError; 32 | -------------------------------------------------------------------------------- /test/helpers/getFunctionName.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const getFunctionName = require('../../lib/helpers/getFunctionName'); 5 | 6 | describe('getFunctionName', () => { 7 | it('return fn.name', () => { 8 | assert.equal(getFunctionName({ name: 'fnName' }), 'fnName'); 9 | }); 10 | 11 | it('return function name', () => { 12 | assert.equal(getFunctionName(function fnName() {}), 'fnName'); 13 | }); 14 | 15 | it('return function functionName', () => { 16 | assert.equal(getFunctionName(function functionName() {}), 'functionName'); 17 | }); 18 | 19 | it('return undefined for arrow function', () => { 20 | // I can't say it's expected behavior, but is how it's behave. 21 | assert.equal(getFunctionName(() => []), undefined); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues" 2 | on: 3 | schedule: 4 | - cron: "0 0 * * *" 5 | workflow_dispatch: 6 | permissions: 7 | issues: write 8 | 9 | jobs: 10 | stale: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: stale 14 | uses: actions/stale@v10 15 | with: 16 | repo-token: ${{ secrets.GITHUB_TOKEN }} 17 | stale-issue-message: 'This issue is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 5 days' 18 | close-issue-message: "This issue was closed because it has been inactive for 19 days and has been marked as stale." 19 | days-before-stale: 14 20 | days-before-close: 5 21 | any-of-labels: can't reproduce,help,needs clarification 22 | -------------------------------------------------------------------------------- /lib/helpers/discriminator/getDiscriminatorByValue.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const areDiscriminatorValuesEqual = require('./areDiscriminatorValuesEqual'); 4 | 5 | /** 6 | * returns discriminator by discriminatorMapping.value 7 | * 8 | * @param {Object} discriminators 9 | * @param {string} value 10 | * @api private 11 | */ 12 | 13 | module.exports = function getDiscriminatorByValue(discriminators, value) { 14 | if (discriminators == null) { 15 | return null; 16 | } 17 | for (const name of Object.keys(discriminators)) { 18 | const it = discriminators[name]; 19 | if ( 20 | it.schema && 21 | it.schema.discriminatorMapping && 22 | areDiscriminatorValuesEqual(it.schema.discriminatorMapping.value, value) 23 | ) { 24 | return it; 25 | } 26 | } 27 | return null; 28 | }; 29 | -------------------------------------------------------------------------------- /lib/schema/operators/bitwise.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Module requirements. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const CastError = require('../../error/cast'); 8 | 9 | /*! 10 | * ignore 11 | */ 12 | 13 | function handleBitwiseOperator(val) { 14 | const _this = this; 15 | if (Array.isArray(val)) { 16 | return val.map(function(v) { 17 | return _castNumber(_this.path, v); 18 | }); 19 | } else if (Buffer.isBuffer(val)) { 20 | return val; 21 | } 22 | // Assume trying to cast to number 23 | return _castNumber(_this.path, val); 24 | } 25 | 26 | /*! 27 | * ignore 28 | */ 29 | 30 | function _castNumber(path, num) { 31 | const v = Number(num); 32 | if (isNaN(v)) { 33 | throw new CastError('number', num, path); 34 | } 35 | return v; 36 | } 37 | 38 | module.exports = handleBitwiseOperator; 39 | -------------------------------------------------------------------------------- /test/helpers/common.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const modifiedPaths = require('../../lib/helpers/common').modifiedPaths; 5 | 6 | describe('modifiedPaths, bad update value which has circular reference field', () => { 7 | it('update value can be null', function() { 8 | modifiedPaths(null, 'path', null); 9 | }); 10 | 11 | it('values with obvious error on circular reference', function() { 12 | const objA = {}; 13 | objA.a = objA; 14 | 15 | assert.throws(() => modifiedPaths(objA, 'path', null), /circular reference/); 16 | }); 17 | 18 | it('values with multiple references which are not circular should succeed', function() { 19 | const objA = {}; 20 | const objB = { a: objA, b: objA }; 21 | 22 | modifiedPaths(objB, 'path', null); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /lib/error/strict.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Module dependencies. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const MongooseError = require('./mongooseError'); 8 | 9 | /** 10 | * Strict mode error constructor 11 | * 12 | * @param {String} path 13 | * @param {String} [msg] 14 | * @param {Boolean} [immutable] 15 | * @inherits MongooseError 16 | * @api private 17 | */ 18 | 19 | 20 | class StrictModeError extends MongooseError { 21 | 22 | constructor(path, msg, immutable) { 23 | msg = msg || 'Field `' + path + '` is not in schema and strict ' + 24 | 'mode is set to throw.'; 25 | super(msg); 26 | this.isImmutableError = !!immutable; 27 | this.path = path; 28 | } 29 | } 30 | 31 | Object.defineProperty(StrictModeError.prototype, 'name', { 32 | value: 'StrictModeError' 33 | }); 34 | 35 | module.exports = StrictModeError; 36 | -------------------------------------------------------------------------------- /lib/helpers/minimize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { isPOJO } = require('../utils'); 4 | 5 | module.exports = minimize; 6 | 7 | /** 8 | * Minimizes an object, removing undefined values and empty objects 9 | * 10 | * @param {Object} object to minimize 11 | * @return {Object|undefined} 12 | * @api private 13 | */ 14 | 15 | function minimize(obj) { 16 | const keys = Object.keys(obj); 17 | let i = keys.length; 18 | let hasKeys; 19 | let key; 20 | let val; 21 | 22 | while (i--) { 23 | key = keys[i]; 24 | val = obj[key]; 25 | 26 | if (isPOJO(val)) { 27 | obj[key] = minimize(val); 28 | } 29 | 30 | if (undefined === obj[key]) { 31 | delete obj[key]; 32 | continue; 33 | } 34 | 35 | hasKeys = true; 36 | } 37 | 38 | return hasKeys 39 | ? obj 40 | : undefined; 41 | } 42 | -------------------------------------------------------------------------------- /lib/helpers/parallelLimit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = parallelLimit; 4 | 5 | /*! 6 | * ignore 7 | */ 8 | 9 | async function parallelLimit(params, fn, limit) { 10 | if (limit <= 0) { 11 | throw new Error('Limit must be positive'); 12 | } 13 | 14 | if (params.length === 0) { 15 | return []; 16 | } 17 | 18 | const results = []; 19 | const executing = new Set(); 20 | 21 | for (let index = 0; index < params.length; index++) { 22 | const param = params[index]; 23 | const p = fn(param, index); 24 | results.push(p); 25 | 26 | executing.add(p); 27 | 28 | const clean = () => executing.delete(p); 29 | p.then(clean).catch(clean); 30 | 31 | if (executing.size >= limit) { 32 | await Promise.race(executing); 33 | } 34 | } 35 | 36 | return Promise.all(results); 37 | } 38 | -------------------------------------------------------------------------------- /test/helpers/applyWriteConcern.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const applyWriteConcern = require('../../lib/helpers/schema/applyWriteConcern'); 5 | const start = require('../common'); 6 | const mongoose = start.mongoose; 7 | 8 | describe('applyWriteConcern', function() { 9 | let db; 10 | before(function() { 11 | db = start(); 12 | }); 13 | after(async function() { 14 | await db.close(); 15 | }); 16 | it('should not overwrite user specified writeConcern options (gh-13592)', async function() { 17 | const options = { writeConcern: { w: 'majority' } }; 18 | const testSchema = new mongoose.Schema({ name: String }, { writeConcern: { w: 0 } }); 19 | applyWriteConcern(testSchema, options); 20 | assert.deepStrictEqual({ writeConcern: { w: 'majority' } }, options); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.yml: -------------------------------------------------------------------------------- 1 | name: ❔ Other 2 | description: Open an issue that is not feature or bug related 3 | 4 | body: 5 | - type: checkboxes 6 | id: prerequisites 7 | attributes: 8 | label: Prerequisites 9 | options: 10 | - label: I have written a descriptive issue title 11 | required: true 12 | - label: | 13 | I have searched existing issues to ensure the issue has not already been raised 14 | required: true 15 | 16 | - type: textarea 17 | id: text 18 | attributes: 19 | label: Issue 20 | description: | 21 | Give as much detail as you can to help us understand. 22 | Make sure you place example code inside a [code (```) block](https://docs.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks) to avoid linking unrelated issues. -------------------------------------------------------------------------------- /lib/helpers/projection/parseProjection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Convert a string or array into a projection object, retaining all 5 | * `-` and `+` paths. 6 | */ 7 | 8 | module.exports = function parseProjection(v, retainMinusPaths) { 9 | const type = typeof v; 10 | 11 | if (type === 'string') { 12 | v = v.split(/\s+/); 13 | } 14 | if (!Array.isArray(v) && Object.prototype.toString.call(v) !== '[object Arguments]') { 15 | return v; 16 | } 17 | 18 | const len = v.length; 19 | const ret = {}; 20 | for (let i = 0; i < len; ++i) { 21 | let field = v[i]; 22 | if (!field) { 23 | continue; 24 | } 25 | const include = '-' == field[0] ? 0 : 1; 26 | if (!retainMinusPaths && include === 0) { 27 | field = field.substring(1); 28 | } 29 | ret[field] = include; 30 | } 31 | 32 | return ret; 33 | }; 34 | -------------------------------------------------------------------------------- /test/types/Errors.test.ts: -------------------------------------------------------------------------------- 1 | import * as mongoose from 'mongoose'; 2 | 3 | /** 4 | * gh-issue #11367 5 | * 6 | * @see https://github.com/Automattic/mongoose/issues/11367 7 | */ 8 | function handleValidationError(err: mongoose.Error.ValidationError): Array { 9 | const errorTypes = Object.keys(err.errors).map((field: string) => err.errors[field].kind); 10 | return errorTypes; 11 | } 12 | 13 | /** 14 | * gh-issue #11838 15 | * 16 | * @see https://github.com/Automattic/mongoose/issues/11838 17 | */ 18 | function gh11838() { 19 | const Model = mongoose.model('Test', new mongoose.Schema({ answer: Number })); 20 | const doc = new Model({ answer: 'not a number' }); 21 | const err = doc.validateSync(); 22 | 23 | err instanceof mongoose.Error; 24 | err instanceof mongoose.MongooseError; 25 | err instanceof mongoose.Error.ValidationError; 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npmjs 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: # 👈 allows you to trigger manually from the Actions tab 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read 13 | id-token: write 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v6.0.0 17 | 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v6 20 | with: 21 | node-version: '24.x' 22 | registry-url: 'https://registry.npmjs.org' 23 | 24 | - name: Install dependencies 25 | run: npm install 26 | 27 | - name: Publish with provenance 28 | run: npm publish --provenance --access public 29 | env: 30 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 31 | -------------------------------------------------------------------------------- /scripts/static.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | const app = express(); 5 | 6 | const website = require('./website'); 7 | 8 | const port = process.env.PORT 9 | ? parseInt(process.env.PORT, 10) 10 | : 8089; 11 | 12 | async function main() { 13 | await Promise.all([ 14 | website.copyAllRequiredFiles(), 15 | website.pugifyAllFiles() 16 | ]); 17 | // start watching for file changes and re-compile them, so that they can be served directly 18 | website.startWatch(); 19 | 20 | app.use('/', express.static(website.cwd)); 21 | 22 | app.listen(port, () => { 23 | let urlPath = '/'; 24 | 25 | if (website.versionObj.versionedDeploy) { 26 | urlPath = website.versionObj.versionedPath; 27 | } 28 | 29 | console.log(`now listening on http://127.0.0.1:${port}${urlPath}`); 30 | }); 31 | } 32 | 33 | main(); 34 | -------------------------------------------------------------------------------- /lib/error/objectParameter.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Module dependencies. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const MongooseError = require('./mongooseError'); 8 | 9 | /** 10 | * Constructor for errors that happen when a parameter that's expected to be 11 | * an object isn't an object 12 | * 13 | * @param {Any} value 14 | * @param {String} paramName 15 | * @param {String} fnName 16 | * @api private 17 | */ 18 | 19 | class ObjectParameterError extends MongooseError { 20 | constructor(value, paramName, fnName) { 21 | super('Parameter "' + paramName + '" to ' + fnName + 22 | '() must be an object, got "' + (value == null ? value : value.toString()) + '" (type ' + typeof value + ')'); 23 | } 24 | } 25 | 26 | 27 | Object.defineProperty(ObjectParameterError.prototype, 'name', { 28 | value: 'ObjectParameterError' 29 | }); 30 | 31 | module.exports = ObjectParameterError; 32 | -------------------------------------------------------------------------------- /test/helpers/schema.cleanPositionalOperators.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const cleanPositionalOperators = require('../../lib/helpers/schema/cleanPositionalOperators'); 5 | 6 | describe('cleanPositionalOperators', function() { 7 | it('replaces trailing array filter', function() { 8 | assert.equal(cleanPositionalOperators('questions.$[q]'), 'questions.0'); 9 | }); 10 | 11 | it('replaces trailing $', function() { 12 | assert.equal(cleanPositionalOperators('questions.$'), 'questions.0'); 13 | }); 14 | 15 | it('replaces interior array filters', function() { 16 | assert.equal(cleanPositionalOperators('questions.$[q].$[r].test'), 'questions.0.0.test'); 17 | }); 18 | 19 | it('replaces interior elemMatch', function() { 20 | assert.equal(cleanPositionalOperators('questions.$.$.test'), 'questions.0.0.test'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/types/check-types-filename.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const uppercaseRE = /[A-Z]/g; 5 | 6 | const checkFolder = (folder) => { 7 | const folderContent = fs.readdirSync(folder); 8 | 9 | for (const entry of folderContent) { 10 | 11 | if (fs.lstatSync(folder + '/' + entry).isDirectory()) { 12 | checkFolder(folder + '/' + entry); 13 | } else { 14 | if (entry === '.gitignore' || entry.endsWith('.d.ts')) { 15 | if (uppercaseRE.test(entry)) { 16 | console.error('File ' + entry + ' contains uppercase characters.\n'); 17 | process.exit(1); 18 | } 19 | continue; 20 | } else { 21 | console.error('File ' + entry + ' does not have a valid extension, must be .d.ts or .gitignore.\n'); 22 | process.exit(1); 23 | } 24 | } 25 | } 26 | }; 27 | 28 | checkFolder('./types'); 29 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | **.swp 3 | *.sw* 4 | *.orig 5 | .DS_Store 6 | node_modules/ 7 | benchmarks/ 8 | docs/ 9 | test/ 10 | Makefile 11 | CNAME 12 | index.html 13 | index.pug 14 | bin/ 15 | tools/31* 16 | *.key 17 | data/ 18 | 19 | *.txt 20 | *.png 21 | 22 | examples/ 23 | .github 24 | .vscode 25 | .eslintignore 26 | CONTRIBUTING.md 27 | History.md 28 | CHANGELOG.md 29 | format_deps.js 30 | release-items.md 31 | static.js 32 | website.js 33 | 34 | .config* 35 | 36 | migrating_to_6.md 37 | migrating_to_5.md 38 | renovate.json 39 | .travis.yml 40 | webpack.config.js 41 | webpack.base.config.js 42 | 43 | .nyc-output 44 | 45 | *.tgz 46 | 47 | notes.md 48 | list.out 49 | 50 | # config files 51 | lgtm.yml 52 | .mocharc.yml 53 | .eslintrc.js 54 | .markdownlint-cli2.cjs 55 | tsconfig.json 56 | 57 | # scripts 58 | scripts/ 59 | tools/ 60 | 61 | *.0x 62 | valnotes.md 63 | 64 | coverage 65 | .nyc_output 66 | -------------------------------------------------------------------------------- /docs/js/redirect-old-api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | window.addEventListener('DOMContentLoaded', () => { 3 | const anchor = window.location.hash; 4 | 5 | // in case there is not anchor 6 | if (!anchor) { 7 | return redirectToBase(); 8 | } 9 | 10 | const firstName = anchor.split('_')[0]; 11 | 12 | // in case there is no split 13 | if (!firstName) { 14 | return redirectToBase(); 15 | } 16 | 17 | const sliced = firstName.slice(1).toLowerCase(); // ignore first character, which will always be "#" 18 | 19 | // in case everything after "#" is empty 20 | if (!sliced) { 21 | return redirectToBase(); 22 | } 23 | 24 | window.location.replace('./api/' + sliced + '.html' + anchor); 25 | }, { once: true }); 26 | 27 | // helper function to redirect in case no other redirect can be found 28 | function redirectToBase() { 29 | window.location.replace('./api/mongoose.html'); 30 | } 31 | -------------------------------------------------------------------------------- /lib/helpers/discriminator/getSchemaDiscriminatorByValue.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const areDiscriminatorValuesEqual = require('./areDiscriminatorValuesEqual'); 4 | 5 | /** 6 | * returns discriminator by discriminatorMapping.value 7 | * 8 | * @param {Schema} schema 9 | * @param {string} value 10 | * @api private 11 | */ 12 | 13 | module.exports = function getSchemaDiscriminatorByValue(schema, value) { 14 | if (schema == null || schema.discriminators == null) { 15 | return null; 16 | } 17 | for (const key of Object.keys(schema.discriminators)) { 18 | const discriminatorSchema = schema.discriminators[key]; 19 | if (discriminatorSchema.discriminatorMapping == null) { 20 | continue; 21 | } 22 | if (areDiscriminatorValuesEqual(discriminatorSchema.discriminatorMapping.value, value)) { 23 | return discriminatorSchema; 24 | } 25 | } 26 | return null; 27 | }; 28 | -------------------------------------------------------------------------------- /lib/options/schemaUnionOptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const SchemaTypeOptions = require('./schemaTypeOptions'); 4 | 5 | /** 6 | * The options defined on a Union schematype. 7 | * 8 | * @api public 9 | * @inherits SchemaTypeOptions 10 | * @constructor SchemaUnionOptions 11 | */ 12 | 13 | class SchemaUnionOptions extends SchemaTypeOptions {} 14 | 15 | const opts = require('./propertyOptions'); 16 | 17 | /** 18 | * If set, specifies the types that this union can take. Mongoose will cast 19 | * the value to one of the given types. 20 | * 21 | * If not set, Mongoose will not cast the value to any specific type. 22 | * 23 | * @api public 24 | * @property of 25 | * @memberOf SchemaUnionOptions 26 | * @type {Function|Function[]|string|string[]} 27 | * @instance 28 | */ 29 | 30 | Object.defineProperty(SchemaUnionOptions.prototype, 'of', opts); 31 | 32 | module.exports = SchemaUnionOptions; 33 | -------------------------------------------------------------------------------- /lib/helpers/topology/isAtlas.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getConstructorName = require('../getConstructorName'); 4 | 5 | /** 6 | * @typedef { import('mongodb').TopologyDescription } TopologyDescription 7 | */ 8 | 9 | /** 10 | * Checks if topologyDescription contains servers connected to an atlas instance 11 | * 12 | * @param {TopologyDescription} topologyDescription 13 | * @returns {boolean} 14 | */ 15 | module.exports = function isAtlas(topologyDescription) { 16 | if (getConstructorName(topologyDescription) !== 'TopologyDescription') { 17 | return false; 18 | } 19 | 20 | if (topologyDescription.servers.size === 0) { 21 | return false; 22 | } 23 | 24 | for (const server of topologyDescription.servers.values()) { 25 | if (server.host.endsWith('.mongodb.net') === false || server.port !== 27017) { 26 | return false; 27 | } 28 | } 29 | 30 | return true; 31 | }; 32 | -------------------------------------------------------------------------------- /docs/customschematypes.md: -------------------------------------------------------------------------------- 1 | # Custom Schema Types 2 | 3 | ## Creating a Basic Custom Schema Type 4 | 5 | *New in Mongoose 4.4.0:* Mongoose supports custom types. Before you 6 | reach for a custom type, however, know that a custom type is overkill 7 | for most use cases. You can do most basic tasks with 8 | [custom getters/setters](http://mongoosejs.com/docs/2.7.x/docs/getters-setters.html), 9 | [virtuals](http://mongoosejs.com/docs/guide.html#virtuals), and 10 | [single embedded docs](http://mongoosejs.com/docs/subdocs.html#single-embedded). 11 | 12 | Let's take a look at an example of a basic schema type: a 1-byte integer. 13 | To create a new schema type, you need to inherit from `mongoose.SchemaType` 14 | and add the corresponding property to `mongoose.Schema.Types`. The one 15 | method you need to implement is the `cast()` method. 16 | 17 | ```acquit 18 | [require:Creating a Basic Custom Schema Type] 19 | ``` 20 | -------------------------------------------------------------------------------- /lib/helpers/populate/removeDeselectedForeignField.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const get = require('../get'); 4 | const mpath = require('mpath'); 5 | const parseProjection = require('../projection/parseProjection'); 6 | 7 | /*! 8 | * ignore 9 | */ 10 | 11 | module.exports = function removeDeselectedForeignField(foreignFields, options, docs) { 12 | const projection = parseProjection(get(options, 'select', null), true) || 13 | parseProjection(get(options, 'options.select', null), true); 14 | 15 | if (projection == null) { 16 | return; 17 | } 18 | for (const foreignField of foreignFields) { 19 | if (!Object.hasOwn(projection, '-' + foreignField)) { 20 | continue; 21 | } 22 | 23 | for (const val of docs) { 24 | if (val.$__ != null) { 25 | mpath.unset(foreignField, val._doc); 26 | } else { 27 | mpath.unset(foreignField, val); 28 | } 29 | } 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /tools/repl.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | run().catch(error => { 4 | console.error(error); 5 | process.exit(-1); 6 | }); 7 | 8 | async function run() { 9 | const ReplSet = require('mongodb-memory-server').MongoMemoryReplSet; 10 | 11 | // Create new instance 12 | const replSet = new ReplSet({ 13 | binary: { 14 | version: process.argv[2] 15 | }, 16 | instanceOpts: [ 17 | // Set the expiry job in MongoDB to run every second 18 | { 19 | port: 27017, 20 | args: ['--setParameter', 'ttlMonitorSleepSecs=1'] 21 | } 22 | ], 23 | dbName: 'mongoose_test', 24 | replSet: { 25 | name: 'rs0', 26 | count: 2, 27 | storageEngine: 'wiredTiger' 28 | } 29 | }); 30 | 31 | await replSet.start(); 32 | await replSet.waitUntilRunning(); 33 | console.log('MongoDB-ReplicaSet is now running.'); 34 | console.log(replSet.getUri('mongoose_test')); 35 | } 36 | -------------------------------------------------------------------------------- /docs/images/favicon/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /lib/helpers/arrayDepth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = arrayDepth; 4 | 5 | function arrayDepth(arr) { 6 | if (!Array.isArray(arr)) { 7 | return { min: 0, max: 0, containsNonArrayItem: true }; 8 | } 9 | if (arr.length === 0) { 10 | return { min: 1, max: 1, containsNonArrayItem: false }; 11 | } 12 | if (arr.length === 1 && !Array.isArray(arr[0])) { 13 | return { min: 1, max: 1, containsNonArrayItem: false }; 14 | } 15 | 16 | const res = arrayDepth(arr[0]); 17 | 18 | for (let i = 1; i < arr.length; ++i) { 19 | const _res = arrayDepth(arr[i]); 20 | if (_res.min < res.min) { 21 | res.min = _res.min; 22 | } 23 | if (_res.max > res.max) { 24 | res.max = _res.max; 25 | } 26 | res.containsNonArrayItem = res.containsNonArrayItem || _res.containsNonArrayItem; 27 | } 28 | 29 | res.min = res.min + 1; 30 | res.max = res.max + 1; 31 | 32 | return res; 33 | } 34 | -------------------------------------------------------------------------------- /lib/helpers/update/updatedPathsByArrayFilter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const modifiedPaths = require('./modifiedPaths'); 4 | 5 | module.exports = function updatedPathsByArrayFilter(update) { 6 | if (update == null) { 7 | return {}; 8 | } 9 | const updatedPaths = modifiedPaths(update); 10 | 11 | return Object.keys(updatedPaths).reduce((cur, path) => { 12 | const matches = path.match(/\$\[[^\]]+\]/g); 13 | if (matches == null) { 14 | return cur; 15 | } 16 | for (const match of matches) { 17 | const firstMatch = path.indexOf(match); 18 | if (firstMatch !== path.lastIndexOf(match)) { 19 | throw new Error(`Path '${path}' contains the same array filter multiple times`); 20 | } 21 | cur[match.substring(2, match.length - 1)] = path. 22 | substring(0, firstMatch - 1). 23 | replace(/\$\[[^\]]+\]/g, '0'); 24 | } 25 | return cur; 26 | }, {}); 27 | }; 28 | -------------------------------------------------------------------------------- /lib/helpers/populate/lookupLocalFields.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function lookupLocalFields(cur, path, val) { 4 | if (cur == null) { 5 | return cur; 6 | } 7 | 8 | if (cur._doc != null) { 9 | cur = cur._doc; 10 | } 11 | 12 | if (arguments.length >= 3) { 13 | if (typeof cur !== 'object') { 14 | return void 0; 15 | } 16 | if (val === void 0) { 17 | return void 0; 18 | } 19 | if (cur instanceof Map) { 20 | cur.set(path, val); 21 | } else { 22 | cur[path] = val; 23 | } 24 | return val; 25 | } 26 | 27 | 28 | // Support populating paths under maps using `map.$*.subpath` 29 | if (path === '$*') { 30 | return cur instanceof Map ? 31 | Array.from(cur.values()) : 32 | Object.keys(cur).map(key => cur[key]); 33 | } 34 | 35 | if (cur instanceof Map) { 36 | return cur.get(path); 37 | } 38 | 39 | return cur[path]; 40 | }; 41 | -------------------------------------------------------------------------------- /docs/tutorials/custom-casting.md: -------------------------------------------------------------------------------- 1 | # Custom Casting 2 | 3 | [Mongoose 5.4.0](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md#540--2018-12-14) introduced [several ways to configure SchemaTypes globally](http://thecodebarbarian.com/whats-new-in-mongoose-54-global-schematype-configuration). 4 | One of these new features is the [`SchemaType.cast()` function](../api/schematype.html#schematype_SchemaType-cast), which enables you to override Mongoose's built-in casting. 5 | 6 | For example, by default Mongoose will throw an error if you attempt to cast 7 | a string that contains a Japanese numeral to a number. 8 | 9 | ```acquit 10 | [require:custom casting.*casting error] 11 | ``` 12 | 13 | You can overwrite the default casting function for numbers to allow converting 14 | the string that contains the Japanese numeral "2" to a number as shown below. 15 | 16 | ```acquit 17 | [require:custom casting.*casting override] 18 | ``` 19 | -------------------------------------------------------------------------------- /lib/options/schemaBufferOptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const SchemaTypeOptions = require('./schemaTypeOptions'); 4 | 5 | /** 6 | * The options defined on a Buffer schematype. 7 | * 8 | * #### Example: 9 | * 10 | * const schema = new Schema({ bitmap: Buffer }); 11 | * schema.path('bitmap').options; // SchemaBufferOptions instance 12 | * 13 | * @api public 14 | * @inherits SchemaTypeOptions 15 | * @constructor SchemaBufferOptions 16 | */ 17 | 18 | class SchemaBufferOptions extends SchemaTypeOptions {} 19 | 20 | const opts = require('./propertyOptions'); 21 | 22 | /** 23 | * Set the default subtype for this buffer. 24 | * 25 | * @api public 26 | * @property subtype 27 | * @memberOf SchemaBufferOptions 28 | * @type {Number} 29 | * @instance 30 | */ 31 | 32 | Object.defineProperty(SchemaBufferOptions.prototype, 'subtype', opts); 33 | 34 | /*! 35 | * ignore 36 | */ 37 | 38 | module.exports = SchemaBufferOptions; 39 | -------------------------------------------------------------------------------- /lib/error/version.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * Module dependencies. 5 | */ 6 | 7 | const MongooseError = require('./mongooseError'); 8 | 9 | /** 10 | * Version Error constructor. 11 | * 12 | * @param {Document} doc 13 | * @param {Number} currentVersion 14 | * @param {Array} modifiedPaths 15 | * @api private 16 | */ 17 | 18 | class VersionError extends MongooseError { 19 | 20 | constructor(doc, currentVersion, modifiedPaths) { 21 | const modifiedPathsStr = modifiedPaths.join(', '); 22 | super('No matching document found for id "' + doc._doc._id + 23 | '" version ' + currentVersion + ' modifiedPaths "' + modifiedPathsStr + '"'); 24 | this.version = currentVersion; 25 | this.modifiedPaths = modifiedPaths; 26 | } 27 | } 28 | 29 | 30 | Object.defineProperty(VersionError.prototype, 'name', { 31 | value: 'VersionError' 32 | }); 33 | 34 | /*! 35 | * exports 36 | */ 37 | 38 | module.exports = VersionError; 39 | -------------------------------------------------------------------------------- /lib/validOptions.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Valid mongoose options 4 | */ 5 | 6 | 'use strict'; 7 | 8 | const VALID_OPTIONS = Object.freeze([ 9 | 'allowDiskUse', 10 | 'applyPluginsToChildSchemas', 11 | 'applyPluginsToDiscriminators', 12 | 'autoCreate', 13 | 'autoIndex', 14 | 'autoSearchIndex', 15 | 'bufferCommands', 16 | 'bufferTimeoutMS', 17 | 'cloneSchemas', 18 | 'createInitialConnection', 19 | 'debug', 20 | 'forceRepopulate', 21 | 'id', 22 | 'maxTimeMS', 23 | 'objectIdGetter', 24 | 'overwriteModels', 25 | 'returnOriginal', 26 | 'runValidators', 27 | 'sanitizeFilter', 28 | 'sanitizeProjection', 29 | 'selectPopulatedPaths', 30 | 'setDefaultsOnInsert', 31 | 'strict', 32 | 'strictPopulate', 33 | 'strictQuery', 34 | 'timestamps.createdAt.immutable', 35 | 'toJSON', 36 | 'toObject', 37 | 'transactionAsyncLocalStorage', 38 | 'translateAliases', 39 | 'updatePipeline' 40 | ]); 41 | 42 | module.exports = VALID_OPTIONS; 43 | -------------------------------------------------------------------------------- /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | name: TypeScript Benchmark 2 | on: 3 | pull_request: 4 | paths: 5 | - ".github/workflows/benchmark.yml" 6 | - "package.json" 7 | - "types/**" 8 | - "benchmarks/typescript/**" 9 | push: 10 | branches: 11 | - master 12 | paths: 13 | - ".github/workflows/benchmark.yml" 14 | - "package.json" 15 | - "types/**" 16 | - "benchmarks/typescript/**" 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | typescript: 22 | runs-on: ubuntu-22.04 23 | name: Benchmark TypeScript Types 24 | steps: 25 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 26 | - name: Setup node 27 | uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 28 | with: 29 | node-version: 22 30 | 31 | - run: npm install 32 | 33 | - run: node ./scripts/create-tarball.js 34 | 35 | - run: npm run ts-benchmark 36 | -------------------------------------------------------------------------------- /lib/helpers/query/applyGlobalOption.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const utils = require('../../utils'); 4 | 5 | function applyGlobalMaxTimeMS(options, connectionOptions, baseOptions) { 6 | applyGlobalOption(options, connectionOptions, baseOptions, 'maxTimeMS'); 7 | } 8 | 9 | function applyGlobalDiskUse(options, connectionOptions, baseOptions) { 10 | applyGlobalOption(options, connectionOptions, baseOptions, 'allowDiskUse'); 11 | } 12 | 13 | module.exports = { 14 | applyGlobalMaxTimeMS, 15 | applyGlobalDiskUse 16 | }; 17 | 18 | 19 | function applyGlobalOption(options, connectionOptions, baseOptions, optionName) { 20 | if (utils.hasUserDefinedProperty(options, optionName)) { 21 | return; 22 | } 23 | 24 | if (utils.hasUserDefinedProperty(connectionOptions, optionName)) { 25 | options[optionName] = connectionOptions[optionName]; 26 | } else if (utils.hasUserDefinedProperty(baseOptions, optionName)) { 27 | options[optionName] = baseOptions[optionName]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/helpers/update/decorateUpdateWithVersionKey.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Decorate the update with a version key, if necessary 5 | * @api private 6 | */ 7 | 8 | module.exports = function decorateUpdateWithVersionKey(update, options, versionKey) { 9 | if (!versionKey || !(options && options.upsert || false)) { 10 | return; 11 | } 12 | 13 | if (options.overwrite) { 14 | if (!hasKey(update, versionKey)) { 15 | update[versionKey] = 0; 16 | } 17 | } else if ( 18 | !hasKey(update, versionKey) && 19 | !hasKey(update?.$set, versionKey) && 20 | !hasKey(update?.$inc, versionKey) && 21 | !hasKey(update?.$setOnInsert, versionKey) 22 | ) { 23 | if (!update.$setOnInsert) { 24 | update.$setOnInsert = {}; 25 | } 26 | update.$setOnInsert[versionKey] = 0; 27 | } 28 | }; 29 | 30 | function hasKey(obj, key) { 31 | if (obj == null || typeof obj !== 'object') { 32 | return false; 33 | } 34 | return Object.hasOwn(obj, key); 35 | } 36 | -------------------------------------------------------------------------------- /test/deno.mjs: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'node:module'; 2 | import process from 'node:process'; 3 | import { resolve } from 'node:path'; 4 | import { fileURLToPath } from 'node:url'; 5 | 6 | import { spawn } from 'node:child_process'; 7 | 8 | Error.stackTraceLimit = 100; 9 | 10 | const require = createRequire(import.meta.url); 11 | 12 | const fixtures = require('./mocha-fixtures.js'); 13 | 14 | await fixtures.mochaGlobalSetup(); 15 | 16 | const child_args = [ 17 | ...Deno.args, 18 | resolve(fileURLToPath(import.meta.url), '../deno_mocha.mjs') 19 | ]; 20 | 21 | const child = spawn(process.execPath, child_args, { stdio: 'inherit' }); 22 | 23 | child.on('exit', (code, signal) => { 24 | signal ? doExit(-100) : doExit(code); 25 | }); 26 | 27 | Deno.addSignalListener('SIGINT', () => { 28 | console.log('SIGINT'); 29 | child.kill('SIGINT'); 30 | doExit(-2); 31 | }); 32 | 33 | async function doExit(code) { 34 | await fixtures.mochaGlobalTeardown(); 35 | Deno.exit(code); 36 | } 37 | -------------------------------------------------------------------------------- /lib/types/objectid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ObjectId type constructor 3 | * 4 | * #### Example: 5 | * 6 | * const id = new mongoose.Types.ObjectId; 7 | * 8 | * @constructor ObjectId 9 | */ 10 | 11 | 'use strict'; 12 | 13 | const ObjectId = require('mongodb/lib/bson').ObjectId; 14 | const objectIdSymbol = require('../helpers/symbols').objectIdSymbol; 15 | 16 | /** 17 | * Getter for convenience with populate, see gh-6115 18 | * @api private 19 | */ 20 | 21 | Object.defineProperty(ObjectId.prototype, '_id', { 22 | enumerable: false, 23 | configurable: true, 24 | get: function() { 25 | return this; 26 | } 27 | }); 28 | 29 | /*! 30 | * Convenience `valueOf()` to allow comparing ObjectIds using double equals re: gh-7299 31 | */ 32 | 33 | if (!Object.hasOwn(ObjectId.prototype, 'valueOf')) { 34 | ObjectId.prototype.valueOf = function objectIdValueOf() { 35 | return this.toString(); 36 | }; 37 | } 38 | 39 | ObjectId.prototype[objectIdSymbol] = true; 40 | 41 | module.exports = ObjectId; 42 | -------------------------------------------------------------------------------- /test/helpers/update.applyTimestampsToChildren.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Schema = require('../../lib/schema'); 4 | const applyTimestampsToChildren = require('../../lib/helpers/update/applyTimestampsToChildren'); 5 | const assert = require('assert'); 6 | 7 | describe('applyTimestampsToChildren', function() { 8 | it('applies timestamps to nested subdocs within a $push (gh-11775)', function() { 9 | const update = { 10 | $push: { 11 | l1: { 12 | l2: { 13 | prop: 'test' 14 | } 15 | } 16 | } 17 | }; 18 | const now = new Date('2016-01-01'); 19 | const schema = new Schema({ 20 | l1: [new Schema({ l2: new Schema({ prop: String }, { timestamps: true }) }, { timestamps: true })] 21 | }); 22 | applyTimestampsToChildren(now, update, schema); 23 | 24 | assert.equal(update.$push.l1.l2.createdAt.valueOf(), now.valueOf()); 25 | assert.equal(update.$push.l1.l2.updatedAt.valueOf(), now.valueOf()); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /lib/cast/boolean.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const CastError = require('../error/cast'); 4 | 5 | /** 6 | * Given a value, cast it to a boolean, or throw a `CastError` if the value 7 | * cannot be casted. `null` and `undefined` are considered valid. 8 | * 9 | * @param {Any} value 10 | * @param {String} [path] optional the path to set on the CastError 11 | * @return {Boolean|null|undefined} 12 | * @throws {CastError} if `value` is not one of the allowed values 13 | * @api private 14 | */ 15 | 16 | module.exports = function castBoolean(value, path) { 17 | if (module.exports.convertToTrue.has(value)) { 18 | return true; 19 | } 20 | if (module.exports.convertToFalse.has(value)) { 21 | return false; 22 | } 23 | 24 | if (value == null) { 25 | return value; 26 | } 27 | 28 | throw new CastError('boolean', value, path); 29 | }; 30 | 31 | module.exports.convertToTrue = new Set([true, 'true', 1, '1', 'yes']); 32 | module.exports.convertToFalse = new Set([false, 'false', 0, '0', 'no']); 33 | -------------------------------------------------------------------------------- /lib/helpers/projection/isPathExcluded.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isDefiningProjection = require('./isDefiningProjection'); 4 | 5 | /** 6 | * Determines if `path` is excluded by `projection` 7 | * 8 | * @param {Object} projection 9 | * @param {String} path 10 | * @return {Boolean} 11 | * @api private 12 | */ 13 | 14 | module.exports = function isPathExcluded(projection, path) { 15 | if (projection == null) { 16 | return false; 17 | } 18 | 19 | if (path === '_id') { 20 | return projection._id === 0; 21 | } 22 | 23 | const paths = Object.keys(projection); 24 | let type = null; 25 | 26 | for (const _path of paths) { 27 | if (isDefiningProjection(projection[_path])) { 28 | type = projection[path] === 1 ? 'inclusive' : 'exclusive'; 29 | break; 30 | } 31 | } 32 | 33 | if (type === 'inclusive') { 34 | return projection[path] !== 1; 35 | } 36 | if (type === 'exclusive') { 37 | return projection[path] === 0; 38 | } 39 | return false; 40 | }; 41 | -------------------------------------------------------------------------------- /lib/options/populateOptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const clone = require('../helpers/clone'); 4 | 5 | class PopulateOptions { 6 | constructor(obj) { 7 | this._docs = {}; 8 | this._childDocs = []; 9 | 10 | if (obj == null) { 11 | return; 12 | } 13 | obj = clone(obj); 14 | Object.assign(this, obj); 15 | if (typeof obj.subPopulate === 'object') { 16 | this.populate = obj.subPopulate; 17 | } 18 | 19 | 20 | if (obj.perDocumentLimit != null && obj.limit != null) { 21 | throw new Error('Can not use `limit` and `perDocumentLimit` at the same time. Path: `' + obj.path + '`.'); 22 | } 23 | } 24 | } 25 | 26 | /** 27 | * The connection used to look up models by name. If not specified, Mongoose 28 | * will default to using the connection associated with the model in 29 | * `PopulateOptions#model`. 30 | * 31 | * @memberOf PopulateOptions 32 | * @property {Connection} connection 33 | * @api public 34 | */ 35 | 36 | module.exports = PopulateOptions; 37 | -------------------------------------------------------------------------------- /lib/schema/index.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Module exports. 4 | */ 5 | 6 | 'use strict'; 7 | 8 | exports.Array = require('./array'); 9 | exports.BigInt = require('./bigint'); 10 | exports.Boolean = require('./boolean'); 11 | exports.Buffer = require('./buffer'); 12 | exports.Date = require('./date'); 13 | exports.Decimal128 = exports.Decimal = require('./decimal128'); 14 | exports.DocumentArray = require('./documentArray'); 15 | exports.Double = require('./double'); 16 | exports.Int32 = require('./int32'); 17 | exports.Map = require('./map'); 18 | exports.Mixed = require('./mixed'); 19 | exports.Number = require('./number'); 20 | exports.ObjectId = require('./objectId'); 21 | exports.String = require('./string'); 22 | exports.Subdocument = require('./subdocument'); 23 | exports.UUID = require('./uuid'); 24 | exports.Union = require('./union'); 25 | 26 | // alias 27 | 28 | exports.Oid = exports.ObjectId; 29 | exports.Object = exports.Mixed; 30 | exports.Bool = exports.Boolean; 31 | exports.ObjectID = exports.ObjectId; 32 | -------------------------------------------------------------------------------- /lib/cast/int32.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isBsonType = require('../helpers/isBsonType'); 4 | const assert = require('assert'); 5 | 6 | /** 7 | * Given a value, cast it to a Int32, or throw an `Error` if the value 8 | * cannot be casted. `null` and `undefined` are considered valid. 9 | * 10 | * @param {Any} value 11 | * @return {Number} 12 | * @throws {Error} if `value` does not represent an integer, or is outside the bounds of an 32-bit integer. 13 | * @api private 14 | */ 15 | 16 | module.exports = function castInt32(val) { 17 | if (val == null) { 18 | return val; 19 | } 20 | if (val === '') { 21 | return null; 22 | } 23 | 24 | const coercedVal = isBsonType(val, 'Long') ? val.toNumber() : Number(val); 25 | 26 | const INT32_MAX = 0x7FFFFFFF; 27 | const INT32_MIN = -0x80000000; 28 | 29 | if (coercedVal === (coercedVal | 0) && 30 | coercedVal >= INT32_MIN && 31 | coercedVal <= INT32_MAX 32 | ) { 33 | return coercedVal; 34 | } 35 | assert.ok(false); 36 | }; 37 | -------------------------------------------------------------------------------- /lib/helpers/update/modifiedPaths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _modifiedPaths = require('../common').modifiedPaths; 4 | 5 | /** 6 | * Given an update document with potential update operators (`$set`, etc.) 7 | * returns an object whose keys are the directly modified paths. 8 | * 9 | * If there are any top-level keys that don't start with `$`, we assume those 10 | * will get wrapped in a `$set`. The Mongoose Query is responsible for wrapping 11 | * top-level keys in `$set`. 12 | * 13 | * @param {Object} update 14 | * @return {Object} modified 15 | */ 16 | 17 | module.exports = function modifiedPaths(update) { 18 | const keys = Object.keys(update); 19 | const res = {}; 20 | 21 | const withoutDollarKeys = {}; 22 | for (const key of keys) { 23 | if (key.startsWith('$')) { 24 | _modifiedPaths(update[key], '', res); 25 | continue; 26 | } 27 | withoutDollarKeys[key] = update[key]; 28 | } 29 | 30 | _modifiedPaths(withoutDollarKeys, '', res); 31 | 32 | return res; 33 | }; 34 | -------------------------------------------------------------------------------- /types/virtuals.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'mongoose' { 2 | type VirtualPathFunctions = { 3 | get?: TVirtualPathFN; 4 | set?: TVirtualPathFN; 5 | options?: VirtualTypeOptions, DocType>; 6 | }; 7 | 8 | type TVirtualPathFN = 9 | >(this: Document & DocType, value: PathType, virtual: VirtualType, doc: Document & DocType) => TReturn; 10 | 11 | type SchemaOptionsVirtualsPropertyType, TInstanceMethods = {}> = { 12 | [K in keyof VirtualPaths]: VirtualPathFunctions extends true ? DocType : any, VirtualPaths[K], TInstanceMethods> 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /docs/js/navbar-search.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | (function() { 3 | const versionFromUrl = window.location.pathname.match(/^\/docs\/(\d+\.x)/); 4 | const version = versionFromUrl ? versionFromUrl[1] : null; 5 | 6 | const searchPrefix = versionFromUrl ? '/docs/' + version + '/docs/' : '/docs/'; 7 | 8 | // dont use nav-bar search for search site, let the search site handle that 9 | if (/\/search(:?\.html)?$/i.test(window.location.pathname)) { 10 | return; 11 | } 12 | 13 | document.getElementById('search-button-nav').onclick = function() { 14 | const q = document.getElementById('search-input-nav').value; 15 | window.location.href = searchPrefix + 'search.html?q=' + encodeURIComponent(q); 16 | }; 17 | 18 | document.getElementById('search-input-nav').onkeyup = function(ev) { 19 | if (ev.keyCode === 13) { 20 | const q = document.getElementById('search-input-nav').value; 21 | window.location.href = searchPrefix + 'search.html?q=' + encodeURIComponent(q); 22 | } 23 | }; 24 | })(); 25 | -------------------------------------------------------------------------------- /test/schema.string.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const start = require('./common'); 4 | 5 | const assert = require('assert'); 6 | 7 | const mongoose = start.mongoose; 8 | const Schema = mongoose.Schema; 9 | 10 | describe('SchemaString', function() { 11 | let M; 12 | 13 | before(function() { 14 | const schema = new Schema({ x: { type: String, match: /abc/g } }); 15 | mongoose.deleteModel(/Test/); 16 | M = mongoose.model('Test', schema); 17 | }); 18 | 19 | it('works when RegExp has global flag set (gh-9287)', function() { 20 | const doc = new M({ x: 'abc' }); 21 | assert.ifError(doc.validateSync()); 22 | assert.ifError(doc.validateSync()); 23 | }); 24 | 25 | it('regex validator works with validate() (gh-15380)', async function() { 26 | const schema = new Schema({ x: { type: String, validate: /abc/g } }); 27 | mongoose.deleteModel(/Test/); 28 | M = mongoose.model('Test', schema); 29 | 30 | const doc = new M({ x: 'abc' }); 31 | await doc.validate(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /lib/helpers/document/handleSpreadDoc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const utils = require('../../utils'); 4 | 5 | const keysToSkip = new Set(['__index', '__parentArray', '_doc']); 6 | 7 | /** 8 | * Using spread operator on a Mongoose document gives you a 9 | * POJO that has a tendency to cause infinite recursion. So 10 | * we use this function on `set()` to prevent that. 11 | */ 12 | 13 | module.exports = function handleSpreadDoc(v, includeExtraKeys) { 14 | if (utils.isPOJO(v) && v.$__ != null && v._doc != null) { 15 | if (includeExtraKeys) { 16 | const extraKeys = {}; 17 | for (const key of Object.keys(v)) { 18 | if (typeof key === 'symbol') { 19 | continue; 20 | } 21 | if (key[0] === '$') { 22 | continue; 23 | } 24 | if (keysToSkip.has(key)) { 25 | continue; 26 | } 27 | extraKeys[key] = v[key]; 28 | } 29 | return { ...v._doc, ...extraKeys }; 30 | } 31 | return v._doc; 32 | } 33 | 34 | return v; 35 | }; 36 | -------------------------------------------------------------------------------- /test/helpers/indexes.isIndexSpecEqual.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const isIndexSpecEqual = require('../../lib/helpers/indexes/isIndexSpecEqual'); 5 | 6 | describe('isIndexSpecEqual', function() { 7 | it('should return true for equal index specifications', () => { 8 | const spec1 = { name: 1, age: -1 }; 9 | const spec2 = { name: 1, age: -1 }; 10 | const result = isIndexSpecEqual(spec1, spec2); 11 | assert.strictEqual(result, true); 12 | }); 13 | 14 | it('should return false for different key order', () => { 15 | const spec1 = { name: 1, age: -1 }; 16 | const spec2 = { age: -1, name: 1 }; 17 | const result = isIndexSpecEqual(spec1, spec2); 18 | assert.strictEqual(result, false); 19 | }); 20 | 21 | it('should return false for different index keys', () => { 22 | const spec1 = { name: 1, age: -1 }; 23 | const spec2 = { name: 1, dob: -1 }; 24 | const result = isIndexSpecEqual(spec1, spec2); 25 | assert.strictEqual(result, false); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /lib/helpers/timestamps/setDocumentTimestamps.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function setDocumentTimestamps(doc, timestampOption, currentTime, createdAt, updatedAt) { 4 | const skipUpdatedAt = timestampOption != null && timestampOption.updatedAt === false; 5 | const skipCreatedAt = timestampOption != null && timestampOption.createdAt === false; 6 | 7 | const defaultTimestamp = currentTime != null ? 8 | currentTime() : 9 | doc.ownerDocument().constructor.base.now(); 10 | 11 | if (!skipCreatedAt && 12 | (doc.isNew || doc.$isSubdocument) && 13 | createdAt && 14 | !doc.$__getValue(createdAt) && 15 | doc.$__isSelected(createdAt)) { 16 | doc.$set(createdAt, defaultTimestamp, undefined, { overwriteImmutable: true }); 17 | } 18 | 19 | if (!skipUpdatedAt && updatedAt && (doc.isNew || doc.$isModified())) { 20 | let ts = defaultTimestamp; 21 | if (doc.isNew && createdAt != null) { 22 | ts = doc.$__getValue(createdAt); 23 | } 24 | doc.$set(updatedAt, ts); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /.github/workflows/tidelift-alignment.yml: -------------------------------------------------------------------------------- 1 | name: Tidelift Alignment 2 | on: 3 | push: 4 | paths: 5 | - '.github/workflows/tidelift-alignment.yml' 6 | - 'package.json' 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | build: 13 | name: Run Tidelift to ensure approved open source packages are in use 14 | runs-on: ubuntu-latest 15 | if: github.repository == 'Automattic/mongoose' 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 19 | - name: Setup node 20 | uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 21 | with: 22 | node-version: 22 23 | - name: Alignment 24 | uses: tidelift/alignment-action@8d7700fe795fc01179c1f9fa05b72a089873027d # main 25 | env: 26 | TIDELIFT_API_KEY: ${{ secrets.TIDELIFT_API_KEY }} 27 | TIDELIFT_ORGANIZATION: ${{ secrets.TIDELIFT_ORGANIZATION }} 28 | TIDELIFT_PROJECT: ${{ secrets.TIDELIFT_PROJECT }} 29 | -------------------------------------------------------------------------------- /lib/helpers/printJestWarning.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const utils = require('../utils'); 4 | 5 | if (typeof jest !== 'undefined' && !process.env.SUPPRESS_JEST_WARNINGS) { 6 | if (typeof window !== 'undefined') { 7 | utils.warn('Mongoose: looks like you\'re trying to test a Mongoose app ' + 8 | 'with Jest\'s default jsdom test environment. Please make sure you read ' + 9 | 'Mongoose\'s docs on configuring Jest to test Node.js apps: ' + 10 | 'https://mongoosejs.com/docs/jest.html. Set the SUPPRESS_JEST_WARNINGS to true ' + 11 | 'to hide this warning.'); 12 | } 13 | 14 | if (setTimeout.clock != null && typeof setTimeout.clock.Date === 'function') { 15 | utils.warn('Mongoose: looks like you\'re trying to test a Mongoose app ' + 16 | 'with Jest\'s mock timers enabled. Please make sure you read ' + 17 | 'Mongoose\'s docs on configuring Jest to test Node.js apps: ' + 18 | 'https://mongoosejs.com/docs/jest.html. Set the SUPPRESS_JEST_WARNINGS to true ' + 19 | 'to hide this warning.'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/helpers/projection/isExclusive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isDefiningProjection = require('./isDefiningProjection'); 4 | const isPOJO = require('../isPOJO'); 5 | 6 | /*! 7 | * ignore 8 | */ 9 | 10 | module.exports = function isExclusive(projection) { 11 | if (projection == null) { 12 | return null; 13 | } 14 | 15 | const keys = Object.keys(projection); 16 | let exclude = null; 17 | 18 | if (keys.length === 1 && keys[0] === '_id') { 19 | exclude = !projection._id; 20 | } else { 21 | for (let ki = 0; ki < keys.length; ++ki) { 22 | // Does this projection explicitly define inclusion/exclusion? 23 | // Explicitly avoid `$meta` and `$slice` 24 | const key = keys[ki]; 25 | if (key !== '_id' && isDefiningProjection(projection[key])) { 26 | exclude = isPOJO(projection[key]) ? 27 | (isExclusive(projection[key]) ?? exclude) : 28 | !projection[key]; 29 | if (exclude != null) { 30 | break; 31 | } 32 | } 33 | } 34 | } 35 | 36 | return exclude; 37 | }; 38 | -------------------------------------------------------------------------------- /lib/helpers/query/sanitizeFilter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const hasDollarKeys = require('./hasDollarKeys'); 4 | const { trustedSymbol } = require('./trusted'); 5 | 6 | module.exports = function sanitizeFilter(filter) { 7 | if (filter == null || typeof filter !== 'object') { 8 | return filter; 9 | } 10 | if (Array.isArray(filter)) { 11 | for (const subfilter of filter) { 12 | sanitizeFilter(subfilter); 13 | } 14 | return filter; 15 | } 16 | 17 | const filterKeys = Object.keys(filter); 18 | for (const key of filterKeys) { 19 | const value = filter[key]; 20 | if (value != null && value[trustedSymbol]) { 21 | continue; 22 | } 23 | if (key === '$and' || key === '$or') { 24 | sanitizeFilter(value); 25 | continue; 26 | } 27 | 28 | if (hasDollarKeys(value)) { 29 | const keys = Object.keys(value); 30 | if (keys.length === 1 && keys[0] === '$eq') { 31 | continue; 32 | } 33 | filter[key] = { $eq: filter[key] }; 34 | } 35 | } 36 | 37 | return filter; 38 | }; 39 | -------------------------------------------------------------------------------- /test/types/utility.test.ts: -------------------------------------------------------------------------------- 1 | import { MergeType, WithTimestamps } from 'mongoose'; 2 | import { expectType } from 'tsd'; 3 | 4 | type A = { a: string, c: number }; 5 | type B = { a: number, b: string }; 6 | 7 | expectType({} as MergeType['a']); 8 | expectType({} as MergeType['b']); 9 | expectType({} as MergeType['c']); 10 | 11 | expectType({} as MergeType['a']); 12 | expectType({} as MergeType['b']); 13 | expectType({} as MergeType['c']); 14 | 15 | type C = WithTimestamps<{ a: string; b: string }>; 16 | expectType({} as C['a']); 17 | expectType({} as C['b']); 18 | expectType({} as C['createdAt']); 19 | expectType({} as C['updatedAt']); 20 | 21 | type D = WithTimestamps< 22 | { a: string; b: string }, 23 | { 24 | createdAt: 'created'; 25 | updatedAt: 'modified'; 26 | } 27 | >; 28 | expectType({} as D['a']); 29 | expectType({} as D['b']); 30 | expectType({} as D['created']); 31 | expectType({} as D['modified']); 32 | -------------------------------------------------------------------------------- /docs/source/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fs = require('fs'); 3 | 4 | /** 5 | * @typedef {import("./index").DocsOptions} DocsOptions 6 | */ 7 | 8 | /** 9 | * Map sub-directories with custom options 10 | * @param {String} subDoc Path to the subdoc, like "tutorials" (no beginning or ending slash) 11 | * @param {DocsOptions} options Options applied to all files in the subdoc (title gets appended to provided title) 12 | * @param {Object} exportsObj The "module.exports" object to apply changes to 13 | */ 14 | function mapSubDoc(subDoc, options, exportsObj) { 15 | const dirName = `docs/${subDoc}`; 16 | 17 | const files = fs.readdirSync(dirName).filter(file => file.endsWith('.md')); 18 | 19 | files.forEach((filename) => { 20 | const content = fs.readFileSync(`${dirName}/${filename}`, 'utf8'); 21 | exportsObj[`${dirName}/${filename}`] = { 22 | ...options, 23 | title: `${options.title} ${content.split('\n')[0].replace(/^#+/, '').trim()}` 24 | }; 25 | }); 26 | } 27 | 28 | module.exports = mapSubDoc; 29 | module.exports.mapSubDoc = mapSubDoc; 30 | -------------------------------------------------------------------------------- /lib/helpers/projection/hasIncludedChildren.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Creates an object that precomputes whether a given path has child fields in 5 | * the projection. 6 | * 7 | * #### Example: 8 | * 9 | * const res = hasIncludedChildren({ 'a.b.c': 0 }); 10 | * res.a; // 1 11 | * res['a.b']; // 1 12 | * res['a.b.c']; // 1 13 | * res['a.c']; // undefined 14 | * 15 | * @param {Object} fields 16 | * @api private 17 | */ 18 | 19 | module.exports = function hasIncludedChildren(fields) { 20 | const hasIncludedChildren = {}; 21 | const keys = Object.keys(fields); 22 | 23 | for (const key of keys) { 24 | 25 | if (key.indexOf('.') === -1) { 26 | hasIncludedChildren[key] = 1; 27 | continue; 28 | } 29 | const parts = key.split('.'); 30 | let c = parts[0]; 31 | 32 | for (let i = 0; i < parts.length; ++i) { 33 | hasIncludedChildren[c] = 1; 34 | if (i + 1 < parts.length) { 35 | c = c + '.' + parts[i + 1]; 36 | } 37 | } 38 | } 39 | 40 | return hasIncludedChildren; 41 | }; 42 | -------------------------------------------------------------------------------- /docs/css/copy-code.css: -------------------------------------------------------------------------------- 1 | /* Base button placement & visibility */ 2 | .copy-btn { 3 | position: absolute; 4 | top: 6px; 5 | right: 6px; 6 | background: transparent; 7 | border: none; 8 | padding: 4px; 9 | cursor: pointer; 10 | 11 | opacity: 0; 12 | transform: translateY(-3px); 13 | transition: opacity 0.18s ease, transform 0.18s ease, color 0.2s ease; 14 | 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | 19 | color: #666; /* default icon color */ 20 | } 21 | 22 | /* Show button only when the code block is hovered */ 23 | pre:hover .copy-btn { 24 | opacity: 1; 25 | transform: translateY(0); 26 | } 27 | 28 | /* Hover state always forces black — applies to both icons */ 29 | .copy-btn:hover { 30 | color: #000 !important; 31 | } 32 | 33 | /* Tick icon uses the same neutral grey so hover can override cleanly */ 34 | .copy-btn.copied { 35 | color: #666; 36 | } 37 | 38 | /* Icon sizing + smooth color transition */ 39 | .copy-btn svg { 40 | width: 20px; 41 | height: 20px; 42 | transition: color 0.2s ease; 43 | } 44 | -------------------------------------------------------------------------------- /lib/error/eachAsyncMultiError.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Module dependencies. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const MongooseError = require('./mongooseError'); 8 | 9 | 10 | /** 11 | * If `eachAsync()` is called with `continueOnError: true`, there can be 12 | * multiple errors. This error class contains an `errors` property, which 13 | * contains an array of all errors that occurred in `eachAsync()`. 14 | * 15 | * @api private 16 | */ 17 | 18 | class EachAsyncMultiError extends MongooseError { 19 | /** 20 | * @param {String} connectionString 21 | */ 22 | constructor(errors) { 23 | let preview = errors.map(e => e.message).join(', '); 24 | if (preview.length > 50) { 25 | preview = preview.slice(0, 50) + '...'; 26 | } 27 | super(`eachAsync() finished with ${errors.length} errors: ${preview}`); 28 | 29 | this.errors = errors; 30 | } 31 | } 32 | 33 | Object.defineProperty(EachAsyncMultiError.prototype, 'name', { 34 | value: 'EachAsyncMultiError' 35 | }); 36 | 37 | /*! 38 | * exports 39 | */ 40 | 41 | module.exports = EachAsyncMultiError; 42 | -------------------------------------------------------------------------------- /test/helpers/isAsyncFunction.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const isAsyncFunction = require('../../lib/helpers/isAsyncFunction'); 5 | 6 | describe('isAsyncFunction', function() { 7 | it('should return false for non-functions', () => { 8 | assert.ok(!isAsyncFunction('a')); 9 | assert.ok(!isAsyncFunction(1)); 10 | assert.ok(!isAsyncFunction(1n)); 11 | assert.ok(!isAsyncFunction({})); 12 | assert.ok(!isAsyncFunction(new Date())); 13 | assert.ok(!isAsyncFunction([])); 14 | assert.ok(!isAsyncFunction(true)); 15 | }); 16 | it('should return false for sync function', () => { 17 | assert.ok(!isAsyncFunction(function syncFunction() { return 'a';})); 18 | }); 19 | it('should return true for async function', () => { 20 | assert.ok(isAsyncFunction(async function asyncFunction() { return 'a';})); 21 | }); 22 | it('should return false for sync function returning a Promise', () => { 23 | assert.ok(!isAsyncFunction(function promiseReturningFunction() { return Promise.resolve('a');})); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /lib/helpers/discriminator/getConstructor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getDiscriminatorByValue = require('./getDiscriminatorByValue'); 4 | 5 | /** 6 | * Find the correct constructor, taking into account discriminators 7 | * @api private 8 | */ 9 | 10 | module.exports = function getConstructor(Constructor, value, defaultDiscriminatorValue) { 11 | const discriminatorKey = Constructor.schema.options.discriminatorKey; 12 | let discriminatorValue = (value != null && value[discriminatorKey]); 13 | if (discriminatorValue == null) { 14 | discriminatorValue = defaultDiscriminatorValue; 15 | } 16 | if (Constructor.discriminators && 17 | discriminatorValue != null) { 18 | if (Constructor.discriminators[discriminatorValue]) { 19 | Constructor = Constructor.discriminators[discriminatorValue]; 20 | } else { 21 | const constructorByValue = getDiscriminatorByValue(Constructor.discriminators, discriminatorValue); 22 | if (constructorByValue) { 23 | Constructor = constructorByValue; 24 | } 25 | } 26 | } 27 | 28 | return Constructor; 29 | }; 30 | -------------------------------------------------------------------------------- /test/schema.number.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const start = require('./common'); 5 | 6 | const mongoose = start.mongoose; 7 | const Schema = mongoose.Schema; 8 | 9 | describe('SchemaNumber', function() { 10 | it('allows 0 with required: true and ref set (gh-11912)', async function() { 11 | const schema = new Schema({ x: { type: Number, required: true, ref: 'Foo' } }); 12 | 13 | await schema.path('x').doValidate(0); 14 | }); 15 | 16 | it('allows calling `min()` with no message arg (gh-15236)', async function() { 17 | const schema = new Schema({ x: { type: Number } }); 18 | schema.path('x').min(0); 19 | 20 | const err = await schema.path('x').doValidate(-1).then(() => null, err => err); 21 | assert.ok(err); 22 | assert.equal(err.message, 'Path `x` (-1) is less than minimum allowed value (0).'); 23 | 24 | schema.path('x').min(0, 'Invalid value!'); 25 | 26 | const err2 = await schema.path('x').doValidate(-1).then(() => null, err => err); 27 | assert.equal(err2.message, 'Invalid value!'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/schema.boolean.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | const start = require('./common'); 8 | 9 | const assert = require('assert'); 10 | 11 | const mongoose = start.mongoose; 12 | const Schema = mongoose.Schema; 13 | 14 | describe('schematype', function() { 15 | describe('boolean', function() { 16 | it('null default is permitted (gh-523)', function(done) { 17 | mongoose.deleteModel(/Test/); 18 | const s1 = new Schema({ b: { type: Boolean, default: null } }); 19 | const M1 = mongoose.model('Test1', s1); 20 | const s2 = new Schema({ b: { type: Boolean, default: false } }); 21 | const M2 = mongoose.model('Test2', s2); 22 | const s3 = new Schema({ b: { type: Boolean, default: true } }); 23 | const M3 = mongoose.model('Test3', s3); 24 | 25 | const m1 = new M1(); 26 | assert.strictEqual(null, m1.b); 27 | const m2 = new M2(); 28 | assert.strictEqual(false, m2.b); 29 | const m3 = new M3(); 30 | assert.strictEqual(true, m3.b); 31 | done(); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /benchmarks/insertManySimple.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('../'); 4 | 5 | run().catch(err => { 6 | console.error(err); 7 | process.exit(-1); 8 | }); 9 | 10 | async function run() { 11 | await mongoose.connect('mongodb://127.0.0.1:27017/mongoose_benchmark'); 12 | const FooSchema = new mongoose.Schema({ foo: String }); 13 | const FooModel = mongoose.model('Foo', FooSchema); 14 | 15 | if (!process.env.MONGOOSE_BENCHMARK_SKIP_SETUP) { 16 | await FooModel.deleteMany({}); 17 | } 18 | 19 | const numDocs = 1500; 20 | const docs = []; 21 | for (let i = 0; i < numDocs; ++i) { 22 | docs.push({ foo: 'test foo ' + i }); 23 | } 24 | 25 | const numIterations = 200; 26 | const insertStart = Date.now(); 27 | for (let i = 0; i < numIterations; ++i) { 28 | await FooModel.insertMany(docs); 29 | } 30 | const insertEnd = Date.now(); 31 | 32 | const results = { 33 | 'Average insertMany time ms': +((insertEnd - insertStart) / numIterations).toFixed(2) 34 | }; 35 | 36 | console.log(JSON.stringify(results, null, ' ')); 37 | process.exit(0); 38 | } 39 | -------------------------------------------------------------------------------- /benchmarks/createDeepNestedDocArray.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('../'); 4 | 5 | run().catch(err => { 6 | console.error(err); 7 | process.exit(-1); 8 | }); 9 | 10 | async function run() { 11 | await mongoose.connect('mongodb://127.0.0.1:27017/mongoose_benchmark'); 12 | 13 | const levels = 12; 14 | 15 | let schema = new mongoose.Schema({ test: { type: String, required: true } }); 16 | let doc = { test: 'gh-14897' }; 17 | for (let i = 0; i < levels; ++i) { 18 | schema = new mongoose.Schema({ level: Number, subdocs: [schema] }); 19 | doc = { level: (levels - i), subdocs: [{ ...doc }, { ...doc }] }; 20 | } 21 | const Test = mongoose.model('Test', schema); 22 | 23 | if (!process.env.MONGOOSE_BENCHMARK_SKIP_SETUP) { 24 | await Test.deleteMany({}); 25 | } 26 | 27 | const insertStart = Date.now(); 28 | await Test.create(doc); 29 | const insertEnd = Date.now(); 30 | 31 | const results = { 32 | 'create() time ms': +(insertEnd - insertStart).toFixed(2) 33 | }; 34 | 35 | console.log(JSON.stringify(results, null, ' ')); 36 | process.exit(0); 37 | } -------------------------------------------------------------------------------- /lib/cast/number.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | /** 6 | * Given a value, cast it to a number, or throw an `Error` if the value 7 | * cannot be casted. `null` and `undefined` are considered valid. 8 | * 9 | * @param {Any} value 10 | * @return {Number} 11 | * @throws {Error} if `value` is not one of the allowed values 12 | * @api private 13 | */ 14 | 15 | module.exports = function castNumber(val) { 16 | if (val == null) { 17 | return val; 18 | } 19 | if (val === '') { 20 | return null; 21 | } 22 | 23 | if (typeof val === 'string' || typeof val === 'boolean') { 24 | val = Number(val); 25 | } 26 | 27 | assert.ok(!isNaN(val)); 28 | if (val instanceof Number) { 29 | return val.valueOf(); 30 | } 31 | if (typeof val === 'number') { 32 | return val; 33 | } 34 | if (!Array.isArray(val) && typeof val.valueOf === 'function') { 35 | return Number(val.valueOf()); 36 | } 37 | if (val.toString && !Array.isArray(val) && val.toString() == Number(val)) { 38 | return Number(val); 39 | } 40 | 41 | assert.ok(false); 42 | }; 43 | -------------------------------------------------------------------------------- /lib/cast/uuid.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const UUID = require('mongodb/lib/bson').UUID; 4 | 5 | const UUID_FORMAT = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/i; 6 | 7 | module.exports = function castUUID(value) { 8 | if (value == null) { 9 | return value; 10 | } 11 | 12 | if (value instanceof UUID) { 13 | return value; 14 | } 15 | if (typeof value === 'string') { 16 | if (UUID_FORMAT.test(value)) { 17 | return new UUID(value); 18 | } else { 19 | throw new Error(`"${value}" is not a valid UUID string`); 20 | } 21 | } 22 | 23 | // Re: gh-647 and gh-3030, we're ok with casting using `toString()` 24 | // **unless** its the default Object.toString, because "[object Object]" 25 | // doesn't really qualify as useful data 26 | if (value.toString && value.toString !== Object.prototype.toString) { 27 | if (UUID_FORMAT.test(value.toString())) { 28 | return new UUID(value.toString()); 29 | } 30 | } 31 | 32 | throw new Error(`"${value}" cannot be casted to a UUID`); 33 | }; 34 | 35 | module.exports.UUID_FORMAT = UUID_FORMAT; 36 | -------------------------------------------------------------------------------- /lib/helpers/indexes/isIndexSpecEqual.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Compares two index specifications to determine if they are equal. 5 | * 6 | * #### Example: 7 | * isIndexSpecEqual({ a: 1, b: 1 }, { a: 1, b: 1 }); // true 8 | * isIndexSpecEqual({ a: 1, b: 1 }, { b: 1, a: 1 }); // false 9 | * isIndexSpecEqual({ a: 1, b: -1 }, { a: 1, b: 1 }); // false 10 | * 11 | * @param {Object} spec1 The first index specification to compare. 12 | * @param {Object} spec2 The second index specification to compare. 13 | * @returns {Boolean} Returns true if the index specifications are equal, otherwise returns false. 14 | */ 15 | 16 | module.exports = function isIndexSpecEqual(spec1, spec2) { 17 | const spec1Keys = Object.keys(spec1); 18 | const spec2Keys = Object.keys(spec2); 19 | 20 | if (spec1Keys.length !== spec2Keys.length) { 21 | return false; 22 | } 23 | 24 | for (let i = 0; i < spec1Keys.length; i++) { 25 | const key = spec1Keys[i]; 26 | if (key !== spec2Keys[i] || spec1[key] !== spec2[key]) { 27 | return false; 28 | } 29 | } 30 | 31 | return true; 32 | }; 33 | -------------------------------------------------------------------------------- /test/parallelLimit.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const parallelLimit = require('../lib/helpers/parallelLimit'); 5 | 6 | describe('parallelLimit', function() { 7 | it('works with zero functions', async function() { 8 | const results = await parallelLimit([], value => Promise.resolve(value), 1); 9 | assert.deepEqual(results, []); 10 | }); 11 | 12 | it('executes functions in parallel', async function() { 13 | let started = 0; 14 | let finished = 0; 15 | const params = [1, 2, 3]; 16 | 17 | const fn = async() => { 18 | ++started; 19 | await new Promise(resolve => setTimeout(resolve, 10)); 20 | ++finished; 21 | return finished; 22 | }; 23 | 24 | const results = await parallelLimit(params, async(param, index) => { 25 | if (index === 2) { 26 | assert.equal(started, 2); 27 | assert.ok(finished > 0); 28 | } 29 | return fn(); 30 | }, 2); 31 | 32 | assert.equal(started, 3); 33 | assert.equal(finished, 3); 34 | assert.deepStrictEqual(results, [1, 2, 3]); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/types/queryhelpers.test.ts: -------------------------------------------------------------------------------- 1 | import { HydratedDocument, Model, Query, Schema, model } from 'mongoose'; 2 | 3 | interface Project { 4 | name: string; 5 | stars: number; 6 | } 7 | 8 | type ProjectModelType = Model; 9 | // Query helpers should return `Query> & ProjectQueryHelpers` 10 | // to enable chaining. 11 | type ProjectModelQuery = Query, ProjectQueryHelpers, any> & ProjectQueryHelpers; 12 | interface ProjectQueryHelpers { 13 | byName(this: ProjectModelQuery, name: string): ProjectModelQuery; 14 | } 15 | 16 | const schema = new Schema({ 17 | name: { type: String, required: true }, 18 | stars: { type: Number, required: true } 19 | }); 20 | schema.query.byName = function(name: string): ProjectModelQuery { 21 | return this.find({ name: name }); 22 | }; 23 | 24 | // 2nd param to `model()` is the Model class to return. 25 | const ProjectModel = model('Project', schema); 26 | 27 | ProjectModel.find().where('stars').gt(1000).byName('mongoose').exec(); 28 | -------------------------------------------------------------------------------- /lib/helpers/projection/isInclusive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isDefiningProjection = require('./isDefiningProjection'); 4 | const isPOJO = require('../isPOJO'); 5 | 6 | /*! 7 | * ignore 8 | */ 9 | 10 | module.exports = function isInclusive(projection) { 11 | if (projection == null) { 12 | return false; 13 | } 14 | 15 | const props = Object.keys(projection); 16 | const numProps = props.length; 17 | if (numProps === 0) { 18 | return false; 19 | } 20 | 21 | for (let i = 0; i < numProps; ++i) { 22 | const prop = props[i]; 23 | // Plus paths can't define the projection (see gh-7050) 24 | if (prop.startsWith('+')) { 25 | continue; 26 | } 27 | // If field is truthy (1, true, etc.) and not an object, then this 28 | // projection must be inclusive. If object, assume its $meta, $slice, etc. 29 | if (isDefiningProjection(projection[prop]) && !!projection[prop]) { 30 | if (isPOJO(projection[prop])) { 31 | return isInclusive(projection[prop]); 32 | } else { 33 | return !!projection[prop]; 34 | } 35 | } 36 | } 37 | 38 | return false; 39 | }; 40 | -------------------------------------------------------------------------------- /tools/sharded.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | run().catch(error => { 4 | console.error(error); 5 | process.exit(-1); 6 | }); 7 | 8 | 9 | async function run() { 10 | const Sharded = require('mongodb-topology-manager').Sharded; 11 | 12 | // Create new instance 13 | const topology = new Sharded({ 14 | mongod: 'mongod', 15 | mongos: 'mongos' 16 | }); 17 | 18 | await topology.addShard([{ 19 | options: { 20 | bind_ip: '127.0.0.1', port: 31000, dbpath: '/data/db/31000', shardsvr: null 21 | } 22 | }], { replSet: 'rs1' }); 23 | 24 | await topology.addConfigurationServers([{ 25 | options: { 26 | bind_ip: '127.0.0.1', port: 35000, dbpath: '/data/db/35000' 27 | } 28 | }], { replSet: 'rs0' }); 29 | 30 | await topology.addProxies([{ 31 | bind_ip: '127.0.0.1', port: 51000, configdb: '127.0.0.1:35000' 32 | }], { 33 | binary: 'mongos' 34 | }); 35 | 36 | console.log('Start...'); 37 | // Start up topology 38 | await topology.start(); 39 | 40 | console.log('Started'); 41 | 42 | // Shard db 43 | await topology.enableSharding('test'); 44 | 45 | console.log('done'); 46 | } 47 | -------------------------------------------------------------------------------- /docs/images/search.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lib/helpers/schema/merge.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function merge(s1, s2, skipConflictingPaths) { 4 | const paths = Object.keys(s2.tree); 5 | const pathsToAdd = {}; 6 | for (const key of paths) { 7 | if (skipConflictingPaths && (s1.paths[key] || s1.nested[key] || s1.singleNestedPaths[key])) { 8 | continue; 9 | } 10 | pathsToAdd[key] = s2.tree[key]; 11 | } 12 | s1.options._isMerging = true; 13 | s1.add(pathsToAdd, null); 14 | delete s1.options._isMerging; 15 | 16 | s1.callQueue = s1.callQueue.concat(s2.callQueue); 17 | s1.method(s2.methods); 18 | s1.static(s2.statics); 19 | 20 | for (const [option, value] of Object.entries(s2._userProvidedOptions)) { 21 | if (!(option in s1._userProvidedOptions)) { 22 | s1.set(option, value); 23 | } 24 | } 25 | 26 | for (const query in s2.query) { 27 | s1.query[query] = s2.query[query]; 28 | } 29 | 30 | for (const virtual in s2.virtuals) { 31 | s1.virtuals[virtual] = s2.virtuals[virtual].clone(); 32 | } 33 | 34 | s1._indexes = s1._indexes.concat(s2._indexes || []); 35 | s1.s.hooks.merge(s2.s.hooks, false); 36 | }; 37 | -------------------------------------------------------------------------------- /docs/css/inlinecpc.css: -------------------------------------------------------------------------------- 1 | .native-inline { 2 | visibility: hidden; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, 4 | Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif; 5 | opacity: 0; 6 | transition: all .25s ease-in-out; 7 | } 8 | 9 | .native-show { 10 | visibility: visible; 11 | opacity: 1; 12 | } 13 | 14 | .native-inline { 15 | display: block; 16 | margin-top: 1.5em; 17 | padding: 10px 20px; 18 | border-radius: 4px; 19 | background: repeating-linear-gradient(-45deg, transparent, transparent 5px, hsla(0, 0%, 0%, .03) 5px, hsla(0, 0%, 0%, .03) 10px) hsla(0, 0%, 0%, .03); 20 | } 21 | 22 | .native-inline a { 23 | display: block; 24 | text-decoration: none; 25 | line-height: 1.5; 26 | } 27 | 28 | 29 | .native-inline .sponsor { 30 | position: relative; 31 | top: -1px; 32 | margin-right: 4px; 33 | padding: 2px 6px 3px; 34 | border: solid 1px currentColor; 35 | border-radius: 2px; 36 | color: currentColor; 37 | content: "Sponsor"; 38 | text-transform: uppercase; 39 | letter-spacing: 1px; 40 | font-weight: 600; 41 | font-size: 11px; 42 | line-height: 1; 43 | } 44 | -------------------------------------------------------------------------------- /lib/cast/decimal128.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Decimal128Type = require('../types/decimal128'); 4 | const assert = require('assert'); 5 | 6 | module.exports = function castDecimal128(value) { 7 | if (value == null) { 8 | return value; 9 | } 10 | 11 | if (typeof value === 'object' && typeof value.$numberDecimal === 'string') { 12 | return Decimal128Type.fromString(value.$numberDecimal); 13 | } 14 | 15 | if (value instanceof Decimal128Type) { 16 | return value; 17 | } 18 | 19 | if (typeof value === 'string') { 20 | return Decimal128Type.fromString(value); 21 | } 22 | 23 | if (typeof Buffer === 'function' && Buffer.isBuffer(value)) { 24 | return new Decimal128Type(value); 25 | } 26 | if (typeof Uint8Array === 'function' && value instanceof Uint8Array) { 27 | return new Decimal128Type(value); 28 | } 29 | 30 | if (typeof value === 'number') { 31 | return Decimal128Type.fromString(String(value)); 32 | } 33 | 34 | if (typeof value.valueOf === 'function' && typeof value.valueOf() === 'string') { 35 | return Decimal128Type.fromString(value.valueOf()); 36 | } 37 | 38 | assert.ok(false); 39 | }; 40 | -------------------------------------------------------------------------------- /lib/cast/string.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const CastError = require('../error/cast'); 4 | 5 | /** 6 | * Given a value, cast it to a string, or throw a `CastError` if the value 7 | * cannot be casted. `null` and `undefined` are considered valid. 8 | * 9 | * @param {Any} value 10 | * @param {String} [path] optional the path to set on the CastError 11 | * @return {string|null|undefined} 12 | * @throws {CastError} 13 | * @api private 14 | */ 15 | 16 | module.exports = function castString(value, path) { 17 | // If null or undefined 18 | if (value == null) { 19 | return value; 20 | } 21 | 22 | // handle documents being passed 23 | if (value._id && typeof value._id === 'string') { 24 | return value._id; 25 | } 26 | 27 | // Re: gh-647 and gh-3030, we're ok with casting using `toString()` 28 | // **unless** its the default Object.toString, because "[object Object]" 29 | // doesn't really qualify as useful data 30 | if (value.toString && 31 | value.toString !== Object.prototype.toString && 32 | !Array.isArray(value)) { 33 | return value.toString(); 34 | } 35 | 36 | throw new CastError('string', value, path); 37 | }; 38 | -------------------------------------------------------------------------------- /lib/error/bulkWriteError.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Module dependencies. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const MongooseError = require('./'); 8 | 9 | 10 | /** 11 | * If `bulkWrite()` or `insertMany()` has validation errors, but 12 | * all valid operations succeed, and 'throwOnValidationError' is true, 13 | * Mongoose will throw this error. 14 | * 15 | * @api private 16 | */ 17 | 18 | class MongooseBulkWriteError extends MongooseError { 19 | constructor(validationErrors, results, rawResult, operation) { 20 | let preview = validationErrors.map(e => e.message).join(', '); 21 | if (preview.length > 200) { 22 | preview = preview.slice(0, 200) + '...'; 23 | } 24 | super(`${operation} failed with ${validationErrors.length} Mongoose validation errors: ${preview}`); 25 | 26 | this.validationErrors = validationErrors; 27 | this.results = results; 28 | this.rawResult = rawResult; 29 | this.operation = operation; 30 | } 31 | } 32 | 33 | Object.defineProperty(MongooseBulkWriteError.prototype, 'name', { 34 | value: 'MongooseBulkWriteError' 35 | }); 36 | 37 | /*! 38 | * exports 39 | */ 40 | 41 | module.exports = MongooseBulkWriteError; 42 | -------------------------------------------------------------------------------- /lib/helpers/schema/getSubdocumentStrictValue.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Find the `strict` mode setting for the deepest subdocument along a given path 5 | * to ensure we have the correct default value for `strict`. When setting values 6 | * underneath a subdocument, we should use the subdocument's `strict` setting by 7 | * default, not the top-level document's. 8 | * 9 | * @param {Schema} schema 10 | * @param {String[]} parts 11 | * @returns {boolean | 'throw' | undefined} 12 | */ 13 | 14 | module.exports = function getSubdocumentStrictValue(schema, parts) { 15 | if (parts.length === 1) { 16 | return undefined; 17 | } 18 | let cur = parts[0]; 19 | let strict = undefined; 20 | for (let i = 0; i < parts.length - 1; ++i) { 21 | const curSchemaType = schema.path(cur); 22 | if (curSchemaType && curSchemaType.schema) { 23 | strict = curSchemaType.schema.options.strict; 24 | schema = curSchemaType.schema; 25 | cur = curSchemaType.$isMongooseDocumentArray && !isNaN(parts[i + 1]) ? '' : parts[i + 1]; 26 | } else { 27 | cur += cur.length ? ('.' + parts[i + 1]) : parts[i + 1]; 28 | } 29 | } 30 | 31 | return strict; 32 | }; 33 | -------------------------------------------------------------------------------- /release-items.md: -------------------------------------------------------------------------------- 1 | # Release procedure 2 | 3 | ## mongoose release procedure 4 | 5 | 1. tests must pass 6 | 2. update `package.json` and `package-lock.json` version 7 | 3. update `CHANGELOG.md`. Add # as well as a link to the github user who fixed it if applicable. 8 | 4. git commit -a -m 'release x.x.x' 9 | 5. git tag x.x.x 10 | 6. `npm run release`, or `npm run release-legacy` for 4.x 11 | 7. update mongoosejs.com (see "updating the website" below) 12 | 8. tweet changelog link from [@mongoosejs](https://twitter.com/mongoosejs) 13 | 9. Announce on mongoosejsteam slack channel 14 | 10. if this is a legacy release, `git merge` changes into master. 15 | 16 | ## updating the website 17 | 18 | For 6.x 19 | 20 | 0. Change to the master branch 21 | 1. execute `npm run docs:prepare:publish:stable` (when this process completes you'll be on the gh-pages branch) 22 | 2. `git commit -a -m 'chore: website 6.x.x'` 23 | 3. `git push origin gh-pages` 24 | 25 | For 5.x 26 | 27 | 0. Change to the 5.x branch 28 | 1. execute `make docs_legacy` (when this process completes you'll be on the gh-pages branch) 29 | 2. `git commit -a -m 'chore: website 5.x.x'` 30 | 3. `git push origin gh-pages` 31 | -------------------------------------------------------------------------------- /test/helpers/query.selectPopulatedFields.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const selectPopulatedFields = require('../../lib/helpers/query/selectPopulatedFields'); 5 | 6 | describe('selectPopulatedFields', function() { 7 | it('selects refPath', function() { 8 | const fields = { name: 1 }; 9 | const userProvidedFields = { name: 1 }; 10 | const populateOptions = { 11 | parent: { 12 | refPath: 'parentModel' 13 | } 14 | }; 15 | selectPopulatedFields(fields, userProvidedFields, populateOptions); 16 | assert.deepStrictEqual(fields, { 17 | name: 1, 18 | parent: 1, 19 | parentModel: 1 20 | }); 21 | }); 22 | 23 | it('adds refPath to projection if not deselected by user in exclusive projection', function() { 24 | const fields = { name: 0, parentModel: 0 }; 25 | const userProvidedFields = { name: 0 }; 26 | const populateOptions = { 27 | parent: { 28 | refPath: 'parentModel' 29 | } 30 | }; 31 | selectPopulatedFields(fields, userProvidedFields, populateOptions); 32 | assert.deepStrictEqual(fields, { 33 | name: 0 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /lib/helpers/model/applyStaticHooks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { queryMiddlewareFunctions, aggregateMiddlewareFunctions, modelMiddlewareFunctions, documentMiddlewareFunctions } = require('../../constants'); 4 | 5 | const middlewareFunctions = Array.from( 6 | new Set([ 7 | ...queryMiddlewareFunctions, 8 | ...aggregateMiddlewareFunctions, 9 | ...modelMiddlewareFunctions, 10 | ...documentMiddlewareFunctions 11 | ]) 12 | ); 13 | 14 | module.exports = function applyStaticHooks(model, hooks, statics) { 15 | hooks = hooks.filter(hook => { 16 | // If the custom static overwrites an existing middleware, don't apply 17 | // middleware to it by default. This avoids a potential backwards breaking 18 | // change with plugins like `mongoose-delete` that use statics to overwrite 19 | // built-in Mongoose functions. 20 | if (middlewareFunctions.indexOf(hook.name) !== -1) { 21 | return !!hook.model; 22 | } 23 | return hook.model !== false; 24 | }); 25 | 26 | for (const key of Object.keys(statics)) { 27 | if (hooks.hasHooks(key)) { 28 | const original = model[key]; 29 | 30 | model[key] = hooks.createWrapper(key, original); 31 | } 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2010-2013 LearnBoost 4 | Copyright (c) 2013-2021 Automattic 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /lib/helpers/populate/setPopulatedVirtualValue.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Set a populated virtual value on a document's `$$populatedVirtuals` value 5 | * 6 | * @param {*} populatedVirtuals A document's `$$populatedVirtuals` 7 | * @param {*} name The virtual name 8 | * @param {*} v The result of the populate query 9 | * @param {*} options The populate options. This function handles `justOne` and `count` options. 10 | * @returns {Array|Document|Object|Array} the populated virtual value that was set 11 | */ 12 | 13 | module.exports = function setPopulatedVirtualValue(populatedVirtuals, name, v, options) { 14 | if (options.justOne || options.count) { 15 | populatedVirtuals[name] = Array.isArray(v) ? 16 | v[0] : 17 | v; 18 | 19 | if (typeof populatedVirtuals[name] !== 'object') { 20 | populatedVirtuals[name] = options.count ? v : null; 21 | } 22 | } else { 23 | populatedVirtuals[name] = Array.isArray(v) ? 24 | v : 25 | v == null ? [] : [v]; 26 | 27 | populatedVirtuals[name] = populatedVirtuals[name].filter(function(doc) { 28 | return doc && typeof doc === 'object'; 29 | }); 30 | } 31 | 32 | return populatedVirtuals[name]; 33 | }; 34 | -------------------------------------------------------------------------------- /test/types.decimal128.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | const start = require('./common'); 8 | 9 | const assert = require('assert'); 10 | 11 | const mongoose = start.mongoose; 12 | const Schema = mongoose.Schema; 13 | 14 | /** 15 | * Test. 16 | */ 17 | 18 | describe('types.decimal128', function() { 19 | it('casts from type number (gh-6331)', function() { 20 | const dec128 = new Schema({ 21 | value: Schema.Types.Decimal128 22 | }); 23 | 24 | const BigNum = mongoose.model('gh6331', dec128); 25 | 26 | const big = new BigNum({ value: 10000 }); 27 | 28 | assert.strictEqual(big.value.toString(), '10000'); 29 | }); 30 | 31 | it('uses valueOf method if one exists (gh-6418)', function() { 32 | const dec128 = new Schema({ 33 | value: Schema.Types.Decimal128 34 | }); 35 | 36 | mongoose.deleteModel(/Test/); 37 | const BigNum = mongoose.model('Test', dec128); 38 | 39 | const obj = { 40 | str: '10.123', 41 | valueOf: function() { 42 | return this.str; 43 | } 44 | }; 45 | 46 | const big = new BigNum({ value: obj }); 47 | 48 | assert.strictEqual(big.value.toString(), '10.123'); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /lib/error/notFound.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * Module dependencies. 5 | */ 6 | 7 | const MongooseError = require('./mongooseError'); 8 | const util = require('util'); 9 | 10 | /** 11 | * OverwriteModel Error constructor. 12 | * @api private 13 | */ 14 | 15 | class DocumentNotFoundError extends MongooseError { 16 | 17 | constructor(filter, model, numAffected, result) { 18 | let msg; 19 | const messages = MongooseError.messages; 20 | if (messages.DocumentNotFoundError != null) { 21 | msg = typeof messages.DocumentNotFoundError === 'function' ? 22 | messages.DocumentNotFoundError(filter, model) : 23 | messages.DocumentNotFoundError; 24 | } else { 25 | msg = 'No document found for query "' + util.inspect(filter) + 26 | '" on model "' + model + '"'; 27 | } 28 | 29 | super(msg); 30 | 31 | this.result = result; 32 | this.numAffected = numAffected; 33 | this.filter = filter; 34 | // Backwards compat 35 | this.query = filter; 36 | } 37 | } 38 | 39 | Object.defineProperty(DocumentNotFoundError.prototype, 'name', { 40 | value: 'DocumentNotFoundError' 41 | }); 42 | 43 | /*! 44 | * exports 45 | */ 46 | 47 | module.exports = DocumentNotFoundError; 48 | -------------------------------------------------------------------------------- /lib/helpers/document/getDeepestSubdocumentForPath.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Find the deepest subdocument along a given path to ensure setter functions run 5 | * with the correct subdocument as `this`. If no subdocuments, returns the top-level 6 | * document. 7 | * 8 | * @param {Document} doc 9 | * @param {String[]} parts 10 | * @param {Schema} schema 11 | * @returns Document 12 | */ 13 | 14 | module.exports = function getDeepestSubdocumentForPath(doc, parts, schema) { 15 | let curPath = parts[0]; 16 | let curSchema = schema; 17 | let subdoc = doc; 18 | for (let i = 0; i < parts.length - 1; ++i) { 19 | const curSchemaType = curSchema.path(curPath); 20 | if (curSchemaType && curSchemaType.schema) { 21 | let newSubdoc = subdoc.get(curPath); 22 | curSchema = curSchemaType.schema; 23 | curPath = parts[i + 1]; 24 | if (Array.isArray(newSubdoc) && !isNaN(curPath)) { 25 | newSubdoc = newSubdoc[curPath]; 26 | curPath = ''; 27 | } 28 | if (newSubdoc == null) { 29 | break; 30 | } 31 | subdoc = newSubdoc; 32 | } else { 33 | curPath += curPath.length ? '.' + parts[i + 1] : parts[i + 1]; 34 | } 35 | } 36 | 37 | return subdoc; 38 | }; 39 | -------------------------------------------------------------------------------- /test/helpers/getModelsMapForPopulate.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const start = require('../common'); 5 | const util = require('../util'); 6 | 7 | const mongoose = start.mongoose; 8 | const Schema = mongoose.Schema; 9 | 10 | describe('getModelsMapForPopulate', function() { 11 | let db; 12 | 13 | beforeEach(() => db.deleteModel(/.*/)); 14 | 15 | before(function() { 16 | db = start(); 17 | }); 18 | 19 | after(async function() { 20 | await db.close(); 21 | }); 22 | 23 | afterEach(() => util.clearTestData(db)); 24 | afterEach(() => util.stopRemainingOps(db)); 25 | 26 | it('should error on missing options on populate', async function() { 27 | const sch = new Schema({ 28 | test: mongoose.Schema.Types.ObjectId 29 | }, { 30 | virtuals: { 31 | someVirtual: {} 32 | } 33 | }); 34 | 35 | const model = db.model('Test', sch); 36 | 37 | const doc = await model.create({ test: new mongoose.Types.ObjectId() }); 38 | 39 | await assert.rejects(() => model.findById(doc._id).populate('someVirtual').exec(), /Cannot populate virtual `someVirtual` on model `Test`, because options `localField` and \/ or `foreignField` are missing/); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /docs/js/theme-toggle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | (function() { 3 | const STORAGE_KEY = 'mongoose-theme'; 4 | const supportsMatchMedia = typeof window !== 'undefined' && typeof window.matchMedia === 'function'; 5 | const prefersDarkQuery = supportsMatchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null; 6 | 7 | const theme = localStorage.getItem(STORAGE_KEY) || (prefersDarkQuery?.matches ? 'dark' : 'light'); 8 | applyTheme(theme, true); 9 | const toggleBtn = document.getElementById('theme-toggle-btn'); 10 | toggleBtn.addEventListener('click', toggleTheme); 11 | 12 | function toggleTheme() { 13 | const currentTheme = document.documentElement.getAttribute('data-theme'); 14 | if (currentTheme === 'dark') { 15 | applyTheme('light'); 16 | } else { 17 | applyTheme('dark'); 18 | } 19 | } 20 | 21 | function applyTheme(theme, skipSetStorage) { 22 | document.documentElement.setAttribute('data-theme', theme); 23 | if (!skipSetStorage) { 24 | try { 25 | localStorage.setItem(STORAGE_KEY, theme); 26 | // eslint-disable-next-line no-unused-vars 27 | } catch (err) { 28 | // Silently fail - theme will still work for current session 29 | } 30 | } 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /lib/helpers/symbols.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.arrayAtomicsBackupSymbol = Symbol('mongoose#Array#atomicsBackup'); 4 | exports.arrayAtomicsSymbol = Symbol('mongoose#Array#_atomics'); 5 | exports.arrayParentSymbol = Symbol('mongoose#Array#_parent'); 6 | exports.arrayPathSymbol = Symbol('mongoose#Array#_path'); 7 | exports.arraySchemaSymbol = Symbol('mongoose#Array#_schema'); 8 | exports.documentArrayParent = Symbol('mongoose#documentArrayParent'); 9 | exports.documentIsSelected = Symbol('mongoose#Document#isSelected'); 10 | exports.documentIsModified = Symbol('mongoose#Document#isModified'); 11 | exports.documentModifiedPaths = Symbol('mongoose#Document#modifiedPaths'); 12 | exports.documentSchemaSymbol = Symbol('mongoose#Document#schema'); 13 | exports.getSymbol = Symbol('mongoose#Document#get'); 14 | exports.modelSymbol = Symbol('mongoose#Model'); 15 | exports.objectIdSymbol = Symbol('mongoose#ObjectId'); 16 | exports.populateModelSymbol = Symbol('mongoose#PopulateOptions#Model'); 17 | exports.schemaTypeSymbol = Symbol('mongoose#schemaType'); 18 | exports.sessionNewDocuments = Symbol('mongoose#ClientSession#newDocuments'); 19 | exports.scopeSymbol = Symbol('mongoose#Document#scope'); 20 | exports.validatorErrorSymbol = Symbol('mongoose#validatorError'); 21 | -------------------------------------------------------------------------------- /lib/helpers/update/removeUnusedArrayFilters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * MongoDB throws an error if there's unused array filters. That is, if `options.arrayFilters` defines 5 | * a filter, but none of the `update` keys use it. This should be enough to filter out all unused array 6 | * filters. 7 | */ 8 | 9 | module.exports = function removeUnusedArrayFilters(update, arrayFilters) { 10 | const updateKeys = Object.keys(update). 11 | map(key => Object.keys(update[key])). 12 | reduce((cur, arr) => cur.concat(arr), []); 13 | return arrayFilters.filter(obj => { 14 | return _checkSingleFilterKey(obj, updateKeys); 15 | }); 16 | }; 17 | 18 | function _checkSingleFilterKey(arrayFilter, updateKeys) { 19 | const firstKey = Object.keys(arrayFilter)[0]; 20 | 21 | if (firstKey === '$and' || firstKey === '$or') { 22 | if (!Array.isArray(arrayFilter[firstKey])) { 23 | return false; 24 | } 25 | return arrayFilter[firstKey].find(filter => _checkSingleFilterKey(filter, updateKeys)) != null; 26 | } 27 | 28 | const firstDot = firstKey.indexOf('.'); 29 | const arrayFilterKey = firstDot === -1 ? firstKey : firstKey.slice(0, firstDot); 30 | 31 | return updateKeys.find(key => key.includes('$[' + arrayFilterKey + ']')) != null; 32 | } 33 | -------------------------------------------------------------------------------- /test/helpers/isObject.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const isObject = require('../../lib/helpers/isObject'); 5 | 6 | describe('isObject', () => { 7 | describe('true for', () => { 8 | it('{}', () => { 9 | assert.ok(isObject({})); 10 | }); 11 | 12 | it('Buffer', () => { 13 | assert.ok(isObject(Buffer.from([]))); 14 | }); 15 | 16 | it('Object', () => { 17 | assert.ok(isObject(new Object())); 18 | }); 19 | }); 20 | 21 | describe('false for', () => { 22 | it('""', () => { 23 | assert.ok(!isObject('')); 24 | }); 25 | 26 | it('/.*/', () => { 27 | assert.ok(!isObject(/.*/)); 28 | }); 29 | 30 | it('[]', () => { 31 | assert.ok(!isObject([])); 32 | }); 33 | 34 | it('Array', () => { 35 | assert.ok(!isObject(new Array())); 36 | }); 37 | 38 | it('Function', () => { 39 | assert.ok(!isObject(new Function())); 40 | }); 41 | 42 | it('RegExp', () => { 43 | assert.ok(!isObject(new RegExp())); 44 | }); 45 | 46 | it('String', () => { 47 | assert.ok(!isObject(new String())); 48 | }); 49 | 50 | it('"[object Object]"', () => { 51 | assert.ok(!isObject('[object Object]')); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Feature Proposal 2 | description: Submit a proposal for a new feature 3 | labels: ['enhancement', 'new feature'] 4 | 5 | body: 6 | - type: checkboxes 7 | id: prerequisites 8 | attributes: 9 | label: Prerequisites 10 | options: 11 | - label: I have written a descriptive issue title 12 | required: true 13 | - label: | 14 | I have searched existing issues to ensure the feature has not already been requested 15 | required: true 16 | 17 | - type: textarea 18 | id: proposal 19 | attributes: 20 | label: 🚀 Feature Proposal 21 | description: A clear and concise description of what the feature is. 22 | validations: 23 | required: true 24 | 25 | - type: textarea 26 | id: motivation 27 | attributes: 28 | label: Motivation 29 | description: The motivation for the proposal. 30 | 31 | - type: textarea 32 | id: example 33 | attributes: 34 | label: Example 35 | description: | 36 | An example for how this feature would be used. 37 | Make sure you place example code inside a [code (```) block](https://docs.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks) to avoid linking unrelated issues. -------------------------------------------------------------------------------- /lib/error/divergentArray.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Module dependencies. 4 | */ 5 | 6 | 'use strict'; 7 | 8 | const MongooseError = require('./mongooseError'); 9 | 10 | /** 11 | * DivergentArrayError constructor. 12 | * @param {Array} paths 13 | * @api private 14 | */ 15 | 16 | class DivergentArrayError extends MongooseError { 17 | 18 | constructor(paths) { 19 | const msg = 'For your own good, using `document.save()` to update an array ' 20 | + 'which was selected using an $elemMatch projection OR ' 21 | + 'populated using skip, limit, query conditions, or exclusion of ' 22 | + 'the _id field when the operation results in a $pop or $set of ' 23 | + 'the entire array is not supported. The following ' 24 | + 'path(s) would have been modified unsafely:\n' 25 | + ' ' + paths.join('\n ') + '\n' 26 | + 'Use Model.updateOne() to update these arrays instead. ' 27 | + 'See https://mongoosejs.com/docs/faq.html#divergent-array-error for more information.'; 28 | super(msg); 29 | } 30 | } 31 | 32 | Object.defineProperty(DivergentArrayError.prototype, 'name', { 33 | value: 'DivergentArrayError' 34 | }); 35 | 36 | /*! 37 | * exports 38 | */ 39 | 40 | module.exports = DivergentArrayError; 41 | -------------------------------------------------------------------------------- /lib/plugins/validateBeforeSave.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * ignore 5 | */ 6 | 7 | module.exports = function validateBeforeSave(schema) { 8 | const unshift = true; 9 | schema.pre('save', false, async function validateBeforeSave(options) { 10 | // Nested docs have their own presave 11 | if (this.$isSubdocument) { 12 | return; 13 | } 14 | 15 | const hasValidateBeforeSaveOption = options && 16 | (typeof options === 'object') && 17 | ('validateBeforeSave' in options); 18 | 19 | let shouldValidate; 20 | if (hasValidateBeforeSaveOption) { 21 | shouldValidate = !!options.validateBeforeSave; 22 | } else { 23 | shouldValidate = this.$__schema.options.validateBeforeSave; 24 | } 25 | 26 | // Validate 27 | if (shouldValidate) { 28 | const hasValidateModifiedOnlyOption = options && 29 | (typeof options === 'object') && 30 | ('validateModifiedOnly' in options); 31 | const validateOptions = hasValidateModifiedOnlyOption ? 32 | { validateModifiedOnly: options.validateModifiedOnly } : 33 | null; 34 | await this.$validate(validateOptions).then( 35 | () => { 36 | this.$op = 'save'; 37 | } 38 | ); 39 | } 40 | }, null, unshift); 41 | }; 42 | -------------------------------------------------------------------------------- /types/validation.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'mongoose' { 2 | type SchemaValidator = RegExp | 3 | [RegExp, string] | 4 | Function | 5 | [Function, string] | 6 | ValidateOpts | 7 | ValidateOpts[]; 8 | 9 | interface ValidatorProps { 10 | path: string; 11 | fullPath: string; 12 | value: any; 13 | reason?: Error; 14 | } 15 | 16 | interface ValidatorMessageFn { 17 | (props: ValidatorProps): string; 18 | } 19 | 20 | type ValidateFn = ( 21 | this: THydratedDocumentType | Query, 22 | value: any, 23 | props?: ValidatorProps & Record 24 | ) => boolean; 25 | 26 | type AsyncValidateFn = ( 27 | this: THydratedDocumentType | Query, 28 | value: any, 29 | props?: ValidatorProps & Record 30 | ) => Promise; 31 | 32 | interface ValidateOpts { 33 | msg?: string; 34 | message?: string | ValidatorMessageFn; 35 | type?: string; 36 | validator: ValidateFn | AsyncValidateFn; 37 | propsParameter?: boolean; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/helpers/discriminator/applyEmbeddedDiscriminators.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = applyEmbeddedDiscriminators; 4 | 5 | function applyEmbeddedDiscriminators(schema, seen = new WeakSet(), overwriteExisting = false) { 6 | if (seen.has(schema)) { 7 | return; 8 | } 9 | seen.add(schema); 10 | for (const path of Object.keys(schema.paths)) { 11 | const schemaType = schema.paths[path]; 12 | if (!schemaType.schema) { 13 | continue; 14 | } 15 | applyEmbeddedDiscriminators(schemaType.schema, seen); 16 | if (!schemaType.schema._applyDiscriminators) { 17 | continue; 18 | } 19 | if (schemaType._appliedDiscriminators && !overwriteExisting) { 20 | continue; 21 | } 22 | for (const discriminatorKey of schemaType.schema._applyDiscriminators.keys()) { 23 | const { 24 | schema: discriminatorSchema, 25 | options 26 | } = schemaType.schema._applyDiscriminators.get(discriminatorKey); 27 | applyEmbeddedDiscriminators(discriminatorSchema, seen); 28 | schemaType.discriminator( 29 | discriminatorKey, 30 | discriminatorSchema, 31 | overwriteExisting ? { ...options, overwriteExisting: true } : options 32 | ); 33 | } 34 | schemaType._appliedDiscriminators = true; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/schema.mixed.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | const start = require('./common'); 8 | 9 | const assert = require('assert'); 10 | 11 | const mongoose = new start.mongoose.Mongoose(); 12 | const Schema = mongoose.Schema; 13 | 14 | describe('schematype mixed', function() { 15 | describe('empty object defaults (gh-1380)', function() { 16 | it('are interpreted as fns that return new empty objects', function(done) { 17 | const s = new Schema({ mix: { type: Schema.Types.Mixed, default: {} } }); 18 | const M = mongoose.model('M1', s); 19 | const m1 = new M(); 20 | const m2 = new M(); 21 | m1.mix.val = 3; 22 | assert.equal(m1.mix.val, 3); 23 | assert.equal(m2.mix.val, undefined); 24 | done(); 25 | }); 26 | it('can be forced to share the object between documents', function(done) { 27 | // silly but necessary for backwards compatibility 28 | const s = new Schema({ mix: { type: Schema.Types.Mixed, default: {}, shared: true } }); 29 | const M = mongoose.model('M2', s); 30 | const m1 = new M(); 31 | const m2 = new M(); 32 | m1.mix.val = 3; 33 | assert.equal(m1.mix.val, 3); 34 | assert.equal(m2.mix.val, 3); 35 | done(); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /lib/cast/date.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = function castDate(value) { 6 | // Support empty string because of empty form values. Originally introduced 7 | // in https://github.com/Automattic/mongoose/commit/efc72a1898fc3c33a319d915b8c5463a22938dfe 8 | if (value == null || value === '') { 9 | return null; 10 | } 11 | 12 | if (value instanceof Date) { 13 | assert.ok(!isNaN(value.valueOf())); 14 | 15 | return value; 16 | } 17 | 18 | let date; 19 | 20 | assert.ok(typeof value !== 'boolean'); 21 | 22 | if (value instanceof Number || typeof value === 'number') { 23 | date = new Date(value); 24 | } else if (typeof value === 'string' && !isNaN(Number(value)) && (Number(value) >= 275761 || Number(value) < -271820)) { 25 | // string representation of milliseconds take this path 26 | date = new Date(Number(value)); 27 | } else if (typeof value.valueOf === 'function') { 28 | // support for moment.js. This is also the path strings will take because 29 | // strings have a `valueOf()` 30 | date = new Date(value.valueOf()); 31 | } else { 32 | // fallback 33 | date = new Date(value); 34 | } 35 | 36 | if (!isNaN(date.valueOf())) { 37 | return date; 38 | } 39 | 40 | assert.ok(false); 41 | }; 42 | -------------------------------------------------------------------------------- /test/multiple-require-instance.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const start = require('./common'); 4 | 5 | let mongoose2; 6 | 7 | describe('multiple require instance', function() { 8 | before(function() { 9 | try { 10 | mongoose2 = require('mongoose-separate-require-instance'); 11 | } catch (err) { 12 | if (err.code !== 'MODULE_NOT_FOUND') { 13 | throw err; 14 | } 15 | return this.skip(); 16 | } 17 | }); 18 | 19 | let db; 20 | beforeEach(function() { 21 | db = start(); 22 | }); 23 | afterEach(() => db.close()); 24 | 25 | it('supports arrays defined using schemas from separate instance (gh-15466)', async function() { 26 | const nestedSchema = new mongoose2.Schema( 27 | { name: String }, 28 | { _id: false } 29 | ); 30 | 31 | const schema = new mongoose2.Schema({ 32 | subDoc: nestedSchema, 33 | docArray: [nestedSchema] // this array of nestedSchema causes the error 34 | }); 35 | 36 | const document = { 37 | subDoc: { name: 'Alpha' }, 38 | docArray: [ 39 | { name: 'Bravo' }, 40 | { name: 'Charlie' } 41 | ] 42 | }; 43 | 44 | const TestModel = db.model('Test', schema); 45 | const doc = new TestModel(document); 46 | await doc.validate(); 47 | 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /lib/options/schemaMapOptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const SchemaTypeOptions = require('./schemaTypeOptions'); 4 | 5 | /** 6 | * The options defined on a Map schematype. 7 | * 8 | * #### Example: 9 | * 10 | * const schema = new Schema({ socialMediaHandles: { type: Map, of: String } }); 11 | * schema.path('socialMediaHandles').options; // SchemaMapOptions instance 12 | * 13 | * @api public 14 | * @inherits SchemaTypeOptions 15 | * @constructor SchemaMapOptions 16 | */ 17 | 18 | class SchemaMapOptions extends SchemaTypeOptions {} 19 | 20 | const opts = require('./propertyOptions'); 21 | 22 | /** 23 | * If set, specifies the type of this map's values. Mongoose will cast 24 | * this map's values to the given type. 25 | * 26 | * If not set, Mongoose will not cast the map's values. 27 | * 28 | * #### Example: 29 | * 30 | * // Mongoose will cast `socialMediaHandles` values to strings 31 | * const schema = new Schema({ socialMediaHandles: { type: Map, of: String } }); 32 | * schema.path('socialMediaHandles').options.of; // String 33 | * 34 | * @api public 35 | * @property of 36 | * @memberOf SchemaMapOptions 37 | * @type {Function|string} 38 | * @instance 39 | */ 40 | 41 | Object.defineProperty(SchemaMapOptions.prototype, 'of', opts); 42 | 43 | module.exports = SchemaMapOptions; 44 | -------------------------------------------------------------------------------- /docs/check-version.md: -------------------------------------------------------------------------------- 1 | # How to Check Your Mongoose Version 2 | 3 | To check what version of Mongoose you are using in Node.js, print out the [`mongoose.version` property](./api/mongoose.html#Mongoose.prototype.version) as follows. 4 | 5 | ```javascript 6 | const mongoose = require('mongoose'); 7 | 8 | console.log(mongoose.version); // '7.x.x' 9 | ``` 10 | 11 | We recommend printing the Mongoose version from Node.js, because that better handles cases where you have multiple versions of Mongoose installed. 12 | You can also execute the above logic from your terminal using Node.js' `-e` flag as follows. 13 | 14 | ```sh 15 | # Prints current Mongoose version, e.g. 7.0.3 16 | node -e "console.log(require('mongoose').version)" 17 | ``` 18 | 19 | ## Using `npm list` 20 | 21 | You can also [get the installed version of the Mongoose npm package](https://masteringjs.io/tutorials/npm/version) using `npm list`. 22 | 23 | ```sh 24 | $ npm list mongoose 25 | test@ /path/to/test 26 | └── mongoose@7.0.3 27 | ``` 28 | 29 | `npm list` is helpful because it can identify if you have multiple versions of Mongoose installed. 30 | 31 | Other package managers also support similar functions: 32 | 33 | * [`yarn list --pattern "mongoose"`](https://classic.yarnpkg.com/lang/en/docs/cli/list/) 34 | * [`pnpm list "mongoose"`](https://pnpm.io/cli/list) 35 | -------------------------------------------------------------------------------- /docs/js/mobile-navbar-toggle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // this js is for mobile, to toggle the navbar 3 | 4 | (function(window, document) { 5 | const layout = document.getElementById('layout'), 6 | menu = document.getElementById('menu'), 7 | menuLink = document.getElementById('menuLink'), 8 | content = document.getElementById('content'); 9 | 10 | function toggleClass(element, className) { 11 | const classes = element.className.split(/\s+/), 12 | length = classes.length; 13 | 14 | for (let i = 0; i < length; i++) { 15 | if (classes[i] === className) { 16 | classes.splice(i, 1); 17 | break; 18 | } 19 | } 20 | // The className is not found 21 | if (length === classes.length) { 22 | classes.push(className); 23 | } 24 | 25 | element.className = classes.join(' '); 26 | } 27 | 28 | function toggleAll(e) { 29 | const active = 'active'; 30 | 31 | e.preventDefault(); 32 | toggleClass(layout, active); 33 | toggleClass(menu, active); 34 | toggleClass(menuLink, active); 35 | } 36 | 37 | menuLink.onclick = function(e) { 38 | toggleAll(e); 39 | }; 40 | 41 | content.onclick = function(e) { 42 | if (menu.className.indexOf('active') !== -1) { 43 | toggleAll(e); 44 | } 45 | }; 46 | 47 | }(this, this.document)); 48 | -------------------------------------------------------------------------------- /lib/schema/operators/text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const CastError = require('../../error/cast'); 4 | const castBoolean = require('../../cast/boolean'); 5 | const castString = require('../../cast/string'); 6 | 7 | /** 8 | * Casts val to an object suitable for `$text`. Throws an error if the object 9 | * can't be casted. 10 | * 11 | * @param {Any} val value to cast 12 | * @param {String} [path] path to associate with any errors that occured 13 | * @return {Object} casted object 14 | * @see https://www.mongodb.com/docs/manual/reference/operator/query/text/ 15 | * @api private 16 | */ 17 | 18 | module.exports = function castTextSearch(val, path) { 19 | if (val == null || typeof val !== 'object') { 20 | throw new CastError('$text', val, path); 21 | } 22 | 23 | if (val.$search != null) { 24 | val.$search = castString(val.$search, path + '.$search'); 25 | } 26 | if (val.$language != null) { 27 | val.$language = castString(val.$language, path + '.$language'); 28 | } 29 | if (val.$caseSensitive != null) { 30 | val.$caseSensitive = castBoolean(val.$caseSensitive, 31 | path + '.$caseSensitive'); 32 | } 33 | if (val.$diacriticSensitive != null) { 34 | val.$diacriticSensitive = castBoolean(val.$diacriticSensitive, 35 | path + '.$diacriticSensitive'); 36 | } 37 | 38 | return val; 39 | }; 40 | -------------------------------------------------------------------------------- /test/helpers/get.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const get = require('../../lib/helpers/get'); 5 | 6 | describe('get', function() { 7 | it('gets dotted properties', function() { 8 | const obj = { a: { b: { c: 42 } } }; 9 | assert.strictEqual(get(obj, 'a.b.c', 43), 42); 10 | }); 11 | 12 | it('returns default on undefined', function() { 13 | const obj = { a: { b: { c: void 0 } } }; 14 | assert.strictEqual(get(obj, 'a.b.c', 42), 42); 15 | }); 16 | 17 | it('returns default on bottom null', function() { 18 | const obj = { a: { b: { c: null } } }; 19 | assert.strictEqual(get(obj, 'a.b.c', 42), 42); 20 | }); 21 | 22 | it('returns default on top-level null', function() { 23 | const obj = { a: null }; 24 | assert.strictEqual(get(obj, 'a.b.c', 42), 42); 25 | }); 26 | 27 | it('works with maps', function() { 28 | const obj = { a: new Map([['b', { c: 42 }]]) }; 29 | assert.strictEqual(get(obj, 'a.b.c', 43), 42); 30 | }); 31 | 32 | it('works with dotted at top level', function() { 33 | const obj = { 'a.b': 42 }; 34 | assert.strictEqual(get(obj, 'a.b', 43), 42); 35 | }); 36 | 37 | it('works with dotted nested', function() { 38 | const obj = { a: { 'b.c': 42 } }; 39 | assert.strictEqual(get(obj, 'a.b.c', 43), 42); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/helpers/schema.getPath.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Schema = require('../../lib/schema'); 4 | const assert = require('assert'); 5 | const getPath = require('../../lib/helpers/schema/getPath'); 6 | 7 | describe('getPath()', function() { 8 | it('works', function() { 9 | const schema = new Schema({ 10 | docArr: [{ el: String }] 11 | }); 12 | 13 | assert.equal(getPath(schema, 'docArr.el').constructor.name, 'SchemaString'); 14 | assert.equal(getPath(schema, 'docArr.0.el').constructor.name, 'SchemaString'); 15 | }); 16 | 17 | it('nested arrays', function() { 18 | const schema = new Schema({ 19 | nested: [{ docs: [{ el: String }] }] 20 | }); 21 | 22 | assert.equal(getPath(schema, 'nested.docs.el').constructor.name, 'SchemaString'); 23 | assert.equal(getPath(schema, 'nested.0.docs.el').constructor.name, 'SchemaString'); 24 | }); 25 | 26 | it('handles getting paths underneath Mixed array (gh-15653)', function() { 27 | const schema = new Schema({ 28 | any: [Schema.Types.Mixed] 29 | }); 30 | assert.strictEqual(getPath(schema, 'any.0.foo').constructor.name, 'SchemaMixed'); 31 | assert.strictEqual(getPath(schema, 'any.0.foo.bar.baz').constructor.name, 'SchemaMixed'); 32 | 33 | assert.strictEqual(getPath(schema, 'any.0').constructor.name, 'SchemaMixed'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /.github/workflows/tsd.yml: -------------------------------------------------------------------------------- 1 | name: Typescript Types 2 | on: 3 | pull_request: 4 | paths: 5 | - '.github/workflows/tsd.yml' 6 | - 'package.json' 7 | - 'types/**' 8 | - 'test/types/**' 9 | push: 10 | paths: 11 | - '.github/workflows/tsd.yml' 12 | - 'package.json' 13 | - 'types/**' 14 | - 'test/types/**' 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | lint: 20 | runs-on: ubuntu-latest 21 | name: Lint TS-Files 22 | steps: 23 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 24 | 25 | - name: Setup node 26 | uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 27 | with: 28 | node-version: 22 29 | 30 | - run: npm install 31 | 32 | - name: Lint TS-Files 33 | run: npm run lint-ts 34 | 35 | test-ts-types: 36 | needs: 37 | - lint 38 | runs-on: ubuntu-latest 39 | name: Test Typescript Types 40 | steps: 41 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 42 | 43 | - name: Setup node 44 | uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 45 | with: 46 | node-version: 22 47 | 48 | - run: npm install 49 | 50 | - name: Typings 51 | run: npm run test-tsd 52 | -------------------------------------------------------------------------------- /types/helpers.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'mongoose' { 2 | import mongodb = require('mongodb'); 3 | 4 | /** 5 | * Mongoose uses this function to get the current time when setting 6 | * [timestamps](/docs/guide.html#timestamps). You may stub out this function 7 | * using a tool like [Sinon](https://www.npmjs.com/package/sinon) for testing. 8 | */ 9 | function now(): NativeDate; 10 | 11 | /** 12 | * Tells `sanitizeFilter()` to skip the given object when filtering out potential query selector injection attacks. 13 | * Use this method when you have a known query selector that you want to use. 14 | */ 15 | function trusted(obj: T): T; 16 | 17 | /** 18 | * Returns true if the given value is a Mongoose ObjectId (using `instanceof`) or if the 19 | * given value is a 24 character hex string, which is the most commonly used string representation 20 | * of an ObjectId. 21 | */ 22 | function isObjectIdOrHexString(v: mongodb.ObjectId): true; 23 | function isObjectIdOrHexString(v: mongodb.ObjectId | string): boolean; 24 | function isObjectIdOrHexString(v: any): false; 25 | 26 | /** 27 | * Returns true if Mongoose can cast the given value to an ObjectId, or 28 | * false otherwise. 29 | */ 30 | function isValidObjectId(v: mongodb.ObjectId | Types.ObjectId): true; 31 | function isValidObjectId(v: any): boolean; 32 | } 33 | -------------------------------------------------------------------------------- /lib/helpers/schema/getPath.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const numberRE = /^\d+$/; 4 | 5 | /** 6 | * Behaves like `Schema#path()`, except for it also digs into arrays without 7 | * needing to put `.0.`, so `getPath(schema, 'docArr.elProp')` works. 8 | * @api private 9 | */ 10 | 11 | module.exports = function getPath(schema, path, discriminatorValueMap) { 12 | let schematype = schema.path(path); 13 | if (schematype != null) { 14 | return schematype; 15 | } 16 | const pieces = path.split('.'); 17 | let cur = ''; 18 | let isArray = false; 19 | 20 | for (const piece of pieces) { 21 | if (isArray && numberRE.test(piece)) { 22 | continue; 23 | } 24 | cur = cur.length === 0 ? piece : cur + '.' + piece; 25 | 26 | schematype = schema.path(cur); 27 | if (schematype?.schema) { 28 | schema = schematype.schema; 29 | if (!isArray && schematype.$isMongooseDocumentArray) { 30 | isArray = true; 31 | } 32 | if (discriminatorValueMap && discriminatorValueMap[cur]) { 33 | schema = schema.discriminators[discriminatorValueMap[cur]] ?? schema; 34 | } 35 | cur = ''; 36 | } else if (schematype?.instance === 'Mixed') { 37 | // If we found a mixed path, no point in digging further, the end result is always Mixed 38 | break; 39 | } 40 | } 41 | 42 | return schematype; 43 | }; 44 | -------------------------------------------------------------------------------- /test/types.embeddeddocument.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | const start = require('./common'); 8 | 9 | const assert = require('assert'); 10 | 11 | const mongoose = start.mongoose; 12 | const Schema = mongoose.Schema; 13 | 14 | /** 15 | * Test. 16 | */ 17 | 18 | describe('types.embeddeddocument', function() { 19 | let GrandChildSchema; 20 | let ChildSchema; 21 | let ParentSchema; 22 | 23 | before(function() { 24 | GrandChildSchema = new Schema({ 25 | name: String 26 | }); 27 | 28 | ChildSchema = new Schema({ 29 | name: String, 30 | children: [GrandChildSchema] 31 | }); 32 | 33 | ParentSchema = new Schema({ 34 | name: String, 35 | child: ChildSchema 36 | }); 37 | 38 | mongoose.model('Parent-3589-Embedded', ParentSchema); 39 | }); 40 | 41 | it('returns a proper ownerDocument (gh-3589)', function(done) { 42 | const Parent = mongoose.model('Parent-3589-Embedded'); 43 | const p = new Parent({ 44 | name: 'Parent Parentson', 45 | child: { 46 | name: 'Child Parentson', 47 | children: [ 48 | { 49 | name: 'GrandChild Parentson' 50 | } 51 | ] 52 | } 53 | }); 54 | 55 | assert.equal(p._id, p.child.children[0].ownerDocument()._id); 56 | done(); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /lib/helpers/document/cleanModifiedSubpaths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * ignore 5 | */ 6 | 7 | module.exports = function cleanModifiedSubpaths(doc, path, options) { 8 | options = options || {}; 9 | const skipDocArrays = options.skipDocArrays; 10 | 11 | let deleted = 0; 12 | if (!doc) { 13 | return deleted; 14 | } 15 | 16 | for (const modifiedPath of Object.keys(doc.$__.activePaths.getStatePaths('modify'))) { 17 | if (skipDocArrays) { 18 | const schemaType = doc.$__schema.path(modifiedPath); 19 | if (schemaType && schemaType.$isMongooseDocumentArray) { 20 | continue; 21 | } 22 | } 23 | if (modifiedPath.startsWith(path + '.')) { 24 | doc.$__.activePaths.clearPath(modifiedPath); 25 | ++deleted; 26 | 27 | if (doc.$isSubdocument) { 28 | cleanParent(doc, modifiedPath); 29 | } 30 | } 31 | } 32 | return deleted; 33 | }; 34 | 35 | function cleanParent(doc, path, seen = new Set()) { 36 | if (seen.has(doc)) { 37 | throw new Error('Infinite subdocument loop: subdoc with _id ' + doc._id + ' is a parent of itself'); 38 | } 39 | const parent = doc.$parent(); 40 | const newPath = doc.$__pathRelativeToParent(void 0, false) + '.' + path; 41 | parent.$__.activePaths.clearPath(newPath); 42 | if (parent.$isSubdocument) { 43 | cleanParent(parent, newPath, seen); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/cast/bigint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Long } = require('mongodb/lib/bson'); 4 | 5 | /** 6 | * Given a value, cast it to a BigInt, or throw an `Error` if the value 7 | * cannot be casted. `null` and `undefined` are considered valid. 8 | * 9 | * @param {Any} value 10 | * @return {Number} 11 | * @throws {Error} if `value` is not one of the allowed values 12 | * @api private 13 | */ 14 | 15 | const MAX_BIGINT = 9223372036854775807n; 16 | const MIN_BIGINT = -9223372036854775808n; 17 | const ERROR_MESSAGE = `Mongoose only supports BigInts between ${MIN_BIGINT} and ${MAX_BIGINT} because MongoDB does not support arbitrary precision integers`; 18 | 19 | module.exports = function castBigInt(val) { 20 | if (val == null) { 21 | return val; 22 | } 23 | if (val === '') { 24 | return null; 25 | } 26 | if (typeof val === 'bigint') { 27 | if (val > MAX_BIGINT || val < MIN_BIGINT) { 28 | throw new Error(ERROR_MESSAGE); 29 | } 30 | return val; 31 | } 32 | 33 | if (val instanceof Long) { 34 | return val.toBigInt(); 35 | } 36 | 37 | if (typeof val === 'string' || typeof val === 'number') { 38 | val = BigInt(val); 39 | if (val > MAX_BIGINT || val < MIN_BIGINT) { 40 | throw new Error(ERROR_MESSAGE); 41 | } 42 | return val; 43 | } 44 | 45 | throw new Error(`Cannot convert value to BigInt: "${val}"`); 46 | }; 47 | -------------------------------------------------------------------------------- /docs/images/pencil.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * ignore 5 | */ 6 | 7 | const queryOperations = Object.freeze([ 8 | // Read 9 | 'countDocuments', 10 | 'distinct', 11 | 'estimatedDocumentCount', 12 | 'find', 13 | 'findOne', 14 | // Update 15 | 'findOneAndReplace', 16 | 'findOneAndUpdate', 17 | 'replaceOne', 18 | 'updateMany', 19 | 'updateOne', 20 | // Delete 21 | 'deleteMany', 22 | 'deleteOne', 23 | 'findOneAndDelete' 24 | ]); 25 | 26 | exports.queryOperations = queryOperations; 27 | 28 | /*! 29 | * ignore 30 | */ 31 | 32 | const queryMiddlewareFunctions = queryOperations.concat([ 33 | 'validate' 34 | ]); 35 | 36 | exports.queryMiddlewareFunctions = queryMiddlewareFunctions; 37 | 38 | /*! 39 | * ignore 40 | */ 41 | 42 | const aggregateMiddlewareFunctions = [ 43 | 'aggregate' 44 | ]; 45 | 46 | exports.aggregateMiddlewareFunctions = aggregateMiddlewareFunctions; 47 | 48 | /*! 49 | * ignore 50 | */ 51 | 52 | const modelMiddlewareFunctions = [ 53 | 'bulkWrite', 54 | 'createCollection', 55 | 'insertMany' 56 | ]; 57 | 58 | exports.modelMiddlewareFunctions = modelMiddlewareFunctions; 59 | 60 | /*! 61 | * ignore 62 | */ 63 | 64 | const documentMiddlewareFunctions = [ 65 | 'validate', 66 | 'save', 67 | 'remove', 68 | 'updateOne', 69 | 'deleteOne', 70 | 'init' 71 | ]; 72 | 73 | exports.documentMiddlewareFunctions = documentMiddlewareFunctions; 74 | -------------------------------------------------------------------------------- /lib/helpers/query/castFilterPath.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isOperator = require('./isOperator'); 4 | 5 | module.exports = function castFilterPath(ctx, schematype, val) { 6 | const any$conditionals = Object.keys(val).some(isOperator); 7 | 8 | if (!any$conditionals) { 9 | return schematype.castForQuery( 10 | null, 11 | val, 12 | ctx 13 | ); 14 | } 15 | 16 | const ks = Object.keys(val); 17 | 18 | let k = ks.length; 19 | 20 | while (k--) { 21 | const $cond = ks[k]; 22 | const nested = val[$cond]; 23 | 24 | if ($cond === '$not') { 25 | if (nested && schematype && !schematype.embeddedSchemaType && !schematype.Constructor) { 26 | const _keys = Object.keys(nested); 27 | if (_keys.length && isOperator(_keys[0])) { 28 | for (const key of Object.keys(nested)) { 29 | nested[key] = schematype.castForQuery( 30 | key, 31 | nested[key], 32 | ctx 33 | ); 34 | } 35 | } else { 36 | val[$cond] = schematype.castForQuery( 37 | $cond, 38 | nested, 39 | ctx 40 | ); 41 | } 42 | continue; 43 | } 44 | } else { 45 | val[$cond] = schematype.castForQuery( 46 | $cond, 47 | nested, 48 | ctx 49 | ); 50 | } 51 | } 52 | 53 | return val; 54 | }; 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.swp 3 | *.sw* 4 | node_modules/ 5 | *.orig 6 | benchmarks/benchmarks 7 | benchmarks/benchmarks2 8 | benchmarks/benchmarks3 9 | benchmarks/v8.log 10 | benchmarks/typescript/**/*.js 11 | .DS_Store 12 | docs/*.json 13 | docs/source/_docs 14 | tags 15 | test/triage/*.js 16 | /.idea 17 | /inspections 18 | bin/mongoose.min.js 19 | coverage 20 | npm-debug.log 21 | data/ 22 | .nyc_output/ 23 | .env 24 | 25 | tools/31* 26 | 27 | # Visual Studio 28 | # ========= 29 | *.suo 30 | *.ntvs* 31 | *.njsproj 32 | *.sln 33 | 34 | # Visual Studio Code 35 | .vscode 36 | 37 | *.key 38 | 39 | # Webpack output 40 | dist 41 | test/files/main.js 42 | 43 | package-lock.json 44 | 45 | .config.js 46 | 47 | # Compiled docs 48 | docs/*.html 49 | docs/tutorials/*.html 50 | docs/typescript/*.html 51 | docs/api/*.html 52 | # the below excludes things like "0test.x" too, but gitignore does not have something like js regex "[0-9]+", so this is the best for future versions 53 | docs/[0-9]*.x/ 54 | index.html 55 | 56 | # Local Netlify folder 57 | .netlify 58 | 59 | # yarn package-lock 60 | yarn.lock 61 | 62 | # deno lock 63 | deno.lock 64 | 65 | # npm pack output 66 | mongoose.tgz 67 | mongoose-*.tgz 68 | 69 | examples/ecommerce-netlify-functions/.netlify/state.json 70 | 71 | notes.md 72 | list.out 73 | 74 | data 75 | fle-cluster-config.json 76 | 77 | # @ark/attest (type testing) 78 | .attest 79 | .worktrees/ 80 | --------------------------------------------------------------------------------