diff --git a/docs/guide.md b/docs/guide.md index 1770e7890e8..6b0eafe5c9c 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -1158,40 +1158,37 @@ const schema = Schema({ }); ``` -

option: useNestedStrict

+

option: pluginTags

-Write operations like `update()`, `updateOne()`, `updateMany()`, -and `findOneAndUpdate()` only check the top-level -schema's strict mode setting. +Mongoose supports defining global plugins, plugins that apply to all schemas. ```javascript -const childSchema = new Schema({}, { strict: false }); -const parentSchema = new Schema({ child: childSchema }, { strict: 'throw' }); -const Parent = mongoose.model('Parent', parentSchema); -Parent.update({}, { 'child.name': 'Luke Skywalker' }, (error) => { - // Error because parentSchema has `strict: throw`, even though - // `childSchema` has `strict: false` +// Add a `meta` property to all schemas +mongoose.plugin(function myPlugin(schema) { + schema.add({ meta: {} }); }); +``` + +Sometimes, you may only want to apply a given plugin to some schemas. +In that case, you can add `pluginTags` to a schema: -const update = { 'child.name': 'Luke Skywalker' }; -const opts = { strict: false }; -Parent.update({}, update, opts, function(error) { - // This works because passing `strict: false` to `update()` overwrites - // the parent schema. +```javascript +const schema1 = new Schema({ + name: String +}, { pluginTags: ['useMetaPlugin'] }); + +const schema2 = new Schema({ + name: String }); ``` -If you set `useNestedStrict` to true, mongoose will use the child schema's -`strict` option for casting updates. +If you call `plugin()` with a `tags` option, Mongoose will only apply that plugin to schemas that have a matching entry in `pluginTags`. ```javascript -const childSchema = new Schema({}, { strict: false }); -const parentSchema = new Schema({ child: childSchema }, - { strict: 'throw', useNestedStrict: true }); -const Parent = mongoose.model('Parent', parentSchema); -Parent.update({}, { 'child.name': 'Luke Skywalker' }, error => { - // Works! -}); +// Add a `meta` property to all schemas +mongoose.plugin(function myPlugin(schema) { + schema.add({ meta: {} }); +}, { tags: ['useMetaPlugin'] }); ```

diff --git a/index.d.ts b/index.d.ts index fafeab995ed..93297e45ab9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1505,6 +1505,11 @@ declare module 'mongoose' { * optimistic concurrency. */ optimisticConcurrency?: boolean; + /** + * If `plugin()` called with tags, Mongoose will only apply plugins to schemas that have + * a matching tag in `pluginTags` + */ + pluginTags?: string[]; /** * Allows setting query#read options at the schema level, providing us a way to apply default ReadPreferences * to all queries derived from a model. diff --git a/lib/helpers/schema/applyPlugins.js b/lib/helpers/schema/applyPlugins.js index f1daf4012e8..3d91cfd2ca7 100644 --- a/lib/helpers/schema/applyPlugins.js +++ b/lib/helpers/schema/applyPlugins.js @@ -7,7 +7,18 @@ module.exports = function applyPlugins(schema, plugins, options, cacheKey) { schema[cacheKey] = true; if (!options || !options.skipTopLevel) { + let pluginTags = null; for (const plugin of plugins) { + const tags = plugin[1] == null ? null : plugin[1].tags; + if (!Array.isArray(tags)) { + schema.plugin(plugin[0], plugin[1]); + continue; + } + + pluginTags = pluginTags || new Set(schema.options.pluginTags || []); + if (!tags.find(tag => pluginTags.has(tag))) { + continue; + } schema.plugin(plugin[0], plugin[1]); } } diff --git a/lib/schema.js b/lib/schema.js index 060209ff383..233ec26f193 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -73,6 +73,7 @@ let id = 0; * - [selectPopulatedPaths](/docs/guide.html#selectPopulatedPaths): boolean - defaults to `true` * - [skipVersioning](/docs/guide.html#skipVersioning): object - paths to exclude from versioning * - [timestamps](/docs/guide.html#timestamps): object or boolean - defaults to `false`. If true, Mongoose adds `createdAt` and `updatedAt` properties to your schema and manages those properties for you. + * - [pluginTags](/docs/guide.html#pluginTags): array of strings - defaults to `undefined`. If set and plugin called with `tags` option, will only apply that plugin to schemas with a matching tag. * * ####Options for Nested Schemas: * - `excludeIndexes`: bool - defaults to `false`. If `true`, skip building indexes on this schema's paths. diff --git a/test/index.test.js b/test/index.test.js index 26e4b48c085..3d416bef1ef 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -319,6 +319,41 @@ describe('mongoose module:', function() { await mong.disconnect(); }); + it('declaring global plugins with tags (gh-9780)', async function() { + const mong = new Mongoose(); + const schema1 = new Schema({}, { pluginTags: ['tag1'] }); + const schema2 = new Schema({}, { pluginTags: ['tag2'] }); + const schema3 = new Schema({}); + + mong.plugin(function(s) { + s.add({ prop1: String }); + }, { tags: ['tag1'] }); + + mong.plugin(function(s) { + s.add({ prop2: String }); + }, { tags: ['tag1', 'tag2'] }); + + mong.plugin(function(s) { + s.add({ prop3: String }); + }); + + const Test1 = mong.model('Test1', schema1); + const Test2 = mong.model('Test2', schema2); + const Test3 = mong.model('Test3', schema3); + + assert.ok(Test1.schema.path('prop1')); + assert.ok(Test1.schema.path('prop2')); + assert.ok(Test1.schema.path('prop3')); + + assert.ok(!Test2.schema.path('prop1')); + assert.ok(Test2.schema.path('prop2')); + assert.ok(Test2.schema.path('prop3')); + + assert.ok(!Test3.schema.path('prop1')); + assert.ok(!Test3.schema.path('prop2')); + assert.ok(Test3.schema.path('prop3')); + }); + it('global plugins on nested schemas underneath embedded discriminators (gh-7370)', function() { const m = new Mongoose();