diff --git a/.eslintrc.js b/.eslintrc.js index 1bf13a95d2c..b4d7d1652d9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -56,29 +56,15 @@ module.exports = { '@typescript-eslint/ban-types': 'off', '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/indent': [ - 'warn', - 2, - { - SwitchCase: 1, - ignoredNodes: ['TSTypeParameterInstantiation'] - } - ], '@typescript-eslint/prefer-optional-chain': 'error', - '@typescript-eslint/brace-style': 'error', '@typescript-eslint/no-dupe-class-members': 'error', '@typescript-eslint/no-redeclare': 'error', - '@typescript-eslint/type-annotation-spacing': 'error', - '@typescript-eslint/object-curly-spacing': [ - 'error', - 'always' - ], - '@typescript-eslint/semi': 'error', - '@typescript-eslint/space-before-function-paren': [ - 'error', - 'never' - ], - '@typescript-eslint/space-infix-ops': 'off' + '@typescript-eslint/space-infix-ops': 'off', + '@typescript-eslint/no-require-imports': 'off', + '@typescript-eslint/no-empty-object-type': 'off', + '@typescript-eslint/no-wrapper-object-types': 'off', + '@typescript-eslint/no-unused-expressions': 'off', + '@typescript-eslint/no-unsafe-function-type': 'off' } }, { diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 052b34b1075..5afe4965aeb 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -22,13 +22,13 @@ jobs: runs-on: ubuntu-20.04 name: Benchmark TypeScript Types steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - name: Setup node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: - node-version: 16 + node-version: 22 - run: npm install diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index efa77ce7fa8..d0cd8cca68d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 0f9f032ac21..134fbe5ed5e 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -28,12 +28,12 @@ jobs: runs-on: ubuntu-latest name: Lint Markdown files steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: - node-version: 16 + node-version: 22 - run: npm install @@ -48,13 +48,13 @@ jobs: runs-on: ubuntu-20.04 name: Test Generating Docs steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - run: git fetch --depth=1 --tags # download all tags for documentation - name: Setup node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: - node-version: 16 + node-version: 22 - run: npm install - name: Setup MongoDB diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7bd4ceea192..f17b792e2b4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,12 +22,12 @@ jobs: runs-on: ubuntu-latest name: Lint JS-Files steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: - node-version: 18 + node-version: 22 - run: npm install @@ -39,9 +39,9 @@ jobs: strategy: fail-fast: false matrix: - node: [16, 18, 20] + node: [16, 18, 20, 22] os: [ubuntu-20.04, ubuntu-22.04] - mongodb: [4.4.29, 5.0.26, 6.0.15, 7.0.12] + mongodb: [4.4.29, 5.0.26, 6.0.15, 7.0.12, 8.0.0] include: - os: ubuntu-20.04 # customize on which matrix the coverage will be collected on mongodb: 5.0.26 @@ -58,10 +58,10 @@ jobs: MONGOMS_PREFER_GLOBAL_PATH: 1 FORCE_COLOR: true steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: node-version: ${{ matrix.node }} @@ -94,11 +94,11 @@ jobs: MONGOMS_PREFER_GLOBAL_PATH: 1 FORCE_COLOR: true steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: - node-version: 16 + node-version: 22 - name: Load MongoDB binary cache id: cache-mongodb-binaries uses: actions/cache@v4 @@ -106,7 +106,7 @@ jobs: path: ~/.cache/mongodb-binaries key: deno-${{ env.MONGOMS_VERSION }} - name: Setup Deno - uses: denoland/setup-deno@v1 + uses: denoland/setup-deno@v2 with: deno-version: v1.37.x - run: deno --version @@ -122,11 +122,11 @@ jobs: env: FORCE_COLOR: true steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: - node-version: 16 + node-version: 22 - run: npm install - name: Test run: npm run test-rs @@ -139,6 +139,6 @@ jobs: contents: read steps: - name: Check out repo - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Dependency review uses: actions/dependency-review-action@v4 diff --git a/.github/workflows/tidelift-alignment.yml b/.github/workflows/tidelift-alignment.yml index f79e17a20bf..552493a7cbc 100644 --- a/.github/workflows/tidelift-alignment.yml +++ b/.github/workflows/tidelift-alignment.yml @@ -15,11 +15,11 @@ jobs: if: github.repository == 'Automattic/mongoose' steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: - node-version: 16 + node-version: 22 - name: Alignment uses: tidelift/alignment-action@8d7700fe795fc01179c1f9fa05b72a089873027d # main env: diff --git a/.github/workflows/tsd.yml b/.github/workflows/tsd.yml index e5102805477..672bd36229f 100644 --- a/.github/workflows/tsd.yml +++ b/.github/workflows/tsd.yml @@ -20,12 +20,12 @@ jobs: runs-on: ubuntu-latest name: Lint TS-Files steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: - node-version: 18 + node-version: 22 - run: npm install @@ -38,12 +38,12 @@ jobs: runs-on: ubuntu-latest name: Test Typescript Types steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: - node-version: 16 + node-version: 22 - run: npm install diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ef63a29ffc..4db645d514e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,111 @@ +8.8.3 / 2024-11-26 +================== + * fix: disallow using $where in match + * perf: cache results from getAllSubdocs() on saveOptions, only loop through known subdoc properties #15055 #15029 + * fix(model+query): support overwriteDiscriminatorKey for bulkWrite updateOne and updateMany, allow inferring discriminator key from update #15046 #15040 + +8.8.2 / 2024-11-18 +================== + * fix(model): handle array filters when casting bulkWrite #15036 #14978 + * fix(model): make diffIndexes() avoid trying to drop default timeseries collection index #15035 #14984 + * fix: save execution stack in query as string #15039 [durran](https://github.com/durran) + * types(cursor): correct asyncIterator and asyncDispose for TypeScript with lib: 'esnext' #15038 + * docs(migrating_to_8): add note about removing findByIdAndRemove #15024 [dragontaek-lee](https://github.com/dragontaek-lee) + +8.8.1 / 2024-11-08 +================== + * perf: make a few micro-optimizations to help speed up findOne() #15022 #14906 + * fix: apply embedded discriminators to subdoc schemas before compiling top level model so middleware applies correctly #15001 #14961 + * fix(query): add overwriteImmutable option to allow updating immutable properties without disabling strict mode #15000 #8619 + +8.8.0 / 2024-10-31 +================== + * feat: upgrade mongodb -> ~6.10 #14991 #14877 + * feat(query): add schemaLevelProjections option to query to disable schema-level select: false #14986 #11474 + * feat: allow defining virtuals on arrays, not just array elements #14955 #2326 + * feat(model): add applyTimestamps() function to apply all schema timestamps, including subdocuments, to a given POJO #14943 #14698 + * feat(model): add hideIndexes option to syncIndexes() and cleanIndexes() #14987 #14868 + * fix(query): make sanitizeFilter disable implicit $in #14985 #14657 + * fix(model): avoid unhandled error if createIndex() throws a sync error #14995 + * fix(model): avoid throwing TypeError if bulkSave()'s bulkWrite() fails with a non-BulkWriteError #14993 + * types: added toJSON:flattenObjectIds effect #14989 + * types: add `__v` to lean() result type and ModifyResult #14990 #12959 + * types: use globalThis instead of global for NativeDate #14992 #14988 + * docs(change-streams): fix markdown syntax highlighting for script output example #14994 + + +8.7.3 / 2024-10-25 +================== + * fix(cursor): close underlying query cursor when calling destroy() #14982 #14966 + * types: add JSONSerialized helper that can convert HydratedDocument to JSON output type #14981 #14451 + * types(model): convert InsertManyResult to interface and remove unnecessary insertedIds override #14977 + * types(connection): add missing sanitizeFilter option #14975 + * types: improve goto definition for inferred schema definitions #14968 [forivall](https://github.com/forivall) + * docs(migration-guide-v7): correct link to the section "Id Setter" #14973 [rb-ntnx](https://github.com/rb-ntnx) + +8.7.2 / 2024-10-17 +================== + * fix(document): recursively clear modified subpaths when setting deeply nested subdoc to null #14963 #14952 + * fix(populate): handle array of ids with parent refPath #14965 + * types: make Buffers into mongodb.Binary in lean result type to match runtime behavior #14967 + * types: correct schema type inference when using nested typeKey like type: { type: String } #14956 #14950 + * types: re-export DeleteResult and UpdateResult from MongoDB Node.js driver #14947 #14946 + * docs(documents): add section on setting deeply nested properties, including warning about nullish coalescing assignment #14972 + * docs(model): add more info on acknowledged: false, specifically that Mongoose may return that if the update was empty #14957 + +8.7.1 / 2024-10-09 +================== + * fix: set flattenObjectIds to false when calling toObject() for internal purposes #14938 + * fix: add mongodb 8 to test matrix #14937 + * fix: handle buffers stored in MongoDB as EJSON representation with { $binary } #14932 + * docs: indicate that Mongoose 8.7 is required for full MongoDB 8 support #14937 + +8.7.0 / 2024-09-27 +================== + * feat(model): add Model.applyVirtuals() to apply virtuals to a POJO #14905 #14818 + * feat: upgrade mongodb -> 6.9.0 #14914 + * feat(query): cast $rename to string #14887 #3027 + * feat(SchemaType): add getEmbeddedSchemaType() method to SchemaTypes #14880 #8389 + * fix(model): throw MongooseBulkSaveIncompleteError if bulkSave() didn't completely succeed #14884 #14763 + * fix(connection): avoid returning readyState = connected if connection state is stale #14812 #14727 + * fix: depopulate if push() or addToSet() with an ObjectId on a populated array #14883 #1635 + * types: make __v a number, only set __v on top-level documents #14892 + +8.6.4 / 2024-09-26 +================== + * fix(document): avoid massive perf degradation when saving new doc with 10 level deep subdocs #14910 #14897 + * fix(model): skip applying static hooks by default if static name conflicts with aggregate middleware #14904 [dragontaek-lee](https://github.com/dragontaek-lee) + * fix(model): filter applying static hooks by default if static name conflicts with mongoose middleware #14908 [dragontaek-lee](https://github.com/dragontaek-lee) + +7.8.2 / 2024-09-25 +================== + * fix(projection): avoid setting projection to unknown exclusive/inclusive if elemMatch on a Date, ObjectId, etc. #14894 #14893 + +8.6.3 / 2024-09-17 +================== + * fix: make getters convert uuid to string when calling toObject() and toJSON() #14890 #14869 + * fix: fix missing Aggregate re-exports for ESM #14886 [wongsean](https://github.com/wongsean) + * types(document): add generic param to depopulate() to allow updating properties #14891 #14876 + +6.13.2 / 2024-09-12 +=================== + * fix(document): make set() respect merge option on deeply nested objects #14870 #14878 + +8.6.2 / 2024-09-11 +================== + * fix: make set merge deeply nested objects #14870 #14861 [ianHeydoc](https://github.com/ianHeydoc) + * types: allow arbitrary keys in query filters again (revert #14764) #14874 #14863 #14862 #14842 + * types: make SchemaType static setters property accessible in TypeScript #14881 #14879 + * type(inferrawdoctype): infer Date types as JS dates rather than Mongoose SchemaType Date #14882 #14839 + +8.6.1 / 2024-09-03 +================== + * fix(document): avoid unnecessary clone() in applyGetters() that was preventing getters from running on 3-level deep subdocuments #14844 #14840 #14835 + * fix(model): throw error if bulkSave() did not insert or update any documents #14837 #14763 + * fix(cursor): throw error in ChangeStream constructor if changeStreamThunk() throws a sync error #14846 + * types(query): add $expr to RootQuerySelector #14845 + * docs: update populate.md to fix missing match: { } #14847 [makhoulshbeeb](https://github.com/makhoulshbeeb) + 8.6.0 / 2024-08-28 ================== * feat: upgrade mongodb -> 6.8.0, handle throwing error on closed cursor in Mongoose with `MongooseError` instead of `MongoCursorExhaustedError` #14813 @@ -93,6 +201,7 @@ ================== * feat(model): add throwOnValidationError option for opting into getting MongooseBulkWriteError if all valid operations succeed in bulkWrite() and insertMany() #14599 #14587 #14572 #13410 +<<<<<<< HEAD 8.4.3 / 2024-06-17 ================== * fix: remove 0x flamegraph files from release @@ -105,6 +214,11 @@ * fix(connection): fix up some inconsistencies in operation-end event and add to docs #14659 #14648 * types: avoid inferring Boolean, Buffer, ObjectId as Date in schema definitions under certain circumstances #14667 #14630 * docs: add note about parallelism in transations #14647 [fiws](https://github.com/fiws) +======= +6.13.1 / 2024-09-06 +=================== + * fix: remove empty $and, $or, $not that were made empty by scrict mode #14749 #13086 [0x0a0d](https://github.com/0x0a0d) +>>>>>>> 7.x 6.13.0 / 2024-06-06 =================== @@ -608,7 +722,7 @@ ================== * perf: speed up mapOfSubdocs benchmark by 4x by avoiding unnecessary O(n^2) loop in getPathsToValidate() #13614 * feat: upgrade to MongoDB Node.js driver 5.7.0 #13591 - * feat: add `id` setter which allows modifying `_id` by setting `id` (Note this change was reverted in Mongoose 8) #13517 + * BREAKING CHANGE: add `id` setter which allows modifying `_id` by setting `id` (Note this change was originally shipped as a `feat`, but later reverted in Mongoose 8 due to compatibility issues) #13517 * feat: support generating custom cast error message with a function #13608 #3162 * feat(query): support MongoDB driver's includeResultMetadata option for findOneAndUpdate #13584 #13539 * feat(connection): add Connection.prototype.removeDb() for removing a related connection #13580 #11821 diff --git a/benchmarks/createDeepNestedDocArray.js b/benchmarks/createDeepNestedDocArray.js new file mode 100644 index 00000000000..0f3ac6d4a7b --- /dev/null +++ b/benchmarks/createDeepNestedDocArray.js @@ -0,0 +1,37 @@ +'use strict'; + +const mongoose = require('../'); + +run().catch(err => { + console.error(err); + process.exit(-1); +}); + +async function run() { + await mongoose.connect('mongodb://127.0.0.1:27017/mongoose_benchmark'); + + const levels = 12; + + let schema = new mongoose.Schema({ test: { type: String, required: true } }); + let doc = { test: 'gh-14897' }; + for (let i = 0; i < levels; ++i) { + schema = new mongoose.Schema({ level: Number, subdocs: [schema] }); + doc = { level: (levels - i), subdocs: [{ ...doc }, { ...doc }] }; + } + const Test = mongoose.model('Test', schema); + + if (!process.env.MONGOOSE_BENCHMARK_SKIP_SETUP) { + await Test.deleteMany({}); + } + + const insertStart = Date.now(); + await Test.create(doc); + const insertEnd = Date.now(); + + const results = { + 'create() time ms': +(insertEnd - insertStart).toFixed(2) + }; + + console.log(JSON.stringify(results, null, ' ')); + process.exit(0); +} \ No newline at end of file diff --git a/benchmarks/saveSimple.js b/benchmarks/saveSimple.js new file mode 100644 index 00000000000..0029559cdb9 --- /dev/null +++ b/benchmarks/saveSimple.js @@ -0,0 +1,57 @@ +'use strict'; + +const mongoose = require('../'); + +run().catch(err => { + console.error(err); + process.exit(-1); +}); + +async function run() { + await mongoose.connect('mongodb://127.0.0.1:27017/mongoose_benchmark'); + const FooSchema = new mongoose.Schema({ + prop1: String, + prop2: String, + prop3: String, + prop4: String, + prop5: String, + prop6: String, + prop7: String, + prop8: String, + prop9: String, + prop10: String + }); + const FooModel = mongoose.model('Foo', FooSchema); + + if (!process.env.MONGOOSE_BENCHMARK_SKIP_SETUP) { + await FooModel.deleteMany({}); + } + + const numIterations = 500; + const saveStart = Date.now(); + for (let i = 0; i < numIterations; ++i) { + for (let j = 0; j < 10; ++j) { + const doc = new FooModel({ + prop1: `test ${i}`, + prop2: `test ${i}`, + prop3: `test ${i}`, + prop4: `test ${i}`, + prop5: `test ${i}`, + prop6: `test ${i}`, + prop7: `test ${i}`, + prop8: `test ${i}`, + prop9: `test ${i}`, + prop10: `test ${i}` + }); + await doc.save(); + } + } + const saveEnd = Date.now(); + + const results = { + 'Average save time ms': +((saveEnd - saveStart) / numIterations).toFixed(2) + }; + + console.log(JSON.stringify(results, null, ' ')); + process.exit(0); +} diff --git a/docs/change-streams.md b/docs/change-streams.md index cc6d3d36c94..885279050e2 100644 --- a/docs/change-streams.md +++ b/docs/change-streams.md @@ -21,7 +21,7 @@ await Person.create({ name: 'Axl Rose' }); The above script will print output that looks like: -```no-highlight +```javascript { _id: { _data: '8262408DAC000000012B022C0100296E5A10042890851837DB4792BE6B235E8B85489F46645F6964006462408DAC6F5C42FF5EE087A20004' diff --git a/docs/compatibility.md b/docs/compatibility.md index f183a4f67b1..a343a470131 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -18,6 +18,7 @@ Below are the [semver](http://semver.org/) ranges representing which versions of | MongoDB Server | Mongoose | | :------------: | :-------------------------------------: | +| `8.x` | `^8.7.0` | | `7.x` | `^7.4.0 \| ^8.0.0` | | `6.x` | `^6.5.0 \| ^7.0.0 \| ^8.0.0` | | `5.x` | `^5.13.0` \| `^6.0.0 \| ^7.0.0 \| ^8.0.0`| diff --git a/docs/documents.md b/docs/documents.md index 20764c6dbef..6aca0814253 100644 --- a/docs/documents.md +++ b/docs/documents.md @@ -8,6 +8,7 @@ to documents as stored in MongoDB. Each document is an instance of its
  • Documents vs Models
  • Retrieving
  • Updating Using save()
  • +
  • Setting Nested Properties
  • Updating Using Queries
  • Validating
  • Overwriting
  • @@ -81,6 +82,54 @@ doc.name = 'foo'; await doc.save(); // Throws DocumentNotFoundError ``` +## Setting Nested Properties + +Mongoose documents have a `set()` function that you can use to safely set deeply nested properties. + +```javascript +const schema = new Schema({ + nested: { + subdoc: new Schema({ + name: String + }) + } +}); +const TestModel = mongoose.model('Test', schema); + +const doc = new TestModel(); +doc.set('nested.subdoc.name', 'John Smith'); +doc.nested.subdoc.name; // 'John Smith' +``` + +Mongoose documents also have a `get()` function that lets you safely read deeply nested properties. `get()` lets you avoid having to explicitly check for nullish values, similar to JavaScript's [optional chaining operator `?.`](https://masteringjs.io/tutorials/fundamentals/optional-chaining-array). + +```javascript +const doc2 = new TestModel(); + +doc2.get('nested.subdoc.name'); // undefined +doc2.nested?.subdoc?.name; // undefined + +doc2.set('nested.subdoc.name', 'Will Smith'); +doc2.get('nested.subdoc.name'); // 'Will Smith' +``` + +You can use optional chaining `?.` and nullish coalescing `??` with Mongoose documents. +However, be careful when using [nullish coalescing assignments `??=`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_assignment) to create nested paths with Mongoose documents. + +```javascript +// The following works fine +const doc3 = new TestModel(); +doc3.nested.subdoc ??= {}; +doc3.nested.subdoc.name = 'John Smythe'; + +// The following does **NOT** work. +// Do not use the following pattern with Mongoose documents. +const doc4 = new TestModel(); +(doc4.nested.subdoc ??= {}).name = 'Charlie Smith'; +doc.nested.subdoc; // Empty object +doc.nested.subdoc.name; // undefined. +``` + ## Updating Using Queries {#updating-using-queries} The [`save()`](api/model.html#model_Model-save) function is generally the right diff --git a/docs/middleware.md b/docs/middleware.md index b74452affbd..87381fad2e0 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -31,7 +31,6 @@ In document middleware functions, `this` refers to the document. To access the m * [validate](api/document.html#document_Document-validate) * [save](api/model.html#model_Model-save) -* [remove](api/model.html#model_Model-remove) * [updateOne](api/document.html#document_Document-updateOne) * [deleteOne](api/model.html#model_Model-deleteOne) * [init](api/document.html#document_Document-init) (note: init hooks are [synchronous](#synchronous)) @@ -50,7 +49,6 @@ In query middleware functions, `this` refers to the query. * [findOneAndDelete](api/query.html#query_Query-findOneAndDelete) * [findOneAndReplace](api/query.html#query_Query-findOneAndReplace) * [findOneAndUpdate](api/query.html#query_Query-findOneAndUpdate) -* [remove](api/model.html#model_Model-remove) * [replaceOne](api/query.html#query_Query-replaceOne) * [updateOne](api/query.html#query_Query-updateOne) * [updateMany](api/query.html#query_Query-updateMany) @@ -87,7 +85,6 @@ Here are the possible strings that can be passed to `pre()` * findOneAndUpdate * init * insertMany -* remove * replaceOne * save * update @@ -400,11 +397,11 @@ Mongoose has both query and document hooks for `deleteOne()`. ```javascript schema.pre('deleteOne', function() { console.log('Removing!'); }); -// Does **not** print "Removing!". Document middleware for `remove` is not executed by default +// Does **not** print "Removing!". Document middleware for `deleteOne` is not executed by default await doc.deleteOne(); // Prints "Removing!" -Model.remove(); +await Model.deleteOne(); ``` You can pass options to [`Schema.pre()`](api.html#schema_Schema-pre) @@ -418,8 +415,8 @@ schema.pre('deleteOne', { document: true, query: false }, function() { console.log('Deleting doc!'); }); -// Only query middleware. This will get called when you do `Model.remove()` -// but not `doc.remove()`. +// Only query middleware. This will get called when you do `Model.deleteOne()` +// but not `doc.deleteOne()`. schema.pre('deleteOne', { query: true, document: false }, function() { console.log('Deleting!'); }); diff --git a/docs/migrating_to_7.md b/docs/migrating_to_7.md index ffab6f46e9a..21c3aa377c6 100644 --- a/docs/migrating_to_7.md +++ b/docs/migrating_to_7.md @@ -200,7 +200,7 @@ In Mongoose 7, `ObjectId` is now a [JavaScript class](https://masteringjs.io/tut const oid = new mongoose.Types.ObjectId('0'.repeat(24)); ``` -## `id` Setter +## `id` Setter {#id-setter} Starting in Mongoose 7.4, Mongoose's built-in `id` virtual (which stores the document's `_id` as a string) has a setter which allows modifying the document's `_id` property via `id`. diff --git a/docs/migrating_to_8.md b/docs/migrating_to_8.md index 5914318003e..e2748dcb9cd 100644 --- a/docs/migrating_to_8.md +++ b/docs/migrating_to_8.md @@ -87,6 +87,9 @@ In Mongoose 7, `findOneAndRemove()` was an alias for `findOneAndDelete()` that M Mongoose 8 no longer supports `findOneAndRemove()`. Use `findOneAndDelete()` instead. +Similarly, Mongoose 8 no longer supports `findByIdAndRemove()`, which was an alias for `findByIdAndDelete()`. +Please use `findByIdAndDelete()` instead. + ## Removed `count()` {#removed-count} `Model.count()` and `Query.prototype.count()` were removed in Mongoose 8. Use `Model.countDocuments()` and `Query.prototype.countDocuments()` instead. diff --git a/docs/populate.md b/docs/populate.md index 0a24878d68d..c17710a397a 100644 --- a/docs/populate.md +++ b/docs/populate.md @@ -119,6 +119,33 @@ story.author = author; console.log(story.author.name); // prints "Ian Fleming" ``` +You can also push documents or POJOs onto a populated array, and Mongoose will add those documents if their `ref` matches. + +```javascript +const fan1 = await Person.create({ name: 'Sean' }); +await Story.updateOne({ title: 'Casino Royale' }, { $push: { fans: { $each: [fan1._id] } } }); + +const story = await Story.findOne({ title: 'Casino Royale' }).populate('fans'); +story.fans[0].name; // 'Sean' + +const fan2 = await Person.create({ name: 'George' }); +story.fans.push(fan2); +story.fans[1].name; // 'George' + +story.fans.push({ name: 'Roger' }); +story.fans[2].name; // 'Roger' +``` + +If you push a non-POJO and non-document value, like an ObjectId, Mongoose `>= 8.7.0` will depopulate the entire array. + +```javascript +const fan4 = await Person.create({ name: 'Timothy' }); +story.fans.push(fan4._id); // Push the `_id`, not the full document + +story.fans[0].name; // undefined, `fans[0]` is now an ObjectId +story.fans[0].toString() === fan1._id.toString(); // true +``` + ## Checking Whether a Field is Populated {#checking-populated} You can call the `populated()` function to check whether a field is populated. @@ -246,7 +273,7 @@ the story's `author` will be `null`. ```javascript const story = await Story. findOne({ title: 'Casino Royale' }). - populate({ path: 'author', name: { $ne: 'Ian Fleming' } }). + populate({ path: 'author', match: { name: { $ne: 'Ian Fleming' } } }). exec(); story.author; // `null` ``` diff --git a/docs/subdocs.md b/docs/subdocs.md index 5bd37433435..5fbfae5109a 100644 --- a/docs/subdocs.md +++ b/docs/subdocs.md @@ -38,7 +38,7 @@ doc.child; ```