Skip to content

Instantly share code, notes, and snippets.

@wasnot
Created June 25, 2024 05:15
Show Gist options
  • Save wasnot/4e9fca1f59e68bbe33564697d21023c5 to your computer and use it in GitHub Desktop.
Save wasnot/4e9fca1f59e68bbe33564697d21023c5 to your computer and use it in GitHub Desktop.
mongoose-lib-6.0.15...6.12.9.diff
This file has been truncated, but you can view the full file.
diff --git a/index.d.ts b/index.d.ts
deleted file mode 100644
index 542a20dfcad..00000000000
--- a/index.d.ts
+++ /dev/null
@@ -1,3126 +0,0 @@
-declare module 'mongoose' {
- import events = require('events');
- import mongodb = require('mongodb');
- import mongoose = require('mongoose');
- import stream = require('stream');
-
- export enum ConnectionStates {
- disconnected = 0,
- connected = 1,
- connecting = 2,
- disconnecting = 3,
- uninitialized = 99,
- }
-
- class NativeDate extends global.Date {}
-
- /** The Mongoose Date [SchemaType](/docs/schematypes.html). */
- export type Date = Schema.Types.Date;
-
- /**
- * The Mongoose Decimal128 [SchemaType](/docs/schematypes.html). Used for
- * declaring paths in your schema that should be
- * [128-bit decimal floating points](http://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-decimal.html).
- * Do not use this to create a new Decimal128 instance, use `mongoose.Types.Decimal128`
- * instead.
- */
- export type Decimal128 = Schema.Types.Decimal128;
-
- /**
- * The Mongoose Mixed [SchemaType](/docs/schematypes.html). Used for
- * declaring paths in your schema that Mongoose's change tracking, casting,
- * and validation should ignore.
- */
- export type Mixed = Schema.Types.Mixed;
-
- /**
- * Mongoose constructor. The exports object of the `mongoose` module is an instance of this
- * class. Most apps will only use this one instance.
- */
- export const Mongoose: new (options?: MongooseOptions | null) => typeof mongoose;
-
- /**
- * The Mongoose Number [SchemaType](/docs/schematypes.html). Used for
- * declaring paths in your schema that Mongoose should cast to numbers.
- */
- export type Number = Schema.Types.Number;
-
- /**
- * The Mongoose ObjectId [SchemaType](/docs/schematypes.html). Used for
- * declaring paths in your schema that should be
- * [MongoDB ObjectIds](https://docs.mongodb.com/manual/reference/method/ObjectId/).
- * Do not use this to create a new ObjectId instance, use `mongoose.Types.ObjectId`
- * instead.
- */
- export type ObjectId = Schema.Types.ObjectId;
-
- export let Promise: any;
- export const PromiseProvider: any;
-
- /** The various Mongoose SchemaTypes. */
- export const SchemaTypes: typeof Schema.Types;
-
- /** Expose connection states for user-land */
- export const STATES: typeof ConnectionStates;
-
- /** Opens Mongoose's default connection to MongoDB, see [connections docs](https://mongoosejs.com/docs/connections.html) */
- export function connect(uri: string, options: ConnectOptions, callback: CallbackWithoutResult): void;
- export function connect(uri: string, callback: CallbackWithoutResult): void;
- export function connect(uri: string, options?: ConnectOptions): Promise<Mongoose>;
-
- /** The Mongoose module's default connection. Equivalent to `mongoose.connections[0]`, see [`connections`](#mongoose_Mongoose-connections). */
- export const connection: Connection;
-
- /** An array containing all connections associated with this Mongoose instance. */
- export const connections: Connection[];
-
- /**
- * Can be extended to explicitly type specific models.
- */
- interface Models {
- [modelName: string]: Model<any>
- }
-
- /** An array containing all models associated with this Mongoose instance. */
- export const models: Models;
- /** Creates a Connection instance. */
- export function createConnection(uri: string, options?: ConnectOptions): Connection;
- export function createConnection(): Connection;
- export function createConnection(uri: string, options: ConnectOptions, callback: Callback<Connection>): void;
-
- /**
- * Removes the model named `name` from the default connection, if it exists.
- * You can use this function to clean up any models you created in your tests to
- * prevent OverwriteModelErrors.
- */
- export function deleteModel(name: string | RegExp): typeof mongoose;
-
- export function disconnect(): Promise<void>;
- export function disconnect(cb: CallbackWithoutResult): void;
-
- /** Gets mongoose options */
- export function get<K extends keyof MongooseOptions>(key: K): MongooseOptions[K];
-
- /**
- * Returns true if Mongoose can cast the given value to an ObjectId, or
- * false otherwise.
- */
- export function isValidObjectId(v: any): boolean;
-
- export function model<T>(name: string, schema?: Schema<T, any, any> | Schema<T & Document, any, any>, collection?: string, skipInit?: boolean): Model<T>;
- export function model<T, U, TQueryHelpers = {}>(
- name: string,
- schema?: Schema<T, U, TQueryHelpers>,
- collection?: string,
- skipInit?: boolean
- ): U;
-
- /** Returns an array of model names created on this instance of Mongoose. */
- export function modelNames(): Array<string>;
-
- /** The node-mongodb-native driver Mongoose uses. */
- export const mongo: typeof mongodb;
-
- /**
- * Mongoose uses this function to get the current time when setting
- * [timestamps](/docs/guide.html#timestamps). You may stub out this function
- * using a tool like [Sinon](https://www.npmjs.com/package/sinon) for testing.
- */
- export function now(): NativeDate;
-
- /** Declares a global plugin executed on all Schemas. */
- export function plugin(fn: (schema: Schema, opts?: any) => void, opts?: any): typeof mongoose;
-
- /** Getter/setter around function for pluralizing collection names. */
- export function pluralize(fn?: ((str: string) => string) | null): ((str: string) => string) | null;
-
- /** Sets mongoose options */
- export function set<K extends keyof MongooseOptions>(key: K, value: MongooseOptions[K]): typeof mongoose;
-
- /**
- * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions)
- * for benefits like causal consistency, [retryable writes](https://docs.mongodb.com/manual/core/retryable-writes/),
- * and [transactions](http://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html).
- */
- export function startSession(options?: mongodb.ClientSessionOptions): Promise<mongodb.ClientSession>;
- export function startSession(options: mongodb.ClientSessionOptions, cb: Callback<mongodb.ClientSession>): void;
-
- /** The Mongoose version */
- export const version: string;
-
- export type CastError = Error.CastError;
-
- type Mongoose = typeof mongoose;
-
- interface MongooseOptions {
- /** true by default. Set to false to skip applying global plugins to child schemas */
- applyPluginsToChildSchemas?: boolean;
-
- /**
- * false by default. Set to true to apply global plugins to discriminator schemas.
- * This typically isn't necessary because plugins are applied to the base schema and
- * discriminators copy all middleware, methods, statics, and properties from the base schema.
- */
- applyPluginsToDiscriminators?: boolean;
-
- /**
- * Set to `true` to make Mongoose call` Model.createCollection()` automatically when you
- * create a model with `mongoose.model()` or `conn.model()`. This is useful for testing
- * transactions, change streams, and other features that require the collection to exist.
- */
- autoCreate?: boolean;
-
- /**
- * true by default. Set to false to disable automatic index creation
- * for all models associated with this Mongoose instance.
- */
- autoIndex?: boolean;
-
- /** enable/disable mongoose's buffering mechanism for all connections and models */
- bufferCommands?: boolean;
-
- bufferTimeoutMS?: number;
-
- /** false by default. Set to `true` to `clone()` all schemas before compiling into a model. */
- cloneSchemas?: boolean;
-
- /**
- * If `true`, prints the operations mongoose sends to MongoDB to the console.
- * If a writable stream is passed, it will log to that stream, without colorization.
- * If a callback function is passed, it will receive the collection name, the method
- * name, then all arguments passed to the method. For example, if you wanted to
- * replicate the default logging, you could output from the callback
- * `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`.
- */
- debug?:
- | boolean
- | { color?: boolean; shell?: boolean }
- | stream.Writable
- | ((collectionName: string, methodName: string, ...methodArgs: any[]) => void);
-
- /** If set, attaches [maxTimeMS](https://docs.mongodb.com/manual/reference/operator/meta/maxTimeMS/) to every query */
- maxTimeMS?: number;
-
- /**
- * true by default. Mongoose adds a getter to MongoDB ObjectId's called `_id` that
- * returns `this` for convenience with populate. Set this to false to remove the getter.
- */
- objectIdGetter?: boolean;
-
- /**
- * Set to `true` to default to overwriting models with the same name when calling
- * `mongoose.model()`, as opposed to throwing an `OverwriteModelError`.
- */
- overwriteModels?: boolean;
-
- /**
- * If `false`, changes the default `returnOriginal` option to `findOneAndUpdate()`,
- * `findByIdAndUpdate`, and `findOneAndReplace()` to false. This is equivalent to
- * setting the `new` option to `true` for `findOneAndX()` calls by default. Read our
- * `findOneAndUpdate()` [tutorial](https://mongoosejs.com/docs/tutorials/findoneandupdate.html)
- * for more information.
- */
- returnOriginal?: boolean;
-
- /**
- * false by default. Set to true to enable [update validators](
- * https://mongoosejs.com/docs/validation.html#update-validators
- * ) for all validators by default.
- */
- runValidators?: boolean;
-
- sanitizeFilter?: boolean;
-
- sanitizeProjection?: boolean;
-
- /**
- * true by default. Set to false to opt out of Mongoose adding all fields that you `populate()`
- * to your `select()`. The schema-level option `selectPopulatedPaths` overwrites this one.
- */
- selectPopulatedPaths?: boolean;
-
- setDefaultsOnInsert?: boolean;
-
- /** true by default, may be `false`, `true`, or `'throw'`. Sets the default strict mode for schemas. */
- strict?: boolean | 'throw';
-
- /**
- * false by default, may be `false`, `true`, or `'throw'`. Sets the default
- * [strictQuery](https://mongoosejs.com/docs/guide.html#strictQuery) mode for schemas.
- */
- strictQuery?: boolean | 'throw';
-
- /**
- * `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to
- * `toJSON()`, for determining how Mongoose documents get serialized by `JSON.stringify()`
- */
- toJSON?: ToObjectOptions;
-
- /** `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to `toObject()` */
- toObject?: ToObjectOptions;
- }
-
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
- interface ClientSession extends mongodb.ClientSession { }
-
- interface ConnectOptions extends mongodb.MongoClientOptions {
- /** Set to false to [disable buffering](http://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection. */
- bufferCommands?: boolean;
- /** The name of the database you want to use. If not provided, Mongoose uses the database name from connection string. */
- dbName?: string;
- /** username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility. */
- user?: string;
- /** password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility. */
- pass?: string;
- /** Set to false to disable automatic index creation for all models associated with this connection. */
- autoIndex?: boolean;
- /** Set to `true` to make Mongoose automatically call `createCollection()` on every model created on this connection. */
- autoCreate?: boolean;
- }
-
- class Connection extends events.EventEmitter {
- /** Returns a promise that resolves when this connection successfully connects to MongoDB */
- asPromise(): Promise<this>;
-
- /** Closes the connection */
- close(callback: CallbackWithoutResult): void;
- close(force: boolean, callback: CallbackWithoutResult): void;
- close(force?: boolean): Promise<void>;
-
- /** Retrieves a collection, creating it if not cached. */
- collection(name: string, options?: mongodb.CreateCollectionOptions): Collection;
-
- /** A hash of the collections associated with this connection */
- collections: { [index: string]: Collection };
-
- /** A hash of the global options that are associated with this connection */
- config: any;
-
- /** The mongodb.Db instance, set when the connection is opened */
- db: mongodb.Db;
-
- /**
- * Helper for `createCollection()`. Will explicitly create the given collection
- * with specified options. Used to create [capped collections](https://docs.mongodb.com/manual/core/capped-collections/)
- * and [views](https://docs.mongodb.com/manual/core/views/) from mongoose.
- */
- createCollection(name: string, options?: mongodb.CreateCollectionOptions): Promise<mongodb.Collection>;
- createCollection(name: string, cb: Callback<mongodb.Collection>): void;
- createCollection(name: string, options: mongodb.CreateCollectionOptions, cb?: Callback<mongodb.Collection>): Promise<mongodb.Collection>;
-
- /**
- * Removes the model named `name` from this connection, if it exists. You can
- * use this function to clean up any models you created in your tests to
- * prevent OverwriteModelErrors.
- */
- deleteModel(name: string): this;
-
- /**
- * Helper for `dropCollection()`. Will delete the given collection, including
- * all documents and indexes.
- */
- dropCollection(collection: string): Promise<void>;
- dropCollection(collection: string, cb: CallbackWithoutResult): void;
-
- /**
- * Helper for `dropDatabase()`. Deletes the given database, including all
- * collections, documents, and indexes.
- */
- dropDatabase(): Promise<void>;
- dropDatabase(cb: CallbackWithoutResult): void;
-
- /** Gets the value of the option `key`. Equivalent to `conn.options[key]` */
- get(key: string): any;
-
- /**
- * Returns the [MongoDB driver `MongoClient`](http://mongodb.github.io/node-mongodb-native/3.5/api/MongoClient.html) instance
- * that this connection uses to talk to MongoDB.
- */
- getClient(): mongodb.MongoClient;
-
- /**
- * The host name portion of the URI. If multiple hosts, such as a replica set,
- * this will contain the first host name in the URI
- */
- host: string;
-
- /**
- * A number identifier for this connection. Used for debugging when
- * you have [multiple connections](/docs/connections.html#multiple_connections).
- */
- id: number;
-
- /**
- * A [POJO](https://masteringjs.io/tutorials/fundamentals/pojo) containing
- * a map from model names to models. Contains all models that have been
- * added to this connection using [`Connection#model()`](/docs/api/connection.html#connection_Connection-model).
- */
- models: { [index: string]: Model<any> };
-
- /** Defines or retrieves a model. */
- model<T>(name: string, schema?: Schema<any>, collection?: string): Model<T>;
- model<T, U, TQueryHelpers = {}>(
- name: string,
- schema?: Schema<T, U, TQueryHelpers>,
- collection?: string,
- skipInit?: boolean
- ): U;
-
- /** Returns an array of model names created on this connection. */
- modelNames(): Array<string>;
-
- /** The name of the database this connection points to. */
- name: string;
-
- /** Opens the connection with a URI using `MongoClient.connect()`. */
- openUri(uri: string, options?: ConnectOptions): Promise<Connection>;
- openUri(uri: string, callback: (err: CallbackError, conn?: Connection) => void): Connection;
- openUri(uri: string, options: ConnectOptions, callback: (err: CallbackError, conn?: Connection) => void): Connection;
-
- /** The password specified in the URI */
- pass: string;
-
- /**
- * The port portion of the URI. If multiple hosts, such as a replica set,
- * this will contain the port from the first host name in the URI.
- */
- port: number;
-
- /** Declares a plugin executed on all schemas you pass to `conn.model()` */
- plugin(fn: (schema: Schema, opts?: any) => void, opts?: any): Connection;
-
- /** The plugins that will be applied to all models created on this connection. */
- plugins: Array<any>;
-
- /**
- * Connection ready state
- *
- * - 0 = disconnected
- * - 1 = connected
- * - 2 = connecting
- * - 3 = disconnecting
- */
- readyState: number;
-
- /** Sets the value of the option `key`. Equivalent to `conn.options[key] = val` */
- set(key: string, value: any): any;
-
- /**
- * Set the [MongoDB driver `MongoClient`](http://mongodb.github.io/node-mongodb-native/3.5/api/MongoClient.html) instance
- * that this connection uses to talk to MongoDB. This is useful if you already have a MongoClient instance, and want to
- * reuse it.
- */
- setClient(client: mongodb.MongoClient): this;
-
- /**
- * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions)
- * for benefits like causal consistency, [retryable writes](https://docs.mongodb.com/manual/core/retryable-writes/),
- * and [transactions](http://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html).
- */
- startSession(options?: mongodb.ClientSessionOptions): Promise<mongodb.ClientSession>;
- startSession(options: mongodb.ClientSessionOptions, cb: Callback<mongodb.ClientSession>): void;
-
- /**
- * _Requires MongoDB >= 3.6.0._ Executes the wrapped async function
- * in a transaction. Mongoose will commit the transaction if the
- * async function executes successfully and attempt to retry if
- * there was a retriable error.
- */
- transaction(fn: (session: mongodb.ClientSession) => Promise<any>): Promise<any>;
-
- /** Switches to a different database using the same connection pool. */
- useDb(name: string, options?: { useCache?: boolean, noListener?: boolean }): Connection;
-
- /** The username specified in the URI */
- user: string;
-
- /** Watches the entire underlying database for changes. Similar to [`Model.watch()`](/docs/api/model.html#model_Model.watch). */
- watch<ResultType = any>(pipeline?: Array<any>, options?: mongodb.ChangeStreamOptions): mongodb.ChangeStream<ResultType>;
- }
-
- /*
- * section collection.js
- * http://mongoosejs.com/docs/api.html#collection-js
- */
- interface CollectionBase extends mongodb.Collection {
- /*
- * Abstract methods. Some of these are already defined on the
- * mongodb.Collection interface so they've been commented out.
- */
- ensureIndex(...args: any[]): any;
- findAndModify(...args: any[]): any;
- getIndexes(...args: any[]): any;
-
- /** The collection name */
- collectionName: string;
- /** The Connection instance */
- conn: Connection;
- /** The collection name */
- name: string;
- }
-
- /*
- * section drivers/node-mongodb-native/collection.js
- * http://mongoosejs.com/docs/api.html#drivers-node-mongodb-native-collection-js
- */
- let Collection: Collection;
- interface Collection extends CollectionBase {
- /**
- * Collection constructor
- * @param name name of the collection
- * @param conn A MongooseConnection instance
- * @param opts optional collection options
- */
- // eslint-disable-next-line @typescript-eslint/no-misused-new
- new(name: string, conn: Connection, opts?: any): Collection;
- /** Formatter for debug print args */
- $format(arg: any): string;
- /** Debug print helper */
- $print(name: any, i: any, args: any[]): void;
- /** Retrieves information about this collections indexes. */
- getIndexes(): any;
- }
-
- class Document<T = any, TQueryHelpers = any, DocType = any> {
- constructor(doc?: any);
-
- /** This documents _id. */
- _id?: T;
-
- /** This documents __v. */
- __v?: any;
-
- /* Get all subdocs (by bfs) */
- $getAllSubdocs(): Document[];
-
- /** Don't run validation on this path or persist changes to this path. */
- $ignore(path: string): void;
-
- /** Checks if a path is set to its default. */
- $isDefault(path: string): boolean;
-
- /** Getter/setter, determines whether the document was removed or not. */
- $isDeleted(val?: boolean): boolean;
-
- /** Returns an array of all populated documents associated with the query */
- $getPopulatedDocs(): Document[];
-
- /**
- * Returns true if the given path is nullish or only contains empty objects.
- * Useful for determining whether this subdoc will get stripped out by the
- * [minimize option](/docs/guide.html#minimize).
- */
- $isEmpty(path: string): boolean;
-
- /** Checks if a path is invalid */
- $isValid(path: string): boolean;
-
- /**
- * Empty object that you can use for storing properties on the document. This
- * is handy for passing data to middleware without conflicting with Mongoose
- * internals.
- */
- $locals: Record<string, unknown>;
-
- /** Marks a path as valid, removing existing validation errors. */
- $markValid(path: string): void;
-
- /**
- * A string containing the current operation that Mongoose is executing
- * on this document. May be `null`, `'save'`, `'validate'`, or `'remove'`.
- */
- $op: string | null;
-
- /**
- * Getter/setter around the session associated with this document. Used to
- * automatically set `session` if you `save()` a doc that you got from a
- * query with an associated session.
- */
- $session(session?: mongodb.ClientSession | null): mongodb.ClientSession;
-
- /** Alias for `set()`, used internally to avoid conflicts */
- $set(path: string, val: any, options?: any): this;
- $set(path: string, val: any, type: any, options?: any): this;
- $set(value: any): this;
-
- /** Set this property to add additional query filters when Mongoose saves this document and `isNew` is false. */
- $where: Record<string, unknown>;
-
- /** If this is a discriminator model, `baseModelName` is the name of the base model. */
- baseModelName?: string;
-
- /** Collection the model uses. */
- collection: Collection;
-
- /** Connection the model uses. */
- db: Connection;
-
- /** Removes this document from the db. */
- delete(options?: QueryOptions): QueryWithHelpers<any, this, TQueryHelpers>;
- delete(options: QueryOptions, cb?: Callback): void;
- delete(cb: Callback): void;
-
- /** Removes this document from the db. */
- deleteOne(options?: QueryOptions): QueryWithHelpers<any, this, TQueryHelpers>;
- deleteOne(options: QueryOptions, cb?: Callback): void;
- deleteOne(cb: Callback): void;
-
- /**
- * Takes a populated field and returns it to its unpopulated state. If called with
- * no arguments, then all populated fields are returned to their unpopulated state.
- */
- depopulate(path?: string | string[]): this;
-
- /**
- * Returns the list of paths that have been directly modified. A direct
- * modified path is a path that you explicitly set, whether via `doc.foo = 'bar'`,
- * `Object.assign(doc, { foo: 'bar' })`, or `doc.set('foo', 'bar')`.
- */
- directModifiedPaths(): Array<string>;
-
- /**
- * Returns true if this document is equal to another document.
- *
- * Documents are considered equal when they have matching `_id`s, unless neither
- * document has an `_id`, in which case this function falls back to using
- * `deepEqual()`.
- */
- equals(doc: Document<T>): boolean;
-
- /** Hash containing current validation errors. */
- errors?: Error.ValidationError;
-
- /** Returns the value of a path. */
- get(path: string, type?: any, options?: any): any;
-
- /**
- * Returns the changes that happened to the document
- * in the format that will be sent to MongoDB.
- */
- getChanges(): UpdateQuery<this>;
-
- /** The string version of this documents _id. */
- id?: any;
-
- /** Signal that we desire an increment of this documents version. */
- increment(): this;
-
- /**
- * Initializes the document without setters or marking anything modified.
- * Called internally after a document is returned from mongodb. Normally,
- * you do **not** need to call this function on your own.
- */
- init(obj: any, opts?: any, cb?: Callback<this>): this;
-
- /** Marks a path as invalid, causing validation to fail. */
- invalidate(path: string, errorMsg: string | NativeError, value?: any, kind?: string): NativeError | null;
-
- /** Returns true if `path` was directly set and modified, else false. */
- isDirectModified(path: string): boolean;
-
- /** Checks if `path` was explicitly selected. If no projection, always returns true. */
- isDirectSelected(path: string): boolean;
-
- /** Checks if `path` is in the `init` state, that is, it was set by `Document#init()` and not modified since. */
- isInit(path: string): boolean;
-
- /**
- * Returns true if any of the given paths is modified, else false. If no arguments, returns `true` if any path
- * in this document is modified.
- */
- isModified(path?: string | Array<string>): boolean;
-
- /** Boolean flag specifying if the document is new. */
- isNew: boolean;
-
- /** Checks if `path` was selected in the source query which initialized this document. */
- isSelected(path: string): boolean;
-
- /** Marks the path as having pending changes to write to the db. */
- markModified(path: string, scope?: any): void;
-
- /** Returns the list of paths that have been modified. */
- modifiedPaths(options?: { includeChildren?: boolean }): Array<string>;
-
- /** The name of the model */
- modelName: string;
-
- /**
- * Overwrite all values in this document with the values of `obj`, except
- * for immutable properties. Behaves similarly to `set()`, except for it
- * unsets all properties that aren't in `obj`.
- */
- overwrite(obj: AnyObject): this;
-
- /**
- * If this document is a subdocument or populated document, returns the
- * document's parent. Returns undefined otherwise.
- */
- $parent(): Document | undefined;
-
- /** Populates document references. */
- populate<Paths = {}>(path: string | PopulateOptions | (string | PopulateOptions)[]): Promise<this & Paths>;
- populate<Paths = {}>(path: string | PopulateOptions | (string | PopulateOptions)[], callback: Callback<this & Paths>): void;
- populate<Paths = {}>(path: string, names: string): Promise<this & Paths>;
- populate<Paths = {}>(path: string, names: string, callback: Callback<this & Paths>): void;
-
- /** Gets _id(s) used during population of the given `path`. If the path was not populated, returns `undefined`. */
- populated(path: string): any;
-
- /** Removes this document from the db. */
- remove(options?: QueryOptions): Promise<this>;
- remove(options?: QueryOptions, cb?: Callback): void;
-
- /** Sends a replaceOne command with this document `_id` as the query selector. */
- replaceOne(replacement?: AnyObject, options?: QueryOptions | null, callback?: Callback): Query<any, this>;
- replaceOne(replacement?: AnyObject, options?: QueryOptions | null, callback?: Callback): Query<any, this>;
-
- /** Saves this document by inserting a new document into the database if [document.isNew](/docs/api.html#document_Document-isNew) is `true`, or sends an [updateOne](/docs/api.html#document_Document-updateOne) operation with just the modified paths if `isNew` is `false`. */
- save(options?: SaveOptions): Promise<this>;
- save(options?: SaveOptions, fn?: Callback<this>): void;
- save(fn?: Callback<this>): void;
-
- /** The document's schema. */
- schema: Schema;
-
- /** Sets the value of a path, or many paths. */
- set(path: string, val: any, options?: any): this;
- set(path: string, val: any, type: any, options?: any): this;
- set(value: any): this;
-
- /** The return value of this method is used in calls to JSON.stringify(doc). */
- toJSON(options: ToObjectOptions & { flattenMaps: false }): LeanDocument<this>;
- toJSON(options?: ToObjectOptions): FlattenMaps<LeanDocument<this>>;
- toJSON<T = FlattenMaps<DocType>>(options?: ToObjectOptions): T;
-
- /** Converts this document into a plain-old JavaScript object ([POJO](https://masteringjs.io/tutorials/fundamentals/pojo)). */
- toObject(options?: ToObjectOptions): LeanDocument<this>;
- toObject<T = DocType>(options?: ToObjectOptions): T;
-
- /** Clears the modified state on the specified path. */
- unmarkModified(path: string): void;
-
- /** Sends an update command with this document `_id` as the query selector. */
- update(update?: UpdateQuery<this> | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback): Query<any, this>;
-
- /** Sends an updateOne command with this document `_id` as the query selector. */
- updateOne(update?: UpdateQuery<this> | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback): Query<any, this>;
-
- /** Executes registered validation rules for this document. */
- validate(options:{ pathsToSkip?: pathsToSkip }): Promise<void>;
- validate(pathsToValidate?: pathsToValidate, options?: any): Promise<void>;
- validate(callback: CallbackWithoutResult): void;
- validate(pathsToValidate: pathsToValidate, callback: CallbackWithoutResult): void;
- validate(pathsToValidate: pathsToValidate, options: any, callback: CallbackWithoutResult): void;
-
- /** Executes registered validation rules (skipping asynchronous validators) for this document. */
- validateSync(options:{pathsToSkip?: pathsToSkip, [k:string]: any }): Error.ValidationError | null;
- validateSync(pathsToValidate?: Array<string>, options?: any): Error.ValidationError | null;
- }
-
- /** A list of paths to validate. If set, Mongoose will validate only the modified paths that are in the given list. */
- type pathsToValidate = string[] | string;
- /** A list of paths to skip. If set, Mongoose will validate every modified path that is not in this list. */
- type pathsToSkip = string[] | string;
-
- interface AcceptsDiscriminator {
- /** Adds a discriminator type. */
- discriminator<D>(name: string | number, schema: Schema, value?: string | number | ObjectId): Model<D>;
- discriminator<T, U>(name: string | number, schema: Schema<T, U>, value?: string | number | ObjectId): U;
- }
-
- type AnyKeys<T> = { [P in keyof T]?: T[P] | any };
- interface AnyObject { [k: string]: any }
-
- type Require_id<T> = T extends { _id?: any } ? (T & { _id: T['_id'] }) : (T & { _id: Types.ObjectId });
-
- export type HydratedDocument<DocType, TMethods = {}, TVirtuals = {}> = DocType extends Document ? Require_id<DocType> : (Document<any, any, DocType> & Require_id<DocType> & TVirtuals & TMethods);
-
- interface IndexesDiff {
- /** Indexes that would be created in mongodb. */
- toCreate: Array<any>
- /** Indexes that would be dropped in mongodb. */
- toDrop: Array<any>
- }
-
- export const Model: Model<any>;
- interface Model<T, TQueryHelpers = {}, TMethods = {}, TVirtuals = {}> extends NodeJS.EventEmitter, AcceptsDiscriminator {
- new(doc?: AnyKeys<T> & AnyObject, fields?: any | null, options?: boolean | AnyObject): HydratedDocument<T, TMethods, TVirtuals>;
-
- aggregate<R = any>(pipeline?: any[], options?: Record<string, unknown>): Aggregate<Array<R>>;
- aggregate<R = any>(pipeline: any[], cb: Function): Promise<Array<R>>;
- aggregate<R = any>(pipeline: any[], options: Record<string, unknown>, cb: Function): Promise<Array<R>>;
-
- /** Base Mongoose instance the model uses. */
- base: typeof mongoose;
-
- /**
- * If this is a discriminator model, `baseModelName` is the name of
- * the base model.
- */
- baseModelName: string | undefined;
-
- /**
- * Sends multiple `insertOne`, `updateOne`, `updateMany`, `replaceOne`,
- * `deleteOne`, and/or `deleteMany` operations to the MongoDB server in one
- * command. This is faster than sending multiple independent operations (e.g.
- * if you use `create()`) because with `bulkWrite()` there is only one network
- * round trip to the MongoDB server.
- */
- bulkWrite(writes: Array<any>, options?: mongodb.BulkWriteOptions): Promise<mongodb.BulkWriteResult>;
- bulkWrite(writes: Array<any>, options?: mongodb.BulkWriteOptions, cb?: Callback<mongodb.BulkWriteResult>): void;
-
- /**
- * Sends multiple `save()` calls in a single `bulkWrite()`. This is faster than
- * sending multiple `save()` calls because with `bulkSave()` there is only one
- * network round trip to the MongoDB server.
- */
- bulkSave(documents: Array<Document>): Promise<mongodb.BulkWriteResult>;
-
- /** Collection the model uses. */
- collection: Collection;
-
- /** Creates a `count` query: counts the number of documents that match `filter`. */
- count(callback?: Callback<number>): QueryWithHelpers<number, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- count(filter: FilterQuery<T>, callback?: Callback<number>): QueryWithHelpers<number, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Creates a `countDocuments` query: counts the number of documents that match `filter`. */
- countDocuments(callback?: Callback<number>): QueryWithHelpers<number, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- countDocuments(filter: FilterQuery<T>, callback?: Callback<number>): QueryWithHelpers<number, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Creates a new document or documents */
- create(docs: (AnyKeys<T> | AnyObject)[], options?: SaveOptions): Promise<HydratedDocument<T, TMethods, TVirtuals>[]>;
- create(docs: (AnyKeys<T> | AnyObject)[], callback: Callback<HydratedDocument<T, TMethods, TVirtuals>[]>): void;
- create(doc: AnyKeys<T> | AnyObject): Promise<HydratedDocument<T, TMethods, TVirtuals>>;
- create(doc: AnyKeys<T> | AnyObject, callback: Callback<HydratedDocument<T, TMethods, TVirtuals>>): void;
- create<DocContents = AnyKeys<T>>(docs: DocContents[], options?: SaveOptions): Promise<HydratedDocument<T, TMethods, TVirtuals>[]>;
- create<DocContents = AnyKeys<T>>(docs: DocContents[], callback: Callback<HydratedDocument<T, TMethods, TVirtuals>[]>): void;
- create<DocContents = AnyKeys<T>>(doc: DocContents): Promise<HydratedDocument<T, TMethods, TVirtuals>>;
- create<DocContents = AnyKeys<T>>(...docs: DocContents[]): Promise<HydratedDocument<T, TMethods, TVirtuals>[]>;
- create<DocContents = AnyKeys<T>>(doc: DocContents, callback: Callback<HydratedDocument<T, TMethods, TVirtuals>>): void;
-
- /**
- * Create the collection for this model. By default, if no indexes are specified,
- * mongoose will not create the collection for the model until any documents are
- * created. Use this method to create the collection explicitly.
- */
- createCollection(options?: mongodb.CreateCollectionOptions): Promise<mongodb.Collection>;
- createCollection(options: mongodb.CreateCollectionOptions | null, callback: Callback<mongodb.Collection>): void;
-
- /**
- * Similar to `ensureIndexes()`, except for it uses the [`createIndex`](http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#createIndex)
- * function.
- */
- createIndexes(callback?: CallbackWithoutResult): Promise<void>;
- createIndexes(options?: any, callback?: CallbackWithoutResult): Promise<void>;
-
- /** Connection the model uses. */
- db: Connection;
-
- /**
- * Deletes all of the documents that match `conditions` from the collection.
- * Behaves like `remove()`, but deletes all documents that match `conditions`
- * regardless of the `single` option.
- */
- deleteMany(filter?: FilterQuery<T>, options?: QueryOptions, callback?: CallbackWithoutResult): QueryWithHelpers<mongodb.DeleteResult, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- deleteMany(filter: FilterQuery<T>, callback: CallbackWithoutResult): QueryWithHelpers<mongodb.DeleteResult, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- deleteMany(callback: CallbackWithoutResult): QueryWithHelpers<mongodb.DeleteResult, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /**
- * Deletes the first document that matches `conditions` from the collection.
- * Behaves like `remove()`, but deletes at most one document regardless of the
- * `single` option.
- */
- deleteOne(filter?: FilterQuery<T>, options?: QueryOptions, callback?: CallbackWithoutResult): QueryWithHelpers<mongodb.DeleteResult, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- deleteOne(filter: FilterQuery<T>, callback: CallbackWithoutResult): QueryWithHelpers<mongodb.DeleteResult, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- deleteOne(callback: CallbackWithoutResult): QueryWithHelpers<mongodb.DeleteResult, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /**
- * Sends `createIndex` commands to mongo for each index declared in the schema.
- * The `createIndex` commands are sent in series.
- */
- ensureIndexes(callback?: CallbackWithoutResult): Promise<void>;
- ensureIndexes(options?: any, callback?: CallbackWithoutResult): Promise<void>;
-
- /**
- * Event emitter that reports any errors that occurred. Useful for global error
- * handling.
- */
- events: NodeJS.EventEmitter;
-
- /**
- * Finds a single document by its _id field. `findById(id)` is almost*
- * equivalent to `findOne({ _id: id })`. If you want to query by a document's
- * `_id`, use `findById()` instead of `findOne()`.
- */
- findById(id: any, projection?: any | null, options?: QueryOptions | null, callback?: Callback<HydratedDocument<T, TMethods, TVirtuals> | null>): QueryWithHelpers<HydratedDocument<T, TMethods, TVirtuals> | null, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Finds one document. */
- findOne(filter?: FilterQuery<T>, projection?: any | null, options?: QueryOptions | null, callback?: Callback<HydratedDocument<T, TMethods, TVirtuals> | null>): QueryWithHelpers<HydratedDocument<T, TMethods, TVirtuals> | null, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /**
- * Shortcut for creating a new Document from existing raw data, pre-saved in the DB.
- * The document returned has no paths marked as modified initially.
- */
- hydrate(obj: any): HydratedDocument<T, TMethods, TVirtuals>;
-
- /**
- * This function is responsible for building [indexes](https://docs.mongodb.com/manual/indexes/),
- * unless [`autoIndex`](http://mongoosejs.com/docs/guide.html#autoIndex) is turned off.
- * Mongoose calls this function automatically when a model is created using
- * [`mongoose.model()`](/docs/api.html#mongoose_Mongoose-model) or
- * [`connection.model()`](/docs/api.html#connection_Connection-model), so you
- * don't need to call it.
- */
- init(callback?: CallbackWithoutResult): Promise<HydratedDocument<T, TMethods, TVirtuals>>;
-
- /** Inserts one or more new documents as a single `insertMany` call to the MongoDB server. */
- insertMany(docs: Array<AnyKeys<T> | AnyObject>, options: InsertManyOptions & { rawResult: true }): Promise<InsertManyResult>;
- insertMany(docs: Array<AnyKeys<T> | AnyObject>, options?: InsertManyOptions): Promise<Array<HydratedDocument<T, TMethods, TVirtuals>>>;
- insertMany(doc: AnyKeys<T> | AnyObject, options: InsertManyOptions & { rawResult: true }): Promise<InsertManyResult>;
- insertMany(doc: AnyKeys<T> | AnyObject, options?: InsertManyOptions): Promise<HydratedDocument<T, TMethods, TVirtuals>[]>;
- insertMany(doc: AnyKeys<T> | AnyObject, options?: InsertManyOptions, callback?: Callback<HydratedDocument<T, TMethods, TVirtuals>[] | InsertManyResult>): void;
- insertMany(docs: Array<AnyKeys<T> | AnyObject>, options?: InsertManyOptions, callback?: Callback<Array<HydratedDocument<T, TMethods, TVirtuals>> | InsertManyResult>): void;
-
- /**
- * Lists the indexes currently defined in MongoDB. This may or may not be
- * the same as the indexes defined in your schema depending on whether you
- * use the [`autoIndex` option](/docs/guide.html#autoIndex) and if you
- * build indexes manually.
- */
- listIndexes(callback: Callback<Array<any>>): void;
- listIndexes(): Promise<Array<any>>;
-
- /** The name of the model */
- modelName: string;
-
- /** Populates document references. */
- populate(docs: Array<any>, options: PopulateOptions | Array<PopulateOptions> | string,
- callback?: Callback<(HydratedDocument<T, TMethods, TVirtuals>)[]>): Promise<Array<HydratedDocument<T, TMethods, TVirtuals>>>;
- populate(doc: any, options: PopulateOptions | Array<PopulateOptions> | string,
- callback?: Callback<HydratedDocument<T, TMethods, TVirtuals>>): Promise<HydratedDocument<T, TMethods, TVirtuals>>;
-
- /**
- * Makes the indexes in MongoDB match the indexes defined in this model's
- * schema. This function will drop any indexes that are not defined in
- * the model's schema except the `_id` index, and build any indexes that
- * are in your schema but not in MongoDB.
- */
- syncIndexes(options?: Record<string, unknown>): Promise<Array<string>>;
- syncIndexes(options: Record<string, unknown> | null, callback: Callback<Array<string>>): void;
-
- /**
- * Does a dry-run of Model.syncIndexes(), meaning that
- * the result of this function would be the result of
- * Model.syncIndexes().
- */
- diffIndexes(options?: Record<string, unknown>): Promise<IndexesDiff>
- diffIndexes(options: Record<string, unknown> | null, callback: (err: CallbackError, diff: IndexesDiff) => void): void
-
- /**
- * Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions)
- * for benefits like causal consistency, [retryable writes](https://docs.mongodb.com/manual/core/retryable-writes/),
- * and [transactions](http://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html).
- * */
- startSession(options?: mongodb.ClientSessionOptions, cb?: Callback<mongodb.ClientSession>): Promise<mongodb.ClientSession>;
-
- /** Casts and validates the given object against this model's schema, passing the given `context` to custom validators. */
- validate(callback?: CallbackWithoutResult): Promise<void>;
- validate(optional: any, callback?: CallbackWithoutResult): Promise<void>;
- validate(optional: any, pathsToValidate: string[], callback?: CallbackWithoutResult): Promise<void>;
-
- /** Watches the underlying collection for changes using [MongoDB change streams](https://docs.mongodb.com/manual/changeStreams/). */
- watch<ResultType = any>(pipeline?: Array<Record<string, unknown>>, options?: mongodb.ChangeStreamOptions): mongodb.ChangeStream<ResultType>;
-
- /** Adds a `$where` clause to this query */
- $where(argument: string | Function): QueryWithHelpers<Array<HydratedDocument<T, TMethods, TVirtuals>>, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Registered discriminators for this model. */
- discriminators: { [name: string]: Model<any> } | undefined;
-
- /** Translate any aliases fields/conditions so the final query or document object is pure */
- translateAliases(raw: any): any;
-
- /** Creates a `distinct` query: returns the distinct values of the given `field` that match `filter`. */
- distinct(field: string, filter?: FilterQuery<T>, callback?: Callback<number>): QueryWithHelpers<Array<any>, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Creates a `estimatedDocumentCount` query: counts the number of documents in the collection. */
- estimatedDocumentCount(options?: QueryOptions, callback?: Callback<number>): QueryWithHelpers<number, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /**
- * Returns true if at least one document exists in the database that matches
- * the given `filter`, and false otherwise.
- */
- exists(filter: FilterQuery<T>): Promise<boolean>;
- exists(filter: FilterQuery<T>, callback: Callback<boolean>): void;
-
- /** Creates a `find` query: gets a list of documents that match `filter`. */
- find(callback?: Callback<HydratedDocument<T, TMethods, TVirtuals>[]>): QueryWithHelpers<Array<HydratedDocument<T, TMethods, TVirtuals>>, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- find(filter: FilterQuery<T>, callback?: Callback<T[]>): QueryWithHelpers<Array<HydratedDocument<T, TMethods, TVirtuals>>, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- find(filter: FilterQuery<T>, projection?: any | null, options?: QueryOptions | null, callback?: Callback<HydratedDocument<T, TMethods, TVirtuals>[]>): QueryWithHelpers<Array<HydratedDocument<T, TMethods, TVirtuals>>, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Creates a `findByIdAndDelete` query, filtering by the given `_id`. */
- findByIdAndDelete(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: CallbackError, doc: HydratedDocument<T, TMethods, TVirtuals> | null, res: any) => void): QueryWithHelpers<HydratedDocument<T, TMethods, TVirtuals> | null, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Creates a `findByIdAndRemove` query, filtering by the given `_id`. */
- findByIdAndRemove(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: CallbackError, doc: HydratedDocument<T, TMethods, TVirtuals> | null, res: any) => void): QueryWithHelpers<HydratedDocument<T, TMethods, TVirtuals> | null, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Creates a `findOneAndUpdate` query, filtering by the given `_id`. */
- findByIdAndUpdate(id: mongodb.ObjectId | any, update: UpdateQuery<T>, options: QueryOptions & { rawResult: true }, callback?: (err: CallbackError, doc: any, res: any) => void): QueryWithHelpers<mongodb.ModifyResult<HydratedDocument<T, TMethods, TVirtuals>>, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- findByIdAndUpdate(id: mongodb.ObjectId | any, update: UpdateQuery<T>, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: HydratedDocument<T, TMethods, TVirtuals>, res: any) => void): QueryWithHelpers<HydratedDocument<T, TMethods, TVirtuals>, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- findByIdAndUpdate(id?: mongodb.ObjectId | any, update?: UpdateQuery<T>, options?: QueryOptions | null, callback?: (err: CallbackError, doc: HydratedDocument<T, TMethods, TVirtuals> | null, res: any) => void): QueryWithHelpers<HydratedDocument<T, TMethods, TVirtuals> | null, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- findByIdAndUpdate(id: mongodb.ObjectId | any, update: UpdateQuery<T>, callback: (err: CallbackError, doc: HydratedDocument<T, TMethods, TVirtuals> | null, res: any) => void): QueryWithHelpers<HydratedDocument<T, TMethods, TVirtuals> | null, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Creates a `findOneAndDelete` query: atomically finds the given document, deletes it, and returns the document as it was before deletion. */
- findOneAndDelete(filter?: FilterQuery<T>, options?: QueryOptions | null, callback?: (err: CallbackError, doc: HydratedDocument<T, TMethods, TVirtuals> | null, res: any) => void): QueryWithHelpers<HydratedDocument<T, TMethods, TVirtuals> | null, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Creates a `findOneAndRemove` query: atomically finds the given document and deletes it. */
- findOneAndRemove(filter?: FilterQuery<T>, options?: QueryOptions | null, callback?: (err: CallbackError, doc: HydratedDocument<T, TMethods, TVirtuals> | null, res: any) => void): QueryWithHelpers<HydratedDocument<T, TMethods, TVirtuals> | null, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Creates a `findOneAndReplace` query: atomically finds the given document and replaces it with `replacement`. */
- findOneAndReplace(filter: FilterQuery<T>, replacement: T | AnyObject, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: HydratedDocument<T, TMethods, TVirtuals>, res: any) => void): QueryWithHelpers<HydratedDocument<T, TMethods, TVirtuals>, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- findOneAndReplace(filter?: FilterQuery<T>, replacement?: T | AnyObject, options?: QueryOptions | null, callback?: (err: CallbackError, doc: HydratedDocument<T, TMethods, TVirtuals> | null, res: any) => void): QueryWithHelpers<HydratedDocument<T, TMethods, TVirtuals> | null, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Creates a `findOneAndUpdate` query: atomically find the first document that matches `filter` and apply `update`. */
- findOneAndUpdate(filter: FilterQuery<T>, update: UpdateQuery<T>, options: QueryOptions & { rawResult: true }, callback?: (err: CallbackError, doc: any, res: any) => void): QueryWithHelpers<mongodb.ModifyResult<HydratedDocument<T, TMethods, TVirtuals>>, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- findOneAndUpdate(filter: FilterQuery<T>, update: UpdateQuery<T>, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: HydratedDocument<T, TMethods, TVirtuals>, res: any) => void): QueryWithHelpers<HydratedDocument<T, TMethods, TVirtuals>, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- findOneAndUpdate(filter?: FilterQuery<T>, update?: UpdateQuery<T>, options?: QueryOptions | null, callback?: (err: CallbackError, doc: T | null, res: any) => void): QueryWithHelpers<HydratedDocument<T, TMethods, TVirtuals> | null, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- geoSearch(filter?: FilterQuery<T>, options?: GeoSearchOptions, callback?: Callback<Array<HydratedDocument<T, TMethods, TVirtuals>>>): QueryWithHelpers<Array<HydratedDocument<T, TMethods, TVirtuals>>, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Executes a mapReduce command. */
- mapReduce<Key, Value>(
- o: MapReduceOptions<T, Key, Value>,
- callback?: Callback
- ): Promise<any>;
-
- remove(filter?: any, callback?: CallbackWithoutResult): QueryWithHelpers<any, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Creates a `replaceOne` query: finds the first document that matches `filter` and replaces it with `replacement`. */
- replaceOne(filter?: FilterQuery<T>, replacement?: T | AnyObject, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers<any, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- replaceOne(filter?: FilterQuery<T>, replacement?: T | AnyObject, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers<any, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Schema the model uses. */
- schema: Schema;
-
- /**
- * @deprecated use `updateOne` or `updateMany` instead.
- * Creates a `update` query: updates one or many documents that match `filter` with `update`, based on the `multi` option.
- */
- update(filter?: FilterQuery<T>, update?: UpdateQuery<T> | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers<UpdateWriteOpResult, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Creates a `updateMany` query: updates all documents that match `filter` with `update`. */
- updateMany(filter?: FilterQuery<T>, update?: UpdateQuery<T> | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers<UpdateWriteOpResult, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Creates a `updateOne` query: updates the first document that matches `filter` with `update`. */
- updateOne(filter?: FilterQuery<T>, update?: UpdateQuery<T> | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers<UpdateWriteOpResult, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
-
- /** Creates a Query, applies the passed conditions, and returns the Query. */
- where(path: string, val?: any): QueryWithHelpers<Array<HydratedDocument<T, TMethods, TVirtuals>>, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- where(obj: object): QueryWithHelpers<Array<HydratedDocument<T, TMethods, TVirtuals>>, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- where(): QueryWithHelpers<Array<HydratedDocument<T, TMethods, TVirtuals>>, HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
- }
-
- type UpdateWriteOpResult = mongodb.UpdateResult;
-
- interface QueryOptions {
- arrayFilters?: { [key: string]: any }[];
- batchSize?: number;
- collation?: mongodb.CollationOptions;
- comment?: any;
- context?: string;
- explain?: any;
- fields?: any | string;
- hint?: any;
- /**
- * If truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document.
- */
- lean?: boolean | any;
- limit?: number;
- maxTimeMS?: number;
- maxscan?: number;
- multi?: boolean;
- multipleCastError?: boolean;
- /**
- * By default, `findOneAndUpdate()` returns the document as it was **before**
- * `update` was applied. If you set `new: true`, `findOneAndUpdate()` will
- * instead give you the object after `update` was applied.
- */
- new?: boolean;
- overwrite?: boolean;
- overwriteDiscriminatorKey?: boolean;
- populate?: string | string[] | PopulateOptions | PopulateOptions[];
- projection?: any;
- /**
- * if true, returns the raw result from the MongoDB driver
- */
- rawResult?: boolean;
- readPreference?: string | mongodb.ReadPreferenceMode;
- /**
- * An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`.
- */
- returnOriginal?: boolean;
- /**
- * Another alias for the `new` option. `returnOriginal` is deprecated so this should be used.
- */
- returnDocument?: string;
- runValidators?: boolean;
- /* Set to `true` to automatically sanitize potentially unsafe user-generated query projections */
- sanitizeProjection?: boolean;
- /**
- * Set to `true` to automatically sanitize potentially unsafe query filters by stripping out query selectors that
- * aren't explicitly allowed using `mongoose.trusted()`.
- */
- sanitizeFilter?: boolean;
- /** The session associated with this query. */
- session?: mongodb.ClientSession;
- setDefaultsOnInsert?: boolean;
- skip?: number;
- snapshot?: any;
- sort?: any;
- /** overwrites the schema's strict mode option */
- strict?: boolean | string;
- /**
- * equal to `strict` by default, may be `false`, `true`, or `'throw'`. Sets the default
- * [strictQuery](https://mongoosejs.com/docs/guide.html#strictQuery) mode for schemas.
- */
- strictQuery?: boolean | 'throw';
- tailable?: number;
- /**
- * If set to `false` and schema-level timestamps are enabled,
- * skip timestamps for this update. Note that this allows you to overwrite
- * timestamps. Does nothing if schema-level timestamps are not set.
- */
- timestamps?: boolean;
- upsert?: boolean;
- writeConcern?: any;
-
- [other: string]: any;
- }
-
- type MongooseQueryOptions = Pick<QueryOptions, 'populate' | 'lean' | 'strict' | 'sanitizeProjection' | 'sanitizeFilter'>;
-
- interface SaveOptions {
- checkKeys?: boolean;
- j?: boolean;
- safe?: boolean | WriteConcern;
- session?: ClientSession | null;
- timestamps?: boolean;
- validateBeforeSave?: boolean;
- validateModifiedOnly?: boolean;
- w?: number | string;
- wtimeout?: number;
- }
-
- interface WriteConcern {
- j?: boolean;
- w?: number | 'majority' | TagSet;
- wtimeout?: number;
- }
-
- interface TagSet {
- [k: string]: string;
- }
-
- interface InsertManyOptions {
- limit?: number;
- rawResult?: boolean;
- ordered?: boolean;
- lean?: boolean;
- session?: mongodb.ClientSession;
- populate?: string | string[] | PopulateOptions | PopulateOptions[];
- }
-
- interface InsertManyResult extends mongodb.InsertManyResult {
- mongoose?: { validationErrors?: Array<Error.CastError | Error.ValidatorError> }
- }
-
- interface MapReduceOptions<T, Key, Val> {
- map: Function | string;
- reduce: (key: Key, vals: T[]) => Val;
- /** query filter object. */
- query?: any;
- /** sort input objects using this key */
- sort?: any;
- /** max number of documents */
- limit?: number;
- /** keep temporary data default: false */
- keeptemp?: boolean;
- /** finalize function */
- finalize?: (key: Key, val: Val) => Val;
- /** scope variables exposed to map/reduce/finalize during execution */
- scope?: any;
- /** it is possible to make the execution stay in JS. Provided in MongoDB > 2.0.X default: false */
- jsMode?: boolean;
- /** provide statistics on job execution time. default: false */
- verbose?: boolean;
- readPreference?: string;
- /** sets the output target for the map reduce job. default: {inline: 1} */
- out?: {
- /** the results are returned in an array */
- inline?: number;
- /**
- * {replace: 'collectionName'} add the results to collectionName: the
- * results replace the collection
- */
- replace?: string;
- /**
- * {reduce: 'collectionName'} add the results to collectionName: if
- * dups are detected, uses the reducer / finalize functions
- */
- reduce?: string;
- /**
- * {merge: 'collectionName'} add the results to collectionName: if
- * dups exist the new docs overwrite the old
- */
- merge?: string;
- };
- }
-
- interface GeoSearchOptions {
- /** x,y point to search for */
- near: number[];
- /** the maximum distance from the point near that a result can be */
- maxDistance: number;
- /** The maximum number of results to return */
- limit?: number;
- /** return the raw object instead of the Mongoose Model */
- lean?: boolean;
- }
-
- interface PopulateOptions {
- /** space delimited path(s) to populate */
- path: string;
- /** fields to select */
- select?: any;
- /** query conditions to match */
- match?: any;
- /** optional model to use for population */
- model?: string | Model<any>;
- /** optional query options like sort, limit, etc */
- options?: any;
- /** deep populate */
- populate?: PopulateOptions | Array<PopulateOptions>;
- /**
- * If true Mongoose will always set `path` to an array, if false Mongoose will
- * always set `path` to a document. Inferred from schema by default.
- */
- justOne?: boolean;
- /** transform function to call on every populated doc */
- transform?: (doc: any, id: any) => any;
- }
-
- interface ToObjectOptions {
- /** apply all getters (path and virtual getters) */
- getters?: boolean;
- /** apply virtual getters (can override getters option) */
- virtuals?: boolean | string[];
- /** if `options.virtuals = true`, you can set `options.aliases = false` to skip applying aliases. This option is a no-op if `options.virtuals = false`. */
- aliases?: boolean;
- /** remove empty objects (defaults to true) */
- minimize?: boolean;
- /** if set, mongoose will call this function to allow you to transform the returned object */
- transform?: (doc: any, ret: any, options: any) => any;
- /** if true, replace any conventionally populated paths with the original id in the output. Has no affect on virtual populated paths. */
- depopulate?: boolean;
- /** if false, exclude the version key (`__v` by default) from the output */
- versionKey?: boolean;
- /** if true, convert Maps to POJOs. Useful if you want to `JSON.stringify()` the result of `toObject()`. */
- flattenMaps?: boolean;
- /** If true, omits fields that are excluded in this document's projection. Unless you specified a projection, this will omit any field that has `select: false` in the schema. */
- useProjection?: boolean;
- }
-
- type MongooseDocumentMiddleware = 'validate' | 'save' | 'remove' | 'updateOne' | 'deleteOne' | 'init';
- type MongooseQueryMiddleware = 'count' | 'deleteMany' | 'deleteOne' | 'distinct' | 'find' | 'findOne' | 'findOneAndDelete' | 'findOneAndRemove' | 'findOneAndUpdate' | 'remove' | 'update' | 'updateOne' | 'updateMany';
-
- type SchemaPreOptions = { document?: boolean, query?: boolean };
- type SchemaPostOptions = { document?: boolean, query?: boolean };
-
- type ExtractQueryHelpers<M> = M extends Model<any, infer TQueryHelpers, any, any> ? TQueryHelpers : {};
- type ExtractVirtuals<M> = M extends Model<any, any, any, infer TVirtuals> ? TVirtuals : {};
-
- type IndexDirection = 1 | -1 | '2d' | '2dsphere' | 'geoHaystack' | 'hashed' | 'text';
- type IndexDefinition = Record<string, IndexDirection>;
-
- type PreMiddlewareFunction<T> = (this: T, next: (err?: CallbackError) => void) => void | Promise<void>;
- type PreSaveMiddlewareFunction<T> = (this: T, next: (err?: CallbackError) => void, opts: SaveOptions) => void | Promise<void>;
- type PostMiddlewareFunction<ThisType, ResType = any> = (this: ThisType, res: ResType, next: (err?: CallbackError) => void) => void | Promise<void>;
- type ErrorHandlingMiddlewareFunction<ThisType, ResType = any> = (this: ThisType, err: NativeError, res: ResType, next: (err?: CallbackError) => void) => void;
-
- class Schema<DocType = any, M = Model<DocType, any, any, any>, TInstanceMethods = any> extends events.EventEmitter {
- /**
- * Create a new schema
- */
- constructor(definition?: SchemaDefinition<SchemaDefinitionType<DocType>>, options?: SchemaOptions);
-
- /** Adds key path / schema type pairs to this schema. */
- add(obj: SchemaDefinition<SchemaDefinitionType<DocType>> | Schema, prefix?: string): this;
-
- /**
- * Array of child schemas (from document arrays and single nested subdocs)
- * and their corresponding compiled models. Each element of the array is
- * an object with 2 properties: `schema` and `model`.
- */
- childSchemas: { schema: Schema, model: any }[];
-
- /** Returns a copy of this schema */
- clone<T = this>(): T;
-
- /** Object containing discriminators defined on this schema */
- discriminators?: { [name: string]: Schema };
-
- /** Iterates the schemas paths similar to Array#forEach. */
- eachPath(fn: (path: string, type: SchemaType) => void): this;
-
- /** Defines an index (most likely compound) for this schema. */
- index(fields: IndexDefinition, options?: IndexOptions): this;
-
- /**
- * Returns a list of indexes that this schema declares, via `schema.index()`
- * or by `index: true` in a path's options.
- */
- indexes(): Array<IndexDefinition>;
-
- /** Gets a schema option. */
- get<K extends keyof SchemaOptions>(key: K): SchemaOptions[K];
-
- /**
- * Loads an ES6 class into a schema. Maps [setters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) + [getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get), [static methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static),
- * and [instance methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Class_body_and_method_definitions)
- * to schema [virtuals](http://mongoosejs.com/docs/guide.html#virtuals),
- * [statics](http://mongoosejs.com/docs/guide.html#statics), and
- * [methods](http://mongoosejs.com/docs/guide.html#methods).
- */
- loadClass(model: Function, onlyVirtuals?: boolean): this;
-
- /** Adds an instance method to documents constructed from Models compiled from this schema. */
- method(name: string, fn: (this: HydratedDocument<DocType, TInstanceMethods, ExtractVirtuals<M>>, ...args: any[]) => any, opts?: any): this;
- method(obj: { [name: string]: (this: HydratedDocument<DocType, TInstanceMethods, ExtractVirtuals<M>>, ...args: any[]) => any }): this;
-
- /** Object of currently defined methods on this schema. */
- methods: { [name: string]: (this: HydratedDocument<DocType, TInstanceMethods, ExtractVirtuals<M>>, ...args: any[]) => any };
-
- /** The original object passed to the schema constructor */
- obj: any;
-
- /** Gets/sets schema paths. */
- path<ResultType extends SchemaType = SchemaType>(path: string): ResultType;
- path(path: string, constructor: any): this;
-
- /** Lists all paths and their type in the schema. */
- paths: {
- [key: string]: SchemaType;
- }
-
- /** Returns the pathType of `path` for this schema. */
- pathType(path: string): string;
-
- /** Registers a plugin for this schema. */
- plugin(fn: (schema: Schema<DocType>, opts?: any) => void, opts?: any): this;
-
- /** Defines a post hook for the model. */
- post<T = HydratedDocument<DocType, TInstanceMethods, ExtractVirtuals<M>>>(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, fn: PostMiddlewareFunction<T>): this;
- post<T = HydratedDocument<DocType, TInstanceMethods, ExtractVirtuals<M>>>(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, options: SchemaPostOptions, fn: PostMiddlewareFunction<T>): this;
- post<T extends Query<any, any> = Query<any, any>>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, fn: PostMiddlewareFunction<T>): this;
- post<T extends Query<any, any> = Query<any, any>>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, options: SchemaPostOptions, fn: PostMiddlewareFunction<T>): this;
- post<T extends Aggregate<any> = Aggregate<any>>(method: 'aggregate' | RegExp, fn: PostMiddlewareFunction<T, Array<any>>): this;
- post<T extends Aggregate<any> = Aggregate<any>>(method: 'aggregate' | RegExp, options: SchemaPostOptions, fn: PostMiddlewareFunction<T, Array<any>>): this;
- post<T = M>(method: 'insertMany' | RegExp, fn: PostMiddlewareFunction<T>): this;
- post<T = M>(method: 'insertMany' | RegExp, options: SchemaPostOptions, fn: PostMiddlewareFunction<T>): this;
-
- post<T = HydratedDocument<DocType, TInstanceMethods, ExtractVirtuals<M>>>(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, fn: ErrorHandlingMiddlewareFunction<T>): this;
- post<T = HydratedDocument<DocType, TInstanceMethods, ExtractVirtuals<M>>>(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, options: SchemaPostOptions, fn: ErrorHandlingMiddlewareFunction<T>): this;
- post<T extends Query<any, any> = Query<any, any>>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, fn: ErrorHandlingMiddlewareFunction<T>): this;
- post<T extends Query<any, any> = Query<any, any>>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, options: SchemaPostOptions, fn: ErrorHandlingMiddlewareFunction<T>): this;
- post<T extends Aggregate<any> = Aggregate<any>>(method: 'aggregate' | RegExp, fn: ErrorHandlingMiddlewareFunction<T, Array<any>>): this;
- post<T extends Aggregate<any> = Aggregate<any>>(method: 'aggregate' | RegExp, options: SchemaPostOptions, fn: ErrorHandlingMiddlewareFunction<T, Array<any>>): this;
- post<T = M>(method: 'insertMany' | RegExp, fn: ErrorHandlingMiddlewareFunction<T>): this;
- post<T = M>(method: 'insertMany' | RegExp, options: SchemaPostOptions, fn: ErrorHandlingMiddlewareFunction<T>): this;
-
- /** Defines a pre hook for the model. */
- pre<T = HydratedDocument<DocType, TInstanceMethods, ExtractVirtuals<M>>>(method: 'save', fn: PreSaveMiddlewareFunction<T>): this;
- pre<T = HydratedDocument<DocType, TInstanceMethods, ExtractVirtuals<M>>>(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, fn: PreMiddlewareFunction<T>): this;
- pre<T = HydratedDocument<DocType, TInstanceMethods, ExtractVirtuals<M>>>(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, options: SchemaPreOptions, fn: PreMiddlewareFunction<T>): this;
- pre<T extends Query<any, any> = Query<any, any>>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, fn: PreMiddlewareFunction<T>): this;
- pre<T extends Query<any, any> = Query<any, any>>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, options: SchemaPreOptions, fn: PreMiddlewareFunction<T>): this;
- pre<T extends Aggregate<any> = Aggregate<any>>(method: 'aggregate' | RegExp, fn: PreMiddlewareFunction<T>): this;
- pre<T extends Aggregate<any> = Aggregate<any>>(method: 'aggregate' | RegExp, options: SchemaPreOptions, fn: PreMiddlewareFunction<T>): this;
- pre<T = M>(method: 'insertMany' | RegExp, fn: (this: T, next: (err?: CallbackError) => void, docs: any | Array<any>) => void | Promise<void>): this;
- pre<T = M>(method: 'insertMany' | RegExp, options: SchemaPreOptions, fn: (this: T, next: (err?: CallbackError) => void, docs: any | Array<any>) => void | Promise<void>): this;
-
- /** Object of currently defined query helpers on this schema. */
- query: { [name: string]: (this: QueryWithHelpers<any, DocType, ExtractQueryHelpers<M>>, ...args: any[]) => any };
-
- /** Adds a method call to the queue. */
- queue(name: string, args: any[]): this;
-
- /** Removes the given `path` (or [`paths`]). */
- remove(paths: string | Array<string>): this;
-
- /** Returns an Array of path strings that are required by this schema. */
- requiredPaths(invalidate?: boolean): string[];
-
- /** Sets a schema option. */
- set<K extends keyof SchemaOptions>(key: K, value: SchemaOptions[K], _tags?: any): this;
-
- /** Adds static "class" methods to Models compiled from this schema. */
- static(name: string, fn: (this: M, ...args: any[]) => any): this;
- static(obj: { [name: string]: (this: M, ...args: any[]) => any }): this;
-
- /** Object of currently defined statics on this schema. */
- statics: { [name: string]: (this: M, ...args: any[]) => any };
-
- /** Creates a virtual type with the given name. */
- virtual(name: string, options?: VirtualTypeOptions): VirtualType;
-
- /** Object of currently defined virtuals on this schema */
- virtuals: any;
-
- /** Returns the virtual type with the given `name`. */
- virtualpath(name: string): VirtualType | null;
- }
-
- type SchemaDefinitionWithBuiltInClass<T> = T extends number
- ? (typeof Number | 'number' | 'Number' | typeof Schema.Types.Number)
- : T extends string
- ? (typeof String | 'string' | 'String' | typeof Schema.Types.String)
- : T extends boolean
- ? (typeof Boolean | 'boolean' | 'Boolean' | typeof Schema.Types.Boolean)
- : T extends NativeDate
- ? (typeof NativeDate | 'date' | 'Date' | typeof Schema.Types.Date)
- : (Function | string);
-
- type SchemaDefinitionProperty<T = undefined> = SchemaDefinitionWithBuiltInClass<T> |
- SchemaTypeOptions<T extends undefined ? any : T> |
- typeof SchemaType |
- Schema<any, any, any> |
- Schema<any, any, any>[] |
- SchemaTypeOptions<T extends undefined ? any : T>[] |
- Function[] |
- SchemaDefinition<T> |
- SchemaDefinition<T>[];
-
- type SchemaDefinition<T = undefined> = T extends undefined
- ? { [path: string]: SchemaDefinitionProperty; }
- : { [path in keyof T]?: SchemaDefinitionProperty<T[path]>; };
-
- interface SchemaOptions {
- /**
- * By default, Mongoose's init() function creates all the indexes defined in your model's schema by
- * calling Model.createIndexes() after you successfully connect to MongoDB. If you want to disable
- * automatic index builds, you can set autoIndex to false.
- */
- autoIndex?: boolean;
- /**
- * If set to `true`, Mongoose will call Model.createCollection() to create the underlying collection
- * in MongoDB if autoCreate is set to true. Calling createCollection() sets the collection's default
- * collation based on the collation option and establishes the collection as a capped collection if
- * you set the capped schema option.
- */
- autoCreate?: boolean;
- /**
- * By default, mongoose buffers commands when the connection goes down until the driver manages to reconnect.
- * To disable buffering, set bufferCommands to false.
- */
- bufferCommands?: boolean;
- /**
- * If bufferCommands is on, this option sets the maximum amount of time Mongoose buffering will wait before
- * throwing an error. If not specified, Mongoose will use 10000 (10 seconds).
- */
- bufferTimeoutMS?: number;
- /**
- * Mongoose supports MongoDBs capped collections. To specify the underlying MongoDB collection be capped, set
- * the capped option to the maximum size of the collection in bytes.
- */
- capped?: boolean | number | { size?: number; max?: number; autoIndexId?: boolean; };
- /** Sets a default collation for every query and aggregation. */
- collation?: mongodb.CollationOptions;
- /**
- * Mongoose by default produces a collection name by passing the model name to the utils.toCollectionName
- * method. This method pluralizes the name. Set this option if you need a different name for your collection.
- */
- collection?: string;
- /**
- * When you define a [discriminator](/docs/discriminators.html), Mongoose adds a path to your
- * schema that stores which discriminator a document is an instance of. By default, Mongoose
- * adds an `__t` path, but you can set `discriminatorKey` to overwrite this default.
- */
- discriminatorKey?: string;
- /** defaults to false. */
- emitIndexErrors?: boolean;
- excludeIndexes?: any;
- /**
- * Mongoose assigns each of your schemas an id virtual getter by default which returns the document's _id field
- * cast to a string, or in the case of ObjectIds, its hexString.
- */
- id?: boolean;
- /**
- * Mongoose assigns each of your schemas an _id field by default if one is not passed into the Schema
- * constructor. The type assigned is an ObjectId to coincide with MongoDB's default behavior. If you
- * don't want an _id added to your schema at all, you may disable it using this option.
- */
- _id?: boolean;
- /**
- * Mongoose will, by default, "minimize" schemas by removing empty objects. This behavior can be
- * overridden by setting minimize option to false. It will then store empty objects.
- */
- minimize?: boolean;
- /**
- * Optimistic concurrency is a strategy to ensure the document you're updating didn't change between when you
- * loaded it using find() or findOne(), and when you update it using save(). Set to `true` to enable
- * optimistic concurrency.
- */
- optimisticConcurrency?: boolean;
- /**
- * Allows setting query#read options at the schema level, providing us a way to apply default ReadPreferences
- * to all queries derived from a model.
- */
- read?: string;
- /** Allows setting write concern at the schema level. */
- writeConcern?: WriteConcern;
- /** defaults to true. */
- safe?: boolean | { w?: number | string; wtimeout?: number; j?: boolean };
- /**
- * The shardKey option is used when we have a sharded MongoDB architecture. Each sharded collection is
- * given a shard key which must be present in all insert/update operations. We just need to set this
- * schema option to the same shard key and we'll be all set.
- */
- shardKey?: Record<string, unknown>;
- /**
- * The strict option, (enabled by default), ensures that values passed to our model constructor that were not
- * specified in our schema do not get saved to the db.
- */
- strict?: boolean | 'throw';
- /**
- * equal to `strict` by default, may be `false`, `true`, or `'throw'`. Sets the default
- * [strictQuery](https://mongoosejs.com/docs/guide.html#strictQuery) mode for schemas.
- */
- strictQuery?: boolean | 'throw';
- /** Exactly the same as the toObject option but only applies when the document's toJSON method is called. */
- toJSON?: ToObjectOptions;
- /**
- * Documents have a toObject method which converts the mongoose document into a plain JavaScript object.
- * This method accepts a few options. Instead of applying these options on a per-document basis, we may
- * declare the options at the schema level and have them applied to all of the schema's documents by
- * default.
- */
- toObject?: ToObjectOptions;
- /**
- * By default, if you have an object with key 'type' in your schema, mongoose will interpret it as a
- * type declaration. However, for applications like geoJSON, the 'type' property is important. If you want to
- * control which key mongoose uses to find type declarations, set the 'typeKey' schema option.
- */
- typeKey?: string;
-
- /**
- * By default, documents are automatically validated before they are saved to the database. This is to
- * prevent saving an invalid document. If you want to handle validation manually, and be able to save
- * objects which don't pass validation, you can set validateBeforeSave to false.
- */
- validateBeforeSave?: boolean;
- /**
- * The versionKey is a property set on each document when first created by Mongoose. This keys value
- * contains the internal revision of the document. The versionKey option is a string that represents
- * the path to use for versioning. The default is '__v'.
- */
- versionKey?: string | boolean;
- /**
- * By default, Mongoose will automatically select() any populated paths for you, unless you explicitly exclude them.
- */
- selectPopulatedPaths?: boolean;
- /**
- * skipVersioning allows excluding paths from versioning (i.e., the internal revision will not be
- * incremented even if these paths are updated). DO NOT do this unless you know what you're doing.
- * For subdocuments, include this on the parent document using the fully qualified path.
- */
- skipVersioning?: any;
- /**
- * Validation errors in a single nested schema are reported
- * both on the child and on the parent schema.
- * Set storeSubdocValidationError to false on the child schema
- * to make Mongoose only report the parent error.
- */
- storeSubdocValidationError?: boolean;
- /**
- * The timestamps option tells mongoose to assign createdAt and updatedAt fields to your schema. The type
- * assigned is Date. By default, the names of the fields are createdAt and updatedAt. Customize the
- * field names by setting timestamps.createdAt and timestamps.updatedAt.
- */
- timestamps?: boolean | SchemaTimestampsConfig;
- }
-
- interface SchemaTimestampsConfig {
- createdAt?: boolean | string;
- updatedAt?: boolean | string;
- currentTime?: () => (NativeDate | number);
- }
-
- type Unpacked<T> = T extends (infer U)[] ?
- U :
- T extends ReadonlyArray<infer U> ? U : T;
-
- type AnyArray<T> = T[] | ReadonlyArray<T>;
-
- export class SchemaTypeOptions<T> {
- type?:
- T extends string | number | boolean | NativeDate | Function ? SchemaDefinitionWithBuiltInClass<T> :
- T extends Schema<any, any, any> ? T :
- T extends Map<any, any> ? SchemaDefinition<typeof Map> :
- T extends Buffer ? SchemaDefinition<typeof Buffer> :
- T extends object[] ? (AnyArray<Schema<any, any, any>> | AnyArray<SchemaDefinition<Unpacked<T>>> | AnyArray<SchemaTypeOptions<Unpacked<T>>>) :
- T extends string[] ? AnyArray<SchemaDefinitionWithBuiltInClass<string>> | AnyArray<SchemaTypeOptions<string>> :
- T extends number[] ? AnyArray<SchemaDefinitionWithBuiltInClass<number>> | AnyArray<SchemaTypeOptions<number>> :
- T extends boolean[] ? AnyArray<SchemaDefinitionWithBuiltInClass<boolean>> | AnyArray<SchemaTypeOptions<boolean>> :
- T extends Function[] ? AnyArray<SchemaDefinitionWithBuiltInClass<Function>> | AnyArray<SchemaTypeOptions<Unpacked<T>>> :
- T | typeof SchemaType | Schema<any, any, any> | SchemaDefinition<T>;
-
- /** Defines a virtual with the given name that gets/sets this path. */
- alias?: string;
-
- /** Function or object describing how to validate this schematype. See [validation docs](https://mongoosejs.com/docs/validation.html). */
- validate?: RegExp | [RegExp, string] | Function | [Function, string] | ValidateOpts<T> | ValidateOpts<T>[];
-
- /** Allows overriding casting logic for this individual path. If a string, the given string overwrites Mongoose's default cast error message. */
- cast?: string;
-
- /**
- * If true, attach a required validator to this path, which ensures this path
- * path cannot be set to a nullish value. If a function, Mongoose calls the
- * function and only checks for nullish values if the function returns a truthy value.
- */
- required?: boolean | (() => boolean) | [boolean, string] | [() => boolean, string];
-
- /**
- * The default value for this path. If a function, Mongoose executes the function
- * and uses the return value as the default.
- */
- default?: T | ((this: any, doc: any) => T);
-
- /**
- * The model that `populate()` should use if populating this path.
- */
- ref?: string | Model<any> | ((this: any, doc: any) => string | Model<any>);
-
- /**
- * Whether to include or exclude this path by default when loading documents
- * using `find()`, `findOne()`, etc.
- */
- select?: boolean | number;
-
- /**
- * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose will
- * build an index on this path when the model is compiled.
- */
- index?: boolean | number | IndexOptions | '2d' | '2dsphere' | 'hashed' | 'text';
-
- /**
- * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose
- * will build a unique index on this path when the
- * model is compiled. [The `unique` option is **not** a validator](/docs/validation.html#the-unique-option-is-not-a-validator).
- */
- unique?: boolean | number;
-
- /**
- * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose will
- * disallow changes to this path once the document is saved to the database for the first time. Read more
- * about [immutability in Mongoose here](http://thecodebarbarian.com/whats-new-in-mongoose-5-6-immutable-properties.html).
- */
- immutable?: boolean | ((this: any, doc: any) => boolean);
-
- /**
- * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose will
- * build a sparse index on this path.
- */
- sparse?: boolean | number;
-
- /**
- * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose
- * will build a text index on this path.
- */
- text?: boolean | number | any;
-
- /**
- * Define a transform function for this individual schema type.
- * Only called when calling `toJSON()` or `toObject()`.
- */
- transform?: (this: any, val: T) => any;
-
- /** defines a custom getter for this property using [`Object.defineProperty()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty). */
- get?: (value: T, doc?: this) => any;
-
- /** defines a custom setter for this property using [`Object.defineProperty()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty). */
- set?: (value: any, priorVal?: T, doc?: this) => any;
-
- /** array of allowed values for this path. Allowed for strings, numbers, and arrays of strings */
- enum?: Array<string | number | null> | ReadonlyArray<string | number | null> | { values: Array<string | number | null> | ReadonlyArray<string | number | null>, message?: string } | { [path: string]: string | number | null };
-
- /** The default [subtype](http://bsonspec.org/spec.html) associated with this buffer when it is stored in MongoDB. Only allowed for buffer paths */
- subtype?: number
-
- /** The minimum value allowed for this path. Only allowed for numbers and dates. */
- min?: number | NativeDate | [number, string] | [NativeDate, string] | readonly [number, string] | readonly [NativeDate, string];
-
- /** The maximum value allowed for this path. Only allowed for numbers and dates. */
- max?: number | NativeDate | [number, string] | [NativeDate, string] | readonly [number, string] | readonly [NativeDate, string];
-
- /** Defines a TTL index on this path. Only allowed for dates. */
- expires?: string | number;
-
- /** If `true`, Mongoose will skip gathering indexes on subpaths. Only allowed for subdocuments and subdocument arrays. */
- excludeIndexes?: boolean;
-
- /** If set, overrides the child schema's `_id` option. Only allowed for subdocuments and subdocument arrays. */
- _id?: boolean;
-
- /** If set, specifies the type of this map's values. Mongoose will cast this map's values to the given type. */
- of?: Function | SchemaDefinitionProperty<any>;
-
- /** If true, uses Mongoose's default `_id` settings. Only allowed for ObjectIds */
- auto?: boolean;
-
- /** Attaches a validator that succeeds if the data string matches the given regular expression, and fails otherwise. */
- match?: RegExp | [RegExp, string] | readonly [RegExp, string];
-
- /** If truthy, Mongoose will add a custom setter that lowercases this string using JavaScript's built-in `String#toLowerCase()`. */
- lowercase?: boolean;
-
- /** If truthy, Mongoose will add a custom setter that removes leading and trailing whitespace using JavaScript's built-in `String#trim()`. */
- trim?: boolean;
-
- /** If truthy, Mongoose will add a custom setter that uppercases this string using JavaScript's built-in `String#toUpperCase()`. */
- uppercase?: boolean;
-
- /** If set, Mongoose will add a custom validator that ensures the given string's `length` is at least the given number. */
- minlength?: number | [number, string] | readonly [number, string];
-
- /** If set, Mongoose will add a custom validator that ensures the given string's `length` is at most the given number. */
- maxlength?: number | [number, string] | readonly [number, string];
-
- [other: string]: any;
- }
-
- export type RefType =
- | number
- | string
- | Buffer
- | undefined
- | mongoose.Types.ObjectId
- | mongoose.Types.Buffer
- | typeof mongoose.Schema.Types.Number
- | typeof mongoose.Schema.Types.String
- | typeof mongoose.Schema.Types.Buffer
- | typeof mongoose.Schema.Types.ObjectId;
-
- /**
- * Reference another Model
- */
- export type PopulatedDoc<
- PopulatedType,
- RawId extends RefType = (PopulatedType extends { _id?: RefType; } ? NonNullable<PopulatedType['_id']> : mongoose.Types.ObjectId) | undefined
- > = PopulatedType | RawId;
-
- interface IndexOptions extends mongodb.CreateIndexesOptions {
- /**
- * `expires` utilizes the `ms` module from [guille](https://github.com/guille/) allowing us to use a friendlier syntax:
- *
- * @example
- * ```js
- * const schema = new Schema({ prop1: Date });
- *
- * // expire in 24 hours
- * schema.index({ prop1: 1 }, { expires: 60*60*24 })
- *
- * // expire in 24 hours
- * schema.index({ prop1: 1 }, { expires: '24h' })
- *
- * // expire in 1.5 hours
- * schema.index({ prop1: 1 }, { expires: '1.5h' })
- *
- * // expire in 7 days
- * schema.index({ prop1: 1 }, { expires: '7d' })
- * ```
- */
- expires?: number | string;
- weights?: AnyObject;
- }
-
- interface ValidatorProps {
- path: string;
- value: any;
- }
-
- interface ValidatorMessageFn {
- (props: ValidatorProps): string;
- }
-
- interface ValidateFn<T> {
- (value: T): boolean;
- }
-
- interface LegacyAsyncValidateFn<T> {
- (value: T, done: (result: boolean) => void): void;
- }
-
- interface AsyncValidateFn<T> {
- (value: any): Promise<boolean>;
- }
-
- interface ValidateOpts<T> {
- msg?: string;
- message?: string | ValidatorMessageFn;
- type?: string;
- validator: ValidateFn<T> | LegacyAsyncValidateFn<T> | AsyncValidateFn<T>;
- }
-
- interface VirtualTypeOptions {
- /** If `ref` is not nullish, this becomes a populated virtual. */
- ref?: string | Function;
-
- /** The local field to populate on if this is a populated virtual. */
- localField?: string | Function;
-
- /** The foreign field to populate on if this is a populated virtual. */
- foreignField?: string | Function;
-
- /**
- * By default, a populated virtual is an array. If you set `justOne`,
- * the populated virtual will be a single doc or `null`.
- */
- justOne?: boolean;
-
- /** If you set this to `true`, Mongoose will call any custom getters you defined on this virtual. */
- getters?: boolean;
-
- /**
- * If you set this to `true`, `populate()` will set this virtual to the number of populated
- * documents, as opposed to the documents themselves, using `Query#countDocuments()`.
- */
- count?: boolean;
-
- /** Add an extra match condition to `populate()`. */
- match?: FilterQuery<any> | Function;
-
- /** Add a default `limit` to the `populate()` query. */
- limit?: number;
-
- /** Add a default `skip` to the `populate()` query. */
- skip?: number;
-
- /**
- * For legacy reasons, `limit` with `populate()` may give incorrect results because it only
- * executes a single query for every document being populated. If you set `perDocumentLimit`,
- * Mongoose will ensure correct `limit` per document by executing a separate query for each
- * document to `populate()`. For example, `.find().populate({ path: 'test', perDocumentLimit: 2 })`
- * will execute 2 additional queries if `.find()` returns 2 documents.
- */
- perDocumentLimit?: number;
-
- /** Additional options like `limit` and `lean`. */
- options?: QueryOptions & { match?: AnyObject };
-
- /** Additional options for plugins */
- [extra: string]: any;
- }
-
- class VirtualType {
- /** Applies getters to `value`. */
- applyGetters(value: any, doc: Document): any;
-
- /** Applies setters to `value`. */
- applySetters(value: any, doc: Document): any;
-
- /** Adds a custom getter to this virtual. */
- get(fn: Function): this;
-
- /** Adds a custom setter to this virtual. */
- set(fn: Function): this;
- }
-
- namespace Schema {
- namespace Types {
- class Array extends SchemaType implements AcceptsDiscriminator {
- /** This schema type's name, to defend against minifiers that mangle function names. */
- static schemaName: string;
-
- static options: { castNonArrays: boolean; };
-
- discriminator<D>(name: string | number, schema: Schema, value?: string): Model<D>;
- discriminator<T, U>(name: string | number, schema: Schema<T, U>, value?: string): U;
-
- /** The schematype embedded in this array */
- caster?: SchemaType;
-
- /**
- * Adds an enum validator if this is an array of strings or numbers. Equivalent to
- * `SchemaString.prototype.enum()` or `SchemaNumber.prototype.enum()`
- */
- enum(vals: string[] | number[]): this;
- }
-
- class Boolean extends SchemaType {
- /** This schema type's name, to defend against minifiers that mangle function names. */
- static schemaName: string;
-
- /** Configure which values get casted to `true`. */
- static convertToTrue: Set<any>;
-
- /** Configure which values get casted to `false`. */
- static convertToFalse: Set<any>;
- }
-
- class Buffer extends SchemaType {
- /** This schema type's name, to defend against minifiers that mangle function names. */
- static schemaName: string;
-
- /**
- * Sets the default [subtype](https://studio3t.com/whats-new/best-practices-uuid-mongodb/)
- * for this buffer. You can find a [list of allowed subtypes here](http://api.mongodb.com/python/current/api/bson/binary.html).
- */
- subtype(subtype: number): this;
- }
-
- class Date extends SchemaType {
- /** This schema type's name, to defend against minifiers that mangle function names. */
- static schemaName: string;
-
- /** Declares a TTL index (rounded to the nearest second) for _Date_ types only. */
- expires(when: number | string): this;
-
- /** Sets a maximum date validator. */
- max(value: NativeDate, message: string): this;
-
- /** Sets a minimum date validator. */
- min(value: NativeDate, message: string): this;
- }
-
- class Decimal128 extends SchemaType {
- /** This schema type's name, to defend against minifiers that mangle function names. */
- static schemaName: string;
- }
-
- class DocumentArray extends SchemaType implements AcceptsDiscriminator {
- /** This schema type's name, to defend against minifiers that mangle function names. */
- static schemaName: string;
-
- static options: { castNonArrays: boolean; };
-
- discriminator<D>(name: string | number, schema: Schema, value?: string): Model<D>;
- discriminator<T, U>(name: string | number, schema: Schema<T, U>, value?: string): U;
-
- /** The schema used for documents in this array */
- schema: Schema;
-
- /** The constructor used for subdocuments in this array */
- caster?: typeof Types.Subdocument;
- }
-
- class Map extends SchemaType {
- /** This schema type's name, to defend against minifiers that mangle function names. */
- static schemaName: string;
- }
-
- class Mixed extends SchemaType {
- /** This schema type's name, to defend against minifiers that mangle function names. */
- static schemaName: string;
- }
-
- class Number extends SchemaType {
- /** This schema type's name, to defend against minifiers that mangle function names. */
- static schemaName: string;
-
- /** Sets a enum validator */
- enum(vals: number[]): this;
-
- /** Sets a maximum number validator. */
- max(value: number, message: string): this;
-
- /** Sets a minimum number validator. */
- min(value: number, message: string): this;
- }
-
- class ObjectId extends SchemaType {
- /** This schema type's name, to defend against minifiers that mangle function names. */
- static schemaName: string;
-
- /** Adds an auto-generated ObjectId default if turnOn is true. */
- auto(turnOn: boolean): this;
- }
-
- class Subdocument extends SchemaType implements AcceptsDiscriminator {
- /** This schema type's name, to defend against minifiers that mangle function names. */
- static schemaName: string;
-
- /** The document's schema */
- schema: Schema;
-
- discriminator<D>(name: string | number, schema: Schema, value?: string): Model<D>;
- discriminator<T, U>(name: string | number, schema: Schema<T, U>, value?: string): U;
- }
-
- class String extends SchemaType {
- /** This schema type's name, to defend against minifiers that mangle function names. */
- static schemaName: string;
-
- /** Adds an enum validator */
- enum(vals: string[] | any): this;
-
- /** Adds a lowercase [setter](http://mongoosejs.com/docs/api.html#schematype_SchemaType-set). */
- lowercase(shouldApply?: boolean): this;
-
- /** Sets a regexp validator. */
- match(value: RegExp, message: string): this;
-
- /** Sets a maximum length validator. */
- maxlength(value: number, message: string): this;
-
- /** Sets a minimum length validator. */
- minlength(value: number, message: string): this;
-
- /** Adds a trim [setter](http://mongoosejs.com/docs/api.html#schematype_SchemaType-set). */
- trim(shouldTrim?: boolean): this;
-
- /** Adds an uppercase [setter](http://mongoosejs.com/docs/api.html#schematype_SchemaType-set). */
- uppercase(shouldApply?: boolean): this;
- }
- }
- }
-
- namespace Types {
- class Array<T> extends global.Array<T> {
- /** Pops the array atomically at most one time per document `save()`. */
- $pop(): T;
-
- /** Atomically shifts the array at most one time per document `save()`. */
- $shift(): T;
-
- /** Adds values to the array if not already present. */
- addToSet(...args: any[]): any[];
-
- isMongooseArray: true;
-
- /** Pushes items to the array non-atomically. */
- nonAtomicPush(...args: any[]): number;
-
- /** Wraps [`Array#push`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/push) with proper change tracking. */
- push(...args: any[]): number;
-
- /**
- * Pulls items from the array atomically. Equality is determined by casting
- * the provided value to an embedded document and comparing using
- * [the `Document.equals()` function.](./api.html#document_Document-equals)
- */
- pull(...args: any[]): this;
-
- /**
- * Alias of [pull](#mongoosearray_MongooseArray-pull)
- */
- remove(...args: any[]): this;
-
- /** Sets the casted `val` at index `i` and marks the array modified. */
- set(i: number, val: T): this;
-
- /** Atomically shifts the array at most one time per document `save()`. */
- shift(): T;
-
- /** Returns a native js Array. */
- toObject(options?: ToObjectOptions): any;
- toObject<T>(options?: ToObjectOptions): T;
-
- /** Wraps [`Array#unshift`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/unshift) with proper change tracking. */
- unshift(...args: any[]): number;
- }
-
- class Buffer extends global.Buffer {
- /** Sets the subtype option and marks the buffer modified. */
- subtype(subtype: number | ToObjectOptions): void;
-
- /** Converts this buffer to its Binary type representation. */
- toObject(subtype?: number): mongodb.Binary;
- }
-
- class Decimal128 extends mongodb.Decimal128 { }
-
- class DocumentArray<T> extends Types.Array<T> {
- /** DocumentArray constructor */
- constructor(values: any[]);
-
- isMongooseDocumentArray: true;
-
- /** Creates a subdocument casted to this schema. */
- create(obj: any): T;
-
- /** Searches array items for the first document with a matching _id. */
- id(id: any): T | null;
-
- push(...args: (AnyKeys<T> & AnyObject)[]): number;
- }
-
- class Map<V> extends global.Map<string, V> {
- /** Converts a Mongoose map into a vanilla JavaScript map. */
- toObject(options?: ToObjectOptions & { flattenMaps?: boolean }): any;
- }
-
- class ObjectId extends mongodb.ObjectId {
- _id: this;
- }
-
- class Subdocument extends Document {
- $isSingleNested: true;
-
- /** Returns the top level document of this sub-document. */
- ownerDocument(): Document;
-
- /** Returns this sub-documents parent document. */
- parent(): Document;
-
- /** Returns this sub-documents parent document. */
- $parent(): Document;
- }
- }
-
- type ReturnsNewDoc = { new: true } | { returnOriginal: false } | {returnDocument: 'after'};
-
- type QueryWithHelpers<ResultType, DocType, THelpers = {}, RawDocType = DocType> = Query<ResultType, DocType, THelpers, RawDocType> & THelpers;
-
- class Query<ResultType, DocType, THelpers = {}, RawDocType = DocType> {
- _mongooseOptions: MongooseQueryOptions;
-
- /**
- * Returns a wrapper around a [mongodb driver cursor](http://mongodb.github.io/node-mongodb-native/2.1/api/Cursor.html).
- * A QueryCursor exposes a Streams3 interface, as well as a `.next()` function.
- * This is equivalent to calling `.cursor()` with no arguments.
- */
- [Symbol.asyncIterator](): AsyncIterableIterator<DocType>;
-
- /** Executes the query */
- exec(): Promise<ResultType>;
- exec(callback?: Callback<ResultType>): void;
- // @todo: this doesn't seem right
- exec(callback?: Callback<ResultType>): Promise<ResultType> | any;
-
- $where(argument: string | Function): QueryWithHelpers<DocType[], DocType, THelpers, RawDocType>;
-
- /** Specifies an `$all` query condition. When called with one argument, the most recent path passed to `where()` is used. */
- all(val: Array<any>): this;
- all(path: string, val: Array<any>): this;
-
- /** Sets the allowDiskUse option for the query (ignored for < 4.4.0) */
- allowDiskUse(value: boolean): this;
-
- /** Specifies arguments for an `$and` condition. */
- and(array: FilterQuery<DocType>[]): this;
-
- /** Specifies the batchSize option. */
- batchSize(val: number): this;
-
- /** Specifies a `$box` condition */
- box(val: any): this;
- box(lower: number[], upper: number[]): this;
-
- /**
- * Casts this query to the schema of `model`.
- *
- * @param {Model} [model] the model to cast to. If not set, defaults to `this.model`
- * @param {Object} [obj] If not set, defaults to this query's conditions
- * @return {Object} the casted `obj`
- */
- cast(model?: Model<any, THelpers> | null, obj?: any): any;
-
- /**
- * Executes the query returning a `Promise` which will be
- * resolved with either the doc(s) or rejected with the error.
- * Like `.then()`, but only takes a rejection handler.
- */
- catch: Promise<ResultType>['catch'];
-
- /** Specifies a `$center` or `$centerSphere` condition. */
- circle(area: any): this;
- circle(path: string, area: any): this;
-
- /** Make a copy of this query so you can re-execute it. */
- clone(): this;
-
- /** Adds a collation to this op (MongoDB 3.4 and up) */
- collation(value: mongodb.CollationOptions): this;
-
- /** Specifies the `comment` option. */
- comment(val: string): this;
-
- /** Specifies this query as a `count` query. */
- count(callback?: Callback<number>): QueryWithHelpers<number, DocType, THelpers, RawDocType>;
- count(criteria: FilterQuery<DocType>, callback?: Callback<number>): QueryWithHelpers<number, DocType, THelpers, RawDocType>;
-
- /** Specifies this query as a `countDocuments` query. */
- countDocuments(callback?: Callback<number>): QueryWithHelpers<number, DocType, THelpers, RawDocType>;
- countDocuments(criteria: FilterQuery<DocType>, callback?: Callback<number>): QueryWithHelpers<number, DocType, THelpers, RawDocType>;
-
- /**
- * Returns a wrapper around a [mongodb driver cursor](http://mongodb.github.io/node-mongodb-native/2.1/api/Cursor.html).
- * A QueryCursor exposes a Streams3 interface, as well as a `.next()` function.
- */
- cursor(options?: any): QueryCursor<DocType>;
-
- /**
- * Declare and/or execute this query as a `deleteMany()` operation. Works like
- * remove, except it deletes _every_ document that matches `filter` in the
- * collection, regardless of the value of `single`.
- */
- deleteMany(filter?: FilterQuery<DocType>, options?: QueryOptions, callback?: Callback): QueryWithHelpers<any, DocType, THelpers, RawDocType>;
- deleteMany(filter: FilterQuery<DocType>, callback: Callback): QueryWithHelpers<any, DocType, THelpers, RawDocType>;
- deleteMany(callback: Callback): QueryWithHelpers<any, DocType, THelpers, RawDocType>;
-
- /**
- * Declare and/or execute this query as a `deleteOne()` operation. Works like
- * remove, except it deletes at most one document regardless of the `single`
- * option.
- */
- deleteOne(filter?: FilterQuery<DocType>, options?: QueryOptions, callback?: Callback): QueryWithHelpers<any, DocType, THelpers, RawDocType>;
- deleteOne(filter: FilterQuery<DocType>, callback: Callback): QueryWithHelpers<any, DocType, THelpers, RawDocType>;
- deleteOne(callback: Callback): QueryWithHelpers<any, DocType, THelpers, RawDocType>;
-
- /** Creates a `distinct` query: returns the distinct values of the given `field` that match `filter`. */
- distinct(field: string, filter?: FilterQuery<DocType>, callback?: Callback<number>): QueryWithHelpers<Array<any>, DocType, THelpers, RawDocType>;
-
- /** Specifies a `$elemMatch` query condition. When called with one argument, the most recent path passed to `where()` is used. */
- elemMatch(val: Function | any): this;
- elemMatch(path: string, val: Function | any): this;
-
- /**
- * Gets/sets the error flag on this query. If this flag is not null or
- * undefined, the `exec()` promise will reject without executing.
- */
- error(): NativeError | null;
- error(val: NativeError | null): this;
-
- /** Specifies the complementary comparison value for paths specified with `where()` */
- equals(val: any): this;
-
- /** Creates a `estimatedDocumentCount` query: counts the number of documents in the collection. */
- estimatedDocumentCount(options?: QueryOptions, callback?: Callback<number>): QueryWithHelpers<number, DocType, THelpers, RawDocType>;
-
- /** Specifies a `$exists` query condition. When called with one argument, the most recent path passed to `where()` is used. */
- exists(val: boolean): this;
- exists(path: string, val: boolean): this;
-
- /**
- * Sets the [`explain` option](https://docs.mongodb.com/manual/reference/method/cursor.explain/),
- * which makes this query return detailed execution stats instead of the actual
- * query result. This method is useful for determining what index your queries
- * use.
- */
- explain(verbose?: string): this;
-
- /** Creates a `find` query: gets a list of documents that match `filter`. */
- find(callback?: Callback<DocType[]>): QueryWithHelpers<Array<DocType>, DocType, THelpers, RawDocType>;
- find(filter: FilterQuery<DocType>, callback?: Callback<DocType[]>): QueryWithHelpers<Array<DocType>, DocType, THelpers, RawDocType>;
- find(filter: FilterQuery<DocType>, projection?: any | null, options?: QueryOptions | null, callback?: Callback<DocType[]>): QueryWithHelpers<Array<DocType>, DocType, THelpers, RawDocType>;
-
- /** Declares the query a findOne operation. When executed, the first found document is passed to the callback. */
- findOne(filter?: FilterQuery<DocType>, projection?: any | null, options?: QueryOptions | null, callback?: Callback<DocType | null>): QueryWithHelpers<DocType | null, DocType, THelpers, RawDocType>;
-
- /** Creates a `findOneAndDelete` query: atomically finds the given document, deletes it, and returns the document as it was before deletion. */
- findOneAndDelete(filter?: FilterQuery<DocType>, options?: QueryOptions | null, callback?: (err: CallbackError, doc: DocType | null, res: any) => void): QueryWithHelpers<DocType | null, DocType, THelpers, RawDocType>;
-
- /** Creates a `findOneAndRemove` query: atomically finds the given document and deletes it. */
- findOneAndRemove(filter?: FilterQuery<DocType>, options?: QueryOptions | null, callback?: (err: CallbackError, doc: DocType | null, res: any) => void): QueryWithHelpers<DocType | null, DocType, THelpers, RawDocType>;
-
- /** Creates a `findOneAndUpdate` query: atomically find the first document that matches `filter` and apply `update`. */
- findOneAndUpdate(filter: FilterQuery<DocType>, update: UpdateQuery<DocType>, options: QueryOptions & { rawResult: true }, callback?: (err: CallbackError, doc: DocType | null, res: mongodb.ModifyResult<DocType>) => void): QueryWithHelpers<mongodb.ModifyResult<DocType>, DocType, THelpers, RawDocType>;
- findOneAndUpdate(filter: FilterQuery<DocType>, update: UpdateQuery<DocType>, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: DocType, res: mongodb.ModifyResult<DocType>) => void): QueryWithHelpers<DocType, DocType, THelpers, RawDocType>;
- findOneAndUpdate(filter?: FilterQuery<DocType>, update?: UpdateQuery<DocType>, options?: QueryOptions | null, callback?: (err: CallbackError, doc: DocType | null, res: mongodb.ModifyResult<DocType>) => void): QueryWithHelpers<DocType | null, DocType, THelpers, RawDocType>;
-
- /** Creates a `findByIdAndDelete` query, filtering by the given `_id`. */
- findByIdAndDelete(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: CallbackError, doc: DocType | null, res: any) => void): QueryWithHelpers<DocType | null, DocType, THelpers, RawDocType>;
-
- /** Creates a `findOneAndUpdate` query, filtering by the given `_id`. */
- findByIdAndUpdate(id: mongodb.ObjectId | any, update: UpdateQuery<DocType>, options: QueryOptions & { rawResult: true }, callback?: (err: CallbackError, doc: any, res?: any) => void): QueryWithHelpers<any, DocType, THelpers, RawDocType>;
- findByIdAndUpdate(id: mongodb.ObjectId | any, update: UpdateQuery<DocType>, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: DocType, res?: any) => void): QueryWithHelpers<DocType, DocType, THelpers, RawDocType>;
- findByIdAndUpdate(id?: mongodb.ObjectId | any, update?: UpdateQuery<DocType>, options?: QueryOptions | null, callback?: (CallbackError: any, doc: DocType | null, res?: any) => void): QueryWithHelpers<DocType | null, DocType, THelpers, RawDocType>;
- findByIdAndUpdate(id: mongodb.ObjectId | any, update: UpdateQuery<DocType>, callback: (CallbackError: any, doc: DocType | null, res?: any) => void): QueryWithHelpers<DocType | null, DocType, THelpers, RawDocType>;
-
- /** Specifies a `$geometry` condition */
- geometry(object: { type: string, coordinates: any[] }): this;
-
- /**
- * For update operations, returns the value of a path in the update's `$set`.
- * Useful for writing getters/setters that can work with both update operations
- * and `save()`.
- */
- get(path: string): any;
-
- /** Returns the current query filter (also known as conditions) as a POJO. */
- getFilter(): FilterQuery<DocType>;
-
- /** Gets query options. */
- getOptions(): QueryOptions;
-
- /** Gets a list of paths to be populated by this query */
- getPopulatedPaths(): Array<string>;
-
- /** Returns the current query filter. Equivalent to `getFilter()`. */
- getQuery(): FilterQuery<DocType>;
-
- /** Returns the current update operations as a JSON object. */
- getUpdate(): UpdateQuery<DocType> | UpdateWithAggregationPipeline | null;
-
- /** Specifies a `$gt` query condition. When called with one argument, the most recent path passed to `where()` is used. */
- gt(val: number): this;
- gt(path: string, val: number): this;
-
- /** Specifies a `$gte` query condition. When called with one argument, the most recent path passed to `where()` is used. */
- gte(val: number): this;
- gte(path: string, val: number): this;
-
- /** Sets query hints. */
- hint(val: any): this;
-
- /** Specifies an `$in` query condition. When called with one argument, the most recent path passed to `where()` is used. */
- in(val: Array<any>): this;
- in(path: string, val: Array<any>): this;
-
- /** Declares an intersects query for `geometry()`. */
- intersects(arg?: any): this;
-
- /** Requests acknowledgement that this operation has been persisted to MongoDB's on-disk journal. */
- j(val: boolean | null): this;
-
- /** Sets the lean option. */
- lean<LeanResultType = RawDocType extends Document ? LeanDocumentOrArray<ResultType> : LeanDocumentOrArrayWithRawType<ResultType, RawDocType>>(val?: boolean | any): QueryWithHelpers<LeanResultType, DocType, THelpers, RawDocType>;
-
- /** Specifies the maximum number of documents the query will return. */
- limit(val: number): this;
-
- /** Specifies a `$lt` query condition. When called with one argument, the most recent path passed to `where()` is used. */
- lt(val: number): this;
- lt(path: string, val: number): this;
-
- /** Specifies a `$lte` query condition. When called with one argument, the most recent path passed to `where()` is used. */
- lte(val: number): this;
- lte(path: string, val: number): this;
-
- /**
- * Runs a function `fn` and treats the return value of `fn` as the new value
- * for the query to resolve to.
- */
- map<MappedType>(fn: (doc: ResultType) => MappedType): QueryWithHelpers<MappedType, DocType, THelpers, RawDocType>;
-
- /** Specifies an `$maxDistance` query condition. When called with one argument, the most recent path passed to `where()` is used. */
- maxDistance(val: number): this;
- maxDistance(path: string, val: number): this;
-
- /** Specifies the maxScan option. */
- maxScan(val: number): this;
-
- /**
- * Sets the [maxTimeMS](https://docs.mongodb.com/manual/reference/method/cursor.maxTimeMS/)
- * option. This will tell the MongoDB server to abort if the query or write op
- * has been running for more than `ms` milliseconds.
- */
- maxTimeMS(ms: number): this;
-
- /** Merges another Query or conditions object into this one. */
- merge(source: Query<any, any> | FilterQuery<DocType>): this;
-
- /** Specifies a `$mod` condition, filters documents for documents whose `path` property is a number that is equal to `remainder` modulo `divisor`. */
- mod(val: Array<number>): this;
- mod(path: string, val: Array<number>): this;
-
- /** The model this query was created from */
- model: typeof Model;
-
- /**
- * Getter/setter around the current mongoose-specific options for this query
- * Below are the current Mongoose-specific options.
- */
- mongooseOptions(val?: MongooseQueryOptions): MongooseQueryOptions;
-
- /** Specifies a `$ne` query condition. When called with one argument, the most recent path passed to `where()` is used. */
- ne(val: any): this;
- ne(path: string, val: any): this;
-
- /** Specifies a `$near` or `$nearSphere` condition */
- near(val: any): this;
- near(path: string, val: any): this;
-
- /** Specifies an `$nin` query condition. When called with one argument, the most recent path passed to `where()` is used. */
- nin(val: Array<any>): this;
- nin(path: string, val: Array<any>): this;
-
- /** Specifies arguments for an `$nor` condition. */
- nor(array: Array<FilterQuery<DocType>>): this;
-
- /** Specifies arguments for an `$or` condition. */
- or(array: Array<FilterQuery<DocType>>): this;
-
- /**
- * Make this query throw an error if no documents match the given `filter`.
- * This is handy for integrating with async/await, because `orFail()` saves you
- * an extra `if` statement to check if no document was found.
- */
- orFail(err?: NativeError | (() => NativeError)): QueryWithHelpers<NonNullable<ResultType>, DocType, THelpers, RawDocType>;
-
- /** Specifies a `$polygon` condition */
- polygon(...coordinatePairs: number[][]): this;
- polygon(path: string, ...coordinatePairs: number[][]): this;
-
- /** Specifies paths which should be populated with other documents. */
- populate<Paths = {}>(path: string | any, select?: string | any, model?: string | Model<any, THelpers>, match?: any): QueryWithHelpers<ResultType & Paths, DocType, THelpers, RawDocType>;
- populate<Paths = {}>(options: PopulateOptions | Array<PopulateOptions>): QueryWithHelpers<ResultType & Paths, DocType, THelpers, RawDocType>;
-
- /** Get/set the current projection (AKA fields). Pass `null` to remove the current projection. */
- projection(fields?: any | null): any;
-
- /** Determines the MongoDB nodes from which to read. */
- read(pref: string | mongodb.ReadPreferenceMode, tags?: any[]): this;
-
- /** Sets the readConcern option for the query. */
- readConcern(level: string): this;
-
- /** Specifies a `$regex` query condition. When called with one argument, the most recent path passed to `where()` is used. */
- regex(val: string | RegExp): this;
- regex(path: string, val: string | RegExp): this;
-
- /**
- * Declare and/or execute this query as a remove() operation. `remove()` is
- * deprecated, you should use [`deleteOne()`](#query_Query-deleteOne)
- * or [`deleteMany()`](#query_Query-deleteMany) instead.
- */
- remove(filter?: FilterQuery<DocType>, callback?: Callback<mongodb.UpdateResult>): Query<mongodb.UpdateResult, DocType, THelpers, RawDocType>;
-
- /**
- * Declare and/or execute this query as a replaceOne() operation. Same as
- * `update()`, except MongoDB will replace the existing document and will
- * not accept any [atomic](https://docs.mongodb.com/manual/tutorial/model-data-for-atomic-operations/#pattern) operators (`$set`, etc.)
- */
- replaceOne(filter?: FilterQuery<DocType>, replacement?: DocType | AnyObject, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers<any, DocType, THelpers, RawDocType>;
- replaceOne(filter?: FilterQuery<DocType>, replacement?: DocType | AnyObject, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers<any, DocType, THelpers, RawDocType>;
-
- /** Specifies which document fields to include or exclude (also known as the query "projection") */
- select(arg: string | any): this;
-
- /** Determines if field selection has been made. */
- selected(): boolean;
-
- /** Determines if exclusive field selection has been made. */
- selectedExclusively(): boolean;
-
- /** Determines if inclusive field selection has been made. */
- selectedInclusively(): boolean;
-
- /**
- * Sets the [MongoDB session](https://docs.mongodb.com/manual/reference/server-sessions/)
- * associated with this query. Sessions are how you mark a query as part of a
- * [transaction](/docs/transactions.html).
- */
- session(session: mongodb.ClientSession | null): this;
-
- /**
- * Adds a `$set` to this query's update without changing the operation.
- * This is useful for query middleware so you can add an update regardless
- * of whether you use `updateOne()`, `updateMany()`, `findOneAndUpdate()`, etc.
- */
- set(path: string | Record<string, unknown>, value?: any): this;
-
- /** Sets query options. Some options only make sense for certain operations. */
- setOptions(options: QueryOptions, overwrite?: boolean): this;
-
- /** Sets the query conditions to the provided JSON object. */
- setQuery(val: FilterQuery<DocType> | null): void;
-
- setUpdate(update: UpdateQuery<DocType> | UpdateWithAggregationPipeline): void;
-
- /** Specifies an `$size` query condition. When called with one argument, the most recent path passed to `where()` is used. */
- size(val: number): this;
- size(path: string, val: number): this;
-
- /** Specifies the number of documents to skip. */
- skip(val: number): this;
-
- /** Specifies a `$slice` projection for an array. */
- slice(val: number | Array<number>): this;
- slice(path: string, val: number | Array<number>): this;
-
- /** Specifies this query as a `snapshot` query. */
- snapshot(val?: boolean): this;
-
- /** Sets the sort order. If an object is passed, values allowed are `asc`, `desc`, `ascending`, `descending`, `1`, and `-1`. */
- sort(arg: string | any): this;
-
- /** Sets the tailable option (for use with capped collections). */
- tailable(bool?: boolean, opts?: {
- numberOfRetries?: number;
- tailableRetryInterval?: number;
- }): this;
-
- /**
- * Executes the query returning a `Promise` which will be
- * resolved with either the doc(s) or rejected with the error.
- */
- then: Promise<ResultType>['then'];
-
- /** Converts this query to a customized, reusable query constructor with all arguments and options retained. */
- toConstructor(): new (...args: any[]) => QueryWithHelpers<ResultType, DocType, THelpers, RawDocType>;
-
- /** Declare and/or execute this query as an update() operation. */
- update(filter?: FilterQuery<DocType>, update?: UpdateQuery<DocType> | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback<UpdateWriteOpResult>): QueryWithHelpers<UpdateWriteOpResult, DocType, THelpers, RawDocType>;
-
- /**
- * Declare and/or execute this query as an updateMany() operation. Same as
- * `update()`, except MongoDB will update _all_ documents that match
- * `filter` (as opposed to just the first one) regardless of the value of
- * the `multi` option.
- */
- updateMany(filter?: FilterQuery<DocType>, update?: UpdateQuery<DocType> | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback<UpdateWriteOpResult>): QueryWithHelpers<UpdateWriteOpResult, DocType, THelpers, RawDocType>;
-
- /**
- * Declare and/or execute this query as an updateOne() operation. Same as
- * `update()`, except it does not support the `multi` or `overwrite` options.
- */
- updateOne(filter?: FilterQuery<DocType>, update?: UpdateQuery<DocType> | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback<UpdateWriteOpResult>): QueryWithHelpers<UpdateWriteOpResult, DocType, THelpers, RawDocType>;
-
- /**
- * Sets the specified number of `mongod` servers, or tag set of `mongod` servers,
- * that must acknowledge this write before this write is considered successful.
- */
- w(val: string | number | null): this;
-
- /** Specifies a path for use with chaining. */
- where(path: string, val?: any): this;
- where(obj: object): this;
- where(): this;
-
- /** Defines a `$within` or `$geoWithin` argument for geo-spatial queries. */
- within(val?: any): this;
-
- /**
- * If [`w > 1`](/docs/api.html#query_Query-w), the maximum amount of time to
- * wait for this write to propagate through the replica set before this
- * operation fails. The default is `0`, which means no timeout.
- */
- wtimeout(ms: number): this;
- }
-
- export type QuerySelector<T> = {
- // Comparison
- $eq?: T;
- $gt?: T;
- $gte?: T;
- $in?: [T] extends AnyArray<any> ? Unpacked<T>[] : T[];
- $lt?: T;
- $lte?: T;
- $ne?: T;
- $nin?: [T] extends AnyArray<any> ? Unpacked<T>[] : T[];
- // Logical
- $not?: T extends string ? QuerySelector<T> | RegExp : QuerySelector<T>;
- // Element
- /**
- * When `true`, `$exists` matches the documents that contain the field,
- * including documents where the field value is null.
- */
- $exists?: boolean;
- $type?: string | number;
- // Evaluation
- $expr?: any;
- $jsonSchema?: any;
- $mod?: T extends number ? [number, number] : never;
- $regex?: T extends string ? RegExp | string : never;
- $options?: T extends string ? string : never;
- // Geospatial
- // TODO: define better types for geo queries
- $geoIntersects?: { $geometry: object };
- $geoWithin?: object;
- $near?: object;
- $nearSphere?: object;
- $maxDistance?: number;
- // Array
- // TODO: define better types for $all and $elemMatch
- $all?: T extends AnyArray<any> ? any[] : never;
- $elemMatch?: T extends AnyArray<any> ? object : never;
- $size?: T extends AnyArray<any> ? number : never;
- // Bitwise
- $bitsAllClear?: number | mongodb.Binary | number[];
- $bitsAllSet?: number | mongodb.Binary | number[];
- $bitsAnyClear?: number | mongodb.Binary | number[];
- $bitsAnySet?: number | mongodb.Binary | number[];
- };
-
- export type RootQuerySelector<T> = {
- /** @see https://docs.mongodb.com/manual/reference/operator/query/and/#op._S_and */
- $and?: Array<FilterQuery<T>>;
- /** @see https://docs.mongodb.com/manual/reference/operator/query/nor/#op._S_nor */
- $nor?: Array<FilterQuery<T>>;
- /** @see https://docs.mongodb.com/manual/reference/operator/query/or/#op._S_or */
- $or?: Array<FilterQuery<T>>;
- /** @see https://docs.mongodb.com/manual/reference/operator/query/text */
- $text?: {
- $search: string;
- $language?: string;
- $caseSensitive?: boolean;
- $diacriticSensitive?: boolean;
- };
- /** @see https://docs.mongodb.com/manual/reference/operator/query/where/#op._S_where */
- $where?: string | Function;
- /** @see https://docs.mongodb.com/manual/reference/operator/query/comment/#op._S_comment */
- $comment?: string;
- // we could not find a proper TypeScript generic to support nested queries e.g. 'user.friends.name'
- // this will mark all unrecognized properties as any (including nested queries)
- [key: string]: any;
- };
-
- type ReadonlyPartial<TSchema> = {
- [key in keyof TSchema]?: TSchema[key];
- };
-
- type MatchKeysAndValues<TSchema> = ReadonlyPartial<TSchema> & AnyObject;
-
- type ApplyBasicQueryCasting<T> = T extends mongodb.ObjectId ? T | string | (T | string)[] : // Allow strings for ObjectIds
- T extends string ? T | RegExp | T[] : // Allow RegExps for strings
- T extends (infer U)[] ? T | U : // Allow single array elements for arrays
- T | T[];
- type Condition<T> = ApplyBasicQueryCasting<T> | QuerySelector<ApplyBasicQueryCasting<T>>;
-
- type _FilterQuery<T> = {
- [P in keyof T]?: Condition<T[P]>;
- } &
- RootQuerySelector<T>;
-
- export type FilterQuery<T> = _FilterQuery<T>;
-
- type AddToSetOperators<Type> = {
- $each: Type;
- };
-
- type SortValues = -1 | 1 | 'asc' | 'desc';
-
- type ArrayOperator<Type> = {
- $each: Type;
- $slice?: number;
- $position?: number;
- $sort?: SortValues | Record<string, SortValues>;
- };
-
- type OnlyFieldsOfType<TSchema, FieldType = any, AssignableType = FieldType> = {
- [key in keyof TSchema]?: [Extract<TSchema[key], FieldType>] extends [never] ? never : AssignableType;
- };
-
- type NumericTypes = number | Decimal128 | mongodb.Double | mongodb.Int32 | mongodb.Long;
-
- type _UpdateQuery<TSchema> = {
- /** @see https://docs.mongodb.com/manual/reference/operator/update-field/ */
- $currentDate?: OnlyFieldsOfType<TSchema, NativeDate, true | { $type: 'date' | 'timestamp' }> & AnyObject;
- $inc?: OnlyFieldsOfType<TSchema, NumericTypes | undefined> & AnyObject;
- $min?: AnyKeys<TSchema> & AnyObject;
- $max?: AnyKeys<TSchema> & AnyObject;
- $mul?: OnlyFieldsOfType<TSchema, NumericTypes | undefined> & AnyObject;
- $rename?: { [key: string]: string };
- $set?: AnyKeys<TSchema> & AnyObject;
- $setOnInsert?: AnyKeys<TSchema> & AnyObject;
- $unset?: AnyKeys<TSchema> & AnyObject;
-
- /** @see https://docs.mongodb.com/manual/reference/operator/update-array/ */
- $addToSet?: OnlyFieldsOfType<TSchema, any[], any> & AnyObject;
- $pop?: OnlyFieldsOfType<TSchema, ReadonlyArray<any>, 1 | -1> & AnyObject;
- $pull?: OnlyFieldsOfType<TSchema, ReadonlyArray<any>, any> & AnyObject;
- $push?: OnlyFieldsOfType<TSchema, ReadonlyArray<any>, any> & AnyObject;
- $pullAll?: OnlyFieldsOfType<TSchema, ReadonlyArray<any>, any> & AnyObject;
-
- /** @see https://docs.mongodb.com/manual/reference/operator/update-bitwise/ */
- $bit?: {
- [key: string]: { [key in 'and' | 'or' | 'xor']?: number };
- };
- };
-
- type UpdateWithAggregationPipeline = UpdateAggregationStage[];
- type UpdateAggregationStage = { $addFields: any } |
- { $set: any } |
- { $project: any } |
- { $unset: any } |
- { $replaceRoot: any } |
- { $replaceWith: any };
-
- type __UpdateDefProperty<T> =
- [Extract<T, mongodb.ObjectId>] extends [never] ? T :
- T | string;
- type _UpdateQueryDef<T> = {
- [K in keyof T]: __UpdateDefProperty<T[K]>;
- };
-
- export type UpdateQuery<T> = (_UpdateQuery<_UpdateQueryDef<T>> & MatchKeysAndValues<_UpdateQueryDef<DeepPartial<T>>>);
-
- export type DocumentDefinition<T> = {
- [K in keyof Omit<T, Exclude<keyof Document, '_id' | 'id' | '__v'>>]:
- [Extract<T[K], mongodb.ObjectId>] extends [never]
- ? T[K] extends TreatAsPrimitives
- ? T[K]
- : LeanDocumentElement<T[K]>
- : T[K] | string;
- };
-
- export type FlattenMaps<T> = {
- [K in keyof T]: T[K] extends Map<any, any> ? AnyObject : FlattenMaps<T[K]>;
- };
-
- type actualPrimitives = string | boolean | number | bigint | symbol | null | undefined;
- type TreatAsPrimitives = actualPrimitives |
- NativeDate | RegExp | symbol | Error | BigInt | Types.ObjectId;
-
- type LeanType<T> =
- 0 extends (1 & T) ? T : // any
- T extends TreatAsPrimitives ? T : // primitives
- T extends Types.Subdocument ? Omit<LeanDocument<T>, '$isSingleNested' | 'ownerDocument' | 'parent'> : // subdocs
- LeanDocument<T>; // Documents and everything else
-
- type LeanArray<T extends unknown[]> = T extends unknown[][] ? LeanArray<T[number]>[] : LeanType<T[number]>[];
-
- export type _LeanDocument<T> = {
- [K in keyof T]: LeanDocumentElement<T[K]>;
- };
-
- // Keep this a separate type, to ensure that T is a naked type.
- // This way, the conditional type is distributive over union types.
- // This is required for PopulatedDoc.
- type LeanDocumentElement<T> =
- T extends unknown[] ? LeanArray<T> : // Array
- T extends Document ? LeanDocument<T> : // Subdocument
- T;
-
- export type SchemaDefinitionType<T> = T extends Document ? Omit<T, Exclude<keyof Document, '_id' | 'id' | '__v'>> : T;
- export type LeanDocument<T> = Omit<_LeanDocument<T>, Exclude<keyof Document, '_id' | 'id' | '__v'> | '$isSingleNested'>;
-
- type DeepPartial<T> = {
- [K in keyof T]?: DeepPartial<T[K]>;
- }
-
- export type LeanDocumentOrArray<T> = 0 extends (1 & T) ? T :
- T extends unknown[] ? LeanDocument<T[number]>[] :
- T extends Document ? LeanDocument<T> :
- T;
-
- export type LeanDocumentOrArrayWithRawType<T, RawDocType> = 0 extends (1 & T) ? T :
- T extends unknown[] ? RawDocType[] :
- T extends Document ? RawDocType :
- T;
-
- class QueryCursor<DocType> extends stream.Readable {
- [Symbol.asyncIterator](): AsyncIterableIterator<DocType>;
-
- /**
- * Adds a [cursor flag](http://mongodb.github.io/node-mongodb-native/2.2/api/Cursor.html#addCursorFlag).
- * Useful for setting the `noCursorTimeout` and `tailable` flags.
- */
- addCursorFlag(flag: string, value: boolean): this;
-
- /**
- * Marks this cursor as closed. Will stop streaming and subsequent calls to
- * `next()` will error.
- */
- close(): Promise<void>;
- close(callback: CallbackWithoutResult): void;
-
- /**
- * Execute `fn` for every document(s) in the cursor. If batchSize is provided
- * `fn` will be executed for each batch of documents. If `fn` returns a promise,
- * will wait for the promise to resolve before iterating on to the next one.
- * Returns a promise that resolves when done.
- */
- eachAsync(fn: (doc: DocType) => any, options?: { parallel?: number }): Promise<void>;
- eachAsync(fn: (doc: DocType[]) => any, options: { parallel?: number, batchSize: number }): Promise<void>;
- eachAsync(fn: (doc: DocType) => any, options?: { parallel?: number, batchSize?: number }, cb?: CallbackWithoutResult): void;
- eachAsync(fn: (doc: DocType[]) => any, options: { parallel?: number, batchSize: number }, cb?: CallbackWithoutResult): void;
-
- /**
- * Registers a transform function which subsequently maps documents retrieved
- * via the streams interface or `.next()`
- */
- map<ResultType>(fn: (res: DocType) => ResultType): QueryCursor<ResultType>;
-
- /**
- * Get the next document from this cursor. Will return `null` when there are
- * no documents left.
- */
- next(): Promise<DocType>;
- next(callback: Callback<DocType | null>): void;
-
- options: any;
- }
-
- class Aggregate<R> {
- /**
- * Sets an option on this aggregation. This function will be deprecated in a
- * future release. */
- addCursorFlag(flag: string, value: boolean): this;
-
- /**
- * Appends a new $addFields operator to this aggregate pipeline.
- * Requires MongoDB v3.4+ to work
- */
- addFields(arg: any): this;
-
- /** Sets the allowDiskUse option for the aggregation query (ignored for < 2.6.0) */
- allowDiskUse(value: boolean): this;
-
- /** Appends new operators to this aggregate pipeline */
- append(...args: any[]): this;
-
- /**
- * Executes the query returning a `Promise` which will be
- * resolved with either the doc(s) or rejected with the error.
- * Like [`.then()`](#query_Query-then), but only takes a rejection handler.
- */
- catch: Promise<R>['catch'];
-
- /** Adds a collation. */
- collation(options: mongodb.CollationOptions): this;
-
- /** Appends a new $count operator to this aggregate pipeline. */
- count(countName: string): this;
-
- /**
- * Sets the cursor option for the aggregation query (ignored for < 2.6.0).
- */
- cursor(options?: Record<string, unknown>): AggregationCursor;
-
- /** Executes the aggregate pipeline on the currently bound Model. */
- exec(callback?: Callback<R>): Promise<R>;
-
- /** Execute the aggregation with explain */
- explain(callback?: Callback): Promise<any>;
-
- /** Combines multiple aggregation pipelines. */
- facet(options: any): this;
-
- /** Appends new custom $graphLookup operator(s) to this aggregate pipeline, performing a recursive search on a collection. */
- graphLookup(options: any): this;
-
- /** Appends new custom $group operator to this aggregate pipeline. */
- group(arg: any): this;
-
- /** Sets the hint option for the aggregation query (ignored for < 3.6.0) */
- hint(value: Record<string, unknown> | string): this;
-
- /**
- * Appends a new $limit operator to this aggregate pipeline.
- * @param num maximum number of records to pass to the next stage
- */
- limit(num: number): this;
-
- /** Appends new custom $lookup operator to this aggregate pipeline. */
- lookup(options: any): this;
-
- /**
- * Appends a new custom $match operator to this aggregate pipeline.
- * @param arg $match operator contents
- */
- match(arg: any): this;
-
- /**
- * Binds this aggregate to a model.
- * @param model the model to which the aggregate is to be bound
- */
- model(model: any): this;
-
- /**
- * Append a new $near operator to this aggregation pipeline
- * @param arg $near operator contents
- */
- near(arg: { near?: number[]; distanceField: string; maxDistance?: number; query?: Record<string, any>; includeLocs?: string; num?: number; uniqueDocs?: boolean }): this;
-
- /** Returns the current pipeline */
- pipeline(): any[];
-
- /** Appends a new $project operator to this aggregate pipeline. */
- project(arg: string | Object): this;
-
- /** Sets the readPreference option for the aggregation query. */
- read(pref: string | mongodb.ReadPreferenceMode, tags?: any[]): this;
-
- /** Sets the readConcern level for the aggregation query. */
- readConcern(level: string): this;
-
- /** Appends a new $redact operator to this aggregate pipeline. */
- redact(expression: any, thenExpr: string | any, elseExpr: string | any): this;
-
- /** Appends a new $replaceRoot operator to this aggregate pipeline. */
- replaceRoot(newRoot: object | string): this;
-
- /**
- * Helper for [Atlas Text Search](https://docs.atlas.mongodb.com/reference/atlas-search/tutorial/)'s
- * `$search` stage.
- */
- search(options: any): this;
-
- /** Lets you set arbitrary options, for middleware or plugins. */
- option(value: Record<string, unknown>): this;
-
- /** Appends new custom $sample operator to this aggregate pipeline. */
- sample(size: number): this;
-
- /** Sets the session for this aggregation. Useful for [transactions](/docs/transactions.html). */
- session(session: mongodb.ClientSession | null): this;
-
- /**
- * Appends a new $skip operator to this aggregate pipeline.
- * @param num number of records to skip before next stage
- */
- skip(num: number): this;
-
- /** Appends a new $sort operator to this aggregate pipeline. */
- sort(arg: any): this;
-
- /** Provides promise for aggregate. */
- then: Promise<R>['then'];
-
- /**
- * Appends a new $sortByCount operator to this aggregate pipeline. Accepts either a string field name
- * or a pipeline object.
- */
- sortByCount(arg: string | any): this;
-
- /** Appends new custom $unwind operator(s) to this aggregate pipeline. */
- unwind(...args: any[]): this;
- }
-
- class AggregationCursor extends stream.Readable {
- /**
- * Adds a [cursor flag](http://mongodb.github.io/node-mongodb-native/2.2/api/Cursor.html#addCursorFlag).
- * Useful for setting the `noCursorTimeout` and `tailable` flags.
- */
- addCursorFlag(flag: string, value: boolean): this;
-
- /**
- * Marks this cursor as closed. Will stop streaming and subsequent calls to
- * `next()` will error.
- */
- close(): Promise<void>;
- close(callback: CallbackWithoutResult): void;
-
- /**
- * Execute `fn` for every document(s) in the cursor. If batchSize is provided
- * `fn` will be executed for each batch of documents. If `fn` returns a promise,
- * will wait for the promise to resolve before iterating on to the next one.
- * Returns a promise that resolves when done.
- */
- eachAsync(fn: (doc: any) => any, options?: { parallel?: number, batchSize?: number }): Promise<void>;
- eachAsync(fn: (doc: any) => any, options?: { parallel?: number, batchSize?: number }, cb?: CallbackWithoutResult): void;
-
- /**
- * Registers a transform function which subsequently maps documents retrieved
- * via the streams interface or `.next()`
- */
- map(fn: (res: any) => any): this;
-
- /**
- * Get the next document from this cursor. Will return `null` when there are
- * no documents left.
- */
- next(): Promise<any>;
- next(callback: Callback): void;
- }
-
- class SchemaType {
- /** SchemaType constructor */
- constructor(path: string, options?: AnyObject, instance?: string);
-
- /** Get/set the function used to cast arbitrary values to this type. */
- static cast(caster?: Function | boolean): Function;
-
- static checkRequired(checkRequired?: (v: any) => boolean): (v: any) => boolean;
-
- /** Sets a default option for this schema type. */
- static set(option: string, value: any): void;
-
- /** Attaches a getter for all instances of this schema type. */
- static get(getter: (value: any) => any): void;
-
- /** The class that Mongoose uses internally to instantiate this SchemaType's `options` property. */
- OptionsConstructor: typeof SchemaTypeOptions;
-
- /** Cast `val` to this schema type. Each class that inherits from schema type should implement this function. */
- cast(val: any, doc: Document<any>, init: boolean, prev?: any, options?: any): any;
-
- /** Sets a default value for this SchemaType. */
- default(val: any): any;
-
- /** Adds a getter to this schematype. */
- get(fn: Function): this;
-
- /**
- * Defines this path as immutable. Mongoose prevents you from changing
- * immutable paths unless the parent document has [`isNew: true`](/docs/api.html#document_Document-isNew).
- */
- immutable(bool: boolean): this;
-
- /** Declares the index options for this schematype. */
- index(options: any): this;
-
- /** String representation of what type this is, like 'ObjectID' or 'Number' */
- instance: string;
-
- /** The options this SchemaType was instantiated with */
- options: AnyObject;
-
- /**
- * Set the model that this path refers to. This is the option that [populate](https://mongoosejs.com/docs/populate.html)
- * looks at to determine the foreign collection it should query.
- */
- ref(ref: string | boolean | Model<any>): this;
-
- /**
- * Adds a required validator to this SchemaType. The validator gets added
- * to the front of this SchemaType's validators array using unshift().
- */
- required(required: boolean, message?: string): this;
-
- /** The schema this SchemaType instance is part of */
- schema: Schema<any>;
-
- /** Sets default select() behavior for this path. */
- select(val: boolean): this;
-
- /** Adds a setter to this schematype. */
- set(fn: Function): this;
-
- /** Declares a sparse index. */
- sparse(bool: boolean): this;
-
- /** Declares a full text index. */
- text(bool: boolean): this;
-
- /** Defines a custom function for transforming this path when converting a document to JSON. */
- transform(fn: (value: any) => any): this;
-
- /** Declares an unique index. */
- unique(bool: boolean): this;
-
- /** Adds validator(s) for this document path. */
- validate(obj: RegExp | Function | any, errorMsg?: string,
- type?: string): this;
- }
-
- type Callback<T = any> = (error: CallbackError, result: T) => void;
-
- type CallbackWithoutResult = (error: CallbackError) => void;
-
- class NativeError extends global.Error { }
- type CallbackError = NativeError | null;
-
- class Error extends global.Error {
- constructor(msg: string);
-
- /** The type of error. "MongooseError" for generic errors. */
- name: string;
-
- static messages: any;
-
- static Messages: any;
- }
-
- namespace Error {
- export class CastError extends Error {
- name: 'CastError';
- stringValue: string;
- kind: string;
- value: any;
- path: string;
- reason?: NativeError | null;
- model?: any;
-
- constructor(type: string, value: any, path: string, reason?: NativeError, schemaType?: SchemaType);
- }
-
- export class DisconnectedError extends Error {
- name: 'DisconnectedError';
- }
-
- export class DivergentArrayError extends Error {
- name: 'DivergentArrayError';
- }
-
- export class MissingSchemaError extends Error {
- name: 'MissingSchemaError';
- }
-
- export class DocumentNotFoundError extends Error {
- name: 'DocumentNotFoundError';
- result: any;
- numAffected: number;
- filter: any;
- query: any;
- }
-
- export class ObjectExpectedError extends Error {
- name: 'ObjectExpectedError';
- path: string;
- }
-
- export class ObjectParameterError extends Error {
- name: 'ObjectParameterError';
- }
-
- export class OverwriteModelError extends Error {
- name: 'OverwriteModelError';
- }
-
- export class ParallelSaveError extends Error {
- name: 'ParallelSaveError';
- }
-
- export class ParallelValidateError extends Error {
- name: 'ParallelValidateError';
- }
-
- export class MongooseServerSelectionError extends Error {
- name: 'MongooseServerSelectionError';
- }
-
- export class StrictModeError extends Error {
- name: 'StrictModeError';
- isImmutableError: boolean;
- path: string;
- }
-
- export class ValidationError extends Error {
- name: 'ValidationError';
-
- errors: { [path: string]: ValidatorError | CastError | ValidationError };
- addError: (path: string, error: ValidatorError | CastError | ValidationError) => void;
-
- constructor(instance?: Error);
- }
-
- export class ValidatorError extends Error {
- name: 'ValidatorError';
- properties: {
- message: string,
- type?: string,
- path?: string,
- value?: any,
- reason?: any
- };
- kind: string;
- path: string;
- value: any;
- reason?: Error | null;
-
- constructor(properties: {
- message?: string,
- type?: string,
- path?: string,
- value?: any,
- reason?: any
- });
- }
-
- export class VersionError extends Error {
- name: 'VersionError';
- version: number;
- modifiedPaths: Array<string>;
-
- constructor(doc: Document, currentVersion: number, modifiedPaths: Array<string>);
- }
- }
-
- /* for ts-mongoose */
- class mquery {}
-}
diff --git a/index.js b/index.js
index a938b443d43..7320766f0e5 100644
--- a/index.js
+++ b/index.js
@@ -1,4 +1,3 @@
-
/**
* Export lib/mongoose
*
@@ -6,4 +5,59 @@
'use strict';
-module.exports = require('./lib/');
+const mongoose = require('./lib/');
+
+module.exports = mongoose;
+module.exports.default = mongoose;
+module.exports.mongoose = mongoose;
+
+// Re-export for ESM support
+module.exports.cast = mongoose.cast;
+module.exports.STATES = mongoose.STATES;
+module.exports.setDriver = mongoose.setDriver;
+module.exports.set = mongoose.set;
+module.exports.get = mongoose.get;
+module.exports.createConnection = mongoose.createConnection;
+module.exports.connect = mongoose.connect;
+module.exports.disconnect = mongoose.disconnect;
+module.exports.startSession = mongoose.startSession;
+module.exports.pluralize = mongoose.pluralize;
+module.exports.model = mongoose.model;
+module.exports.deleteModel = mongoose.deleteModel;
+module.exports.modelNames = mongoose.modelNames;
+module.exports.plugin = mongoose.plugin;
+module.exports.connections = mongoose.connections;
+module.exports.version = mongoose.version;
+module.exports.Mongoose = mongoose.Mongoose;
+module.exports.Schema = mongoose.Schema;
+module.exports.SchemaType = mongoose.SchemaType;
+module.exports.SchemaTypes = mongoose.SchemaTypes;
+module.exports.VirtualType = mongoose.VirtualType;
+module.exports.Types = mongoose.Types;
+module.exports.Query = mongoose.Query;
+module.exports.Promise = mongoose.Promise;
+module.exports.Model = mongoose.Model;
+module.exports.Document = mongoose.Document;
+module.exports.ObjectId = mongoose.ObjectId;
+module.exports.isValidObjectId = mongoose.isValidObjectId;
+module.exports.isObjectIdOrHexString = mongoose.isObjectIdOrHexString;
+module.exports.syncIndexes = mongoose.syncIndexes;
+module.exports.Decimal128 = mongoose.Decimal128;
+module.exports.Mixed = mongoose.Mixed;
+module.exports.Date = mongoose.Date;
+module.exports.Number = mongoose.Number;
+module.exports.Error = mongoose.Error;
+module.exports.now = mongoose.now;
+module.exports.CastError = mongoose.CastError;
+module.exports.SchemaTypeOptions = mongoose.SchemaTypeOptions;
+module.exports.mongo = mongoose.mongo;
+module.exports.mquery = mongoose.mquery;
+module.exports.sanitizeFilter = mongoose.sanitizeFilter;
+module.exports.trusted = mongoose.trusted;
+module.exports.skipMiddlewareFunction = mongoose.skipMiddlewareFunction;
+module.exports.overwriteMiddlewareResult = mongoose.overwriteMiddlewareResult;
+
+// The following properties are not exported using ESM because `setDriver()` can mutate these
+// module.exports.connection = mongoose.connection;
+// module.exports.Collection = mongoose.Collection;
+// module.exports.Connection = mongoose.Connection;
diff --git a/index.pug b/index.pug
index cc82ef5149f..7976e086b0a 100644
--- a/index.pug
+++ b/index.pug
@@ -121,7 +121,7 @@ html(lang='en')
:markdown
```javascript
const mongoose = require('mongoose');
- mongoose.connect('mongodb://localhost:27017/test');
+ mongoose.connect('mongodb://127.0.0.1:27017/test');
const Cat = mongoose.model('Cat', { name: String });
@@ -151,8 +151,7 @@ html(lang='en')
* [Stack Overflow](http://stackoverflow.com/questions/tagged/mongoose)
* [GitHub Issues](https://github.com/Automattic/mongoose/issues)
- * [Gitter Chat](https://gitter.im/Automattic/mongoose)
- * [MongoDB Support](http://www.mongodb.org/about/support/)
+ * [MongoDB Support](https://www.mongodb.com/docs/manual/support/)
## News
@@ -160,240 +159,39 @@ html(lang='en')
## Changelog
- * [Changelog](https://github.com/Automattic/mongoose/blob/master/History.md)
+ * [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- ## Sponsors
+ ## GitHub Sponsors
<div class="sponsors">
<div>
<a rel="sponsored" href="https://localizejs.com/">
- <img class="sponsor" src="https://images.opencollective.com/localize/bb2cd4d/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.dontpayfull.com/">
- <img class="sponsor" src="https://images.opencollective.com/proxy/images?src=https%3A%2F%2Fopencollective-production.s3-us-west-1.amazonaws.com%2F84c4ba80-1955-11e8-8343-278614155b3e.png&height=100" style="height:100px">
- </a>
- <a rel="sponsored" href="https://frontendmasters.com/">
- <img class="sponsor" src="https://images.opencollective.com/frontendmasters/0b9cda4/logo/256.png" style="height:100px">
- </a>
- <a rel="sponsored" href="https://www.vpsserver.com/">
- <img class="sponsor" src="https://pbs.twimg.com/profile_images/750300526538027008/jo7cFmU9_400x400.jpg" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://icons8.com/">
- <img class="sponsor" src="https://s3.amazonaws.com/codebarbarian-images/Icons8+Logo+500px.png" style="height:100px">
- </a>
- <a rel="sponsored" href="https://www.embedgooglemap.net/blog/best-wordpress-themes/">
- <img class="sponsor" title=" Best WordPress Themes " src="https://www.embedgooglemap.net/ultimate-wp-logo.png" style="height:100px">
- </a>
- <a rel="sponsored" href="https://loanscouter.com/">
- <img class="sponsor" src="https://images.opencollective.com/lead-supply/7400b71/logo.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://fair-laan.se/">
- <img class="sponsor" src="https://images.opencollective.com/fair-laan-se/939304b/logo.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://craftresumes.com/professional-resume-writer/">
- <img class="sponsor" alt="Professional resume writer" src="https://s3.amazonaws.com/codebarbarian-images/craftresumes.png">
- </a>
- <a rel="sponsored" href="https://uiuxagencies.top/">
- <img class="sponsor" src="https://images.opencollective.com/proxy/images?src=https%3A%2F%2Fopencollective-production.s3-us-west-1.amazonaws.com%2F62223ab0-f4f7-11e8-919f-53f2ea21a967.png&height=100" style="height:100px">
- </a>
- <a rel="sponsored" href="https://blokt.com/">
- <img class="sponsor" src="https://images.opencollective.com/proxy/images?src=https%3A%2F%2Flogo.clearbit.com%2Fblokt.com&height=100" style="height:100px">
- </a>
- <a rel="sponsored" href="http://clay.global/">
- <img class="sponsor" src="https://images.opencollective.com/proxy/images?src=https%3A%2F%2Fopencollective-production.s3-us-west-1.amazonaws.com%2F09d57c90-637c-11e7-9ed2-bfa12b0351e8.jpg&height=100" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://tekhattan.com/">
- <img class="sponsor" src="https://images.opencollective.com/proxy/images?src=https%3A%2F%2Fopencollective-production.s3-us-west-1.amazonaws.com%2F31835450-5a98-11e9-adb1-e17fccd4c65e.png&height=80" style="height:80px">
- </a>
- <a rel="sponsored" href="https://www.rabathelten.dk/">
- <img class="sponsor" src="/docs/images/dk.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://lemonlaw.site/">
- <img class="sponsor" src="https://images.opencollective.com/lemon-law/logo.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://uxplanet.org/top-ui-ux-design-agencies-user-experience-firms-8c54697e290">
- <img class="sponsor" src="https://miro.medium.com/fit/c/160/160/1*A0FnBy5FBoVQC02SZXLXPg.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://proxybaylist.org/">
- <img class="sponsor" src="https://images.opencollective.com/proxy-bay/e176fe4/avatar.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://ghostinspector.com/">
- <img class="sponsor" src="https://images.opencollective.com/ghostinspector/51b7be8/logo.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://fortunegames.com/">
- <img class="sponsor" src="/docs/images/fortunegames.jpg" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://medium.com/@niksundin/best-web-design-companies-1872e445775f">
- <img class="sponsor" src="https://images.opencollective.com/top-web-design-agencies/d92d747/logo.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.casinotop.com">
- <img class="sponsor" src="https://images.opencollective.com/casinotop-com/10fd95b/logo.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.casinotopp.net">
- <img class="sponsor" src="https://images.opencollective.com/casino-topp/1dd399a/logo.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.bonus.ca/">
- <img class="sponsor" src="https://images.opencollective.com/bonus-finder/4b3394e/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.bonus.net.nz/free-spins">
- <img class="sponsor" src="https://images.opencollective.com/bonusfinder-new-zealand/a5becb5/logo.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.bonusfinder.com/casino">
- <img class="sponsor" src="https://images.opencollective.com/bonusfinder-com-us/8a47a19/logo.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.bonus.com.de/freispiele">
- <img class="sponsor" src="https://images.opencollective.com/bonusfinder-deutschland/646169e/logo.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.minitool.com/">
- <img class="sponsor" src="https://images.opencollective.com/minitool-solution-ltd/1519aa6/logo.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://maaltidskasser.net/">
- <img class="sponsor" src="https://codebarbarian-images.s3.amazonaws.com/maaltidskasser-logo.png" style="height: 100px" alt="Måltidskasser">
- </a>
- <a rel="sponsored" href="https://taxfreesnus.com/">
- <img class="sponsor" src="https://images.opencollective.com/taxfreesnus-com/ebf869a/logo.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.pelisivut.com/">
- <img class="sponsor" src="https://images.opencollective.com/pelisivut/04f08f2/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.kasinot.fi/">
- <img class="sponsor" src="https://images.opencollective.com/kasinot-fi/e09aa2e/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://mobilunity.com/">
- <img class="sponsor" src="https://images.opencollective.com/mobilunity/e4ca372/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.paraskasino.fi/">
- <img class="sponsor" src="https://images.opencollective.com/paraskasino/a39c3d6/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://casinoonline.casino/">
- <img class="sponsor" src="https://images.opencollective.com/casino-online/4c8371c/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.casinot.net/">
- <img class="sponsor" src="https://images.opencollective.com/casinot-net/242597a/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://420couponcodes.com/">
- <img class="sponsor" src="https://images.opencollective.com/420couponcodes/8965926/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.kasinohai.com/nettikasinot">
- <img class="sponsor" src="https://images.opencollective.com/kasinohai-com/41d4f51/logo/256.png" style="height: 100px">
+ <img class="sponsor" src="https://assets.localizecdn.com/uploads/1609766994137.svg" style="height: 100px">
</a>
- <a rel="sponsored" href="https://onlinecasinoinformatie.com/">
- <img class="sponsor" src="https://images.opencollective.com/ricardo-goncalves/3275f75/avatar/256.png" style="height: 100px">
+ <a rel="sponsored" href="https://birb.app/">
+ <img class="sponsor" src="https://uploads-ssl.webflow.com/618b15b23212e0b2b4f8f67b/618b189f1d441fd9e0f6c5f3_logo-black-text.png" style="height: 100px">
</a>
- <a rel="sponsored" href="https://www.turtlebet.com/fi/kaikki-nettikasinot.html">
- <img class="sponsor" src="https://images.opencollective.com/turtlebet-nettikasinot/4799a27/logo/256.png" style="height: 100px">
+ <a rel="sponsored" href="https://helloclub.com/?source=Mongoose">
+ <img class="sponsor" src="https://codebarbarian-images.s3.amazonaws.com/logo-text-default.svg" style="height:100px">
</a>
- <a rel="sponsored" href="https://www.casinofever.ca/">
- <img class="sponsor" src="https://images.opencollective.com/casinofever-ca1/4ad150e/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.parhaatnettikasinot.com/">
- <img class="sponsor" src="https://images.opencollective.com/parhaatnettikasinot-com/fc53017/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://kasynohex.com/kasyna-online/">
- <img class="sponsor" src="https://images.opencollective.com/kasynohex-com/b25daf6/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.japanesecasino.com/">
- <img class="sponsor" src="https://images.opencollective.com/japanesecasino/b0ffe3c/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.parhaatkasinot.com/">
- <img class="sponsor" src="https://images.opencollective.com/parhaatkasinot/e7fda21/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.polskiekasyno.com/">
- <img class="sponsor" src="https://images.opencollective.com/polskiekasyno-com/a8975c6/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.baocasino.com/">
- <img class="sponsor" src="https://images.opencollective.com/baocasino/b5a71c4/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://casino-academia.jp/">
- <img class="sponsor" src="https://images.opencollective.com/casinoacademia/90fc970/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://nettikasinolista.com/">
- <img class="sponsor" src="https://images.opencollective.com/nettikasinolista/fd361ee/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://casinopro.ca/">
- <img class="sponsor" src="https://images.opencollective.com/casinopro/4729b46/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://twojtyp.pl/">
- <img class="sponsor" src="https://images.opencollective.com/twojtyp/cd0b486/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://nettikasinot247.fi/">
- <img class="sponsor" src="https://images.opencollective.com/nettikasinot-24-7/c51fe6a/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://goread.io/buy-instagram-likes">
- <img class="sponsor" src="https://goread.io/assets/images/Goreadlogo.png" style="height: 100px" alt="Buy Instagram Likes">
- </a>
- <a rel="sponsored" href="https://nflthursday.com/">
- <img class="sponsor" src="https://images.opencollective.com/nfl-thursday/ac3e3cc/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://njsportsjournal.com/">
- <img class="sponsor" src="https://images.opencollective.com/njsj1/9bf603f/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.nettikasinot.media/">
- <img class="sponsor" src="https://images.opencollective.com/nettikasinot-media/2dba7da/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://poprey.com/">
- <img class="sponsor" src="/docs/images/logo-poprey-com-white-100.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://casinomartini.com/nz/new-online-casinos/">
- <img class="sponsor" src="https://images.opencollective.com/casino-martini/8efce1c/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://slot-xo888.com/">
- <img class="sponsor" src="https://images.opencollective.com/slotxo/694a96c/avatar/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.slotbar888.com/">
- <img class="sponsor" src="https://images.opencollective.com/slotbar/2843cec/avatar/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://writersperhour.com/write-my-paper">
- <img class="sponsor" src="https://images.opencollective.com/write-my-paper/5787d4b/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.gambledex.com/">
- <img class="sponsor" src="https://codebarbarian-images.s3.amazonaws.com/gambedex.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.uudetkasinot.com/">
- <img class="sponsor" src="https://images.opencollective.com/uudetkasinot-com/01a3658/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.vedonlyontibonukset.com/">
- <img class="sponsor" src="https://images.opencollective.com/vedonlyontibonukset/69bffdc/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://list.casino/">
- <img class="sponsor" src="https://images.opencollective.com/list-casino/5d33a45/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.bonusfinder.co.uk/">
- <img class="sponsor" src="https://images.opencollective.com/bonusfinder-uk/ecdd3af/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://onlinecasinohex.nl/">
- <img class="sponsor" src="https://images.opencollective.com/onlinecasinohex-nl/ed24082/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://veikkausbonukset.com/">
- <img class="sponsor" src="https://images.opencollective.com/vbonukset1/c39e354/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://casinoenlineahex.com/">
- <img class="sponsor" src="https://images.opencollective.com/casinoenlinea-hex/b917401/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://onlinecasinohex.ca/">
- <img class="sponsor" src="https://images.opencollective.com/hexcasinoca/2da3af2/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.targetedwebtraffic.com/buy/buy-targeted-traffic-that-converts/">
- <img class="sponsor" src="https://images.opencollective.com/targetedwebtraffic/7abacc7/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://nzcasinohex.com/">
- <img class="sponsor" src="https://images.opencollective.com/nzcasinohex/c34e6b1/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://www.instafollowers.co/">
- <img class="sponsor" src="https://images.opencollective.com/instafollowersco/5c0cddd/logo/256.png" style="height: 100px">
- </a>
- <a rel="sponsored" href="https://casino.guide/crypto-casinos/">
- <img class="sponsor" src="https://casino.guide/wp-content/themes/casinoguide/assets/lotti/en/default/images/img_0.png" style="height: 100px">
+ <a rel="sponsored" href="https://devitjobs.us/?utm_source=mongoose_main">
+ <img class="sponsor" src="https://codebarbarian-images.s3.amazonaws.com/devitUS-flat-rectangle-v4.png" style="height:100px">
</a>
</div>
</div>
- <div class="sponsor">
- Sponsor <a href="https://opencollective.com/mongoose">Mongoose on OpenCollective</a>
- to get your company's logo above!
- </div>
+ ## Sponsors
+
+ div.sponsors
+ div
+ each sponsor in opencollectiveSponsors
+ a(rel='sponsored', href=sponsor.website || sponsor.profile)
+ img.sponsor(src=sponsor.image || 'https://next-images.opencollective.com/_next/image?url=%2Fstatic%2Fimages%2Fopencollective-icon.png&w=96&q=75', style='height:50px', alt=sponsor.alt)
+
+ div.sponsors
+ :markdown
+ Sponsor [Mongoose on OpenCollective](https://opencollective.com/mongoose) to get your company's logo above!
p#footer Licensed under MIT.
script.
document.body.className = 'load';
- include docs/includes/track
diff --git a/lgtm.yml b/lgtm.yml
new file mode 100644
index 00000000000..d486db70b13
--- /dev/null
+++ b/lgtm.yml
@@ -0,0 +1,12 @@
+path_classifiers:
+ src:
+ - lib
+ types:
+ - types
+ test:
+ - test
+ docs:
+ - docs
+queries:
+ - exclude: "*"
+ - include: lib
\ No newline at end of file
diff --git a/lib/aggregate.js b/lib/aggregate.js
index 6930a04fc26..36f81c2810d 100644
--- a/lib/aggregate.js
+++ b/lib/aggregate.js
@@ -6,20 +6,22 @@
const AggregationCursor = require('./cursor/AggregationCursor');
const Query = require('./query');
-const applyGlobalMaxTimeMS = require('./helpers/query/applyGlobalMaxTimeMS');
+const { applyGlobalMaxTimeMS, applyGlobalDiskUse } = require('./helpers/query/applyGlobalOption');
const getConstructorName = require('./helpers/getConstructorName');
+const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscriminatorPipeline');
const promiseOrCallback = require('./helpers/promiseOrCallback');
const stringifyFunctionOperators = require('./helpers/aggregate/stringifyFunctionOperators');
-const util = require('util');
const utils = require('./utils');
const read = Query.prototype.read;
const readConcern = Query.prototype.readConcern;
+const validRedactStringValues = new Set(['$$DESCEND', '$$PRUNE', '$$KEEP']);
+
/**
* Aggregate constructor used for building aggregation pipelines. Do not
- * instantiate this class directly, use [Model.aggregate()](/docs/api.html#model_Model.aggregate) instead.
+ * instantiate this class directly, use [Model.aggregate()](/docs/api/model.html#model_Model-aggregate) instead.
*
- * ####Example:
+ * #### Example:
*
* const aggregate = Model.aggregate([
* { $project: { a: 1, b: 1 } },
@@ -31,19 +33,17 @@ const readConcern = Query.prototype.readConcern;
* unwind('tags').
* exec(callback);
*
- * ####Note:
+ * #### Note:
*
* - The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned).
* - Mongoose does **not** cast pipeline stages. The below will **not** work unless `_id` is a string in the database
*
- * ```javascript
- * new Aggregate([{ $match: { _id: '00000000000000000000000a' } }]);
- * // Do this instead to cast to an ObjectId
- * new Aggregate([{ $match: { _id: mongoose.Types.ObjectId('00000000000000000000000a') } }]);
- * ```
+ * new Aggregate([{ $match: { _id: '00000000000000000000000a' } }]);
+ * // Do this instead to cast to an ObjectId
+ * new Aggregate([{ $match: { _id: new mongoose.Types.ObjectId('00000000000000000000000a') } }]);
*
- * @see MongoDB http://docs.mongodb.org/manual/applications/aggregation/
- * @see driver http://mongodb.github.com/node-mongodb-native/api-generated/collection.html#aggregate
+ * @see MongoDB https://www.mongodb.com/docs/manual/applications/aggregation/
+ * @see driver https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#aggregate
* @param {Array} [pipeline] aggregation pipeline as an array of objects
* @param {Model} [model] the model to use with this aggregate.
* @api public
@@ -54,28 +54,30 @@ function Aggregate(pipeline, model) {
this._model = model;
this.options = {};
- if (arguments.length === 1 && util.isArray(pipeline)) {
+ if (arguments.length === 1 && Array.isArray(pipeline)) {
this.append.apply(this, pipeline);
}
}
/**
- * Contains options passed down to the [aggregate command](https://docs.mongodb.com/manual/reference/command/aggregate/).
+ * Contains options passed down to the [aggregate command](https://www.mongodb.com/docs/manual/reference/command/aggregate/).
* Supported options are:
*
- * - `readPreference`
- * - [`cursor`](./api.html#aggregate_Aggregate-cursor)
- * - [`explain`](./api.html#aggregate_Aggregate-explain)
- * - [`allowDiskUse`](./api.html#aggregate_Aggregate-allowDiskUse)
- * - `maxTimeMS`
+ * - [`allowDiskUse`](#aggregate_Aggregate-allowDiskUse)
* - `bypassDocumentValidation`
- * - `raw`
- * - `promoteLongs`
- * - `promoteValues`
- * - `promoteBuffers`
- * - [`collation`](./api.html#aggregate_Aggregate-collation)
+ * - [`collation`](#aggregate_Aggregate-collation)
* - `comment`
- * - [`session`](./api.html#aggregate_Aggregate-session)
+ * - [`cursor`](#aggregate_Aggregate-cursor)
+ * - [`explain`](#aggregate_Aggregate-explain)
+ * - `fieldsAsRaw`
+ * - [`hint`](#aggregate_Aggregate-hint)
+ * - `let`
+ * - `maxTimeMS`
+ * - `raw`
+ * - [`readConcern`](#aggregate_Aggregate-readConcern)
+ * - `readPreference`
+ * - [`session`](#aggregate_Aggregate-session)
+ * - `writeConcern`
*
* @property options
* @memberOf Aggregate
@@ -87,7 +89,8 @@ Aggregate.prototype.options;
/**
* Get/set the model that this aggregation will execute on.
*
- * ####Example:
+ * #### Example:
+ *
* const aggregate = MyModel.aggregate([{ $match: { answer: 42 } }]);
* aggregate.model() === MyModel; // true
*
@@ -95,7 +98,7 @@ Aggregate.prototype.options;
* aggregate.model(SomeOtherModel);
* aggregate.model() === SomeOtherModel; // true
*
- * @param {Model} [model] set the model associated with this aggregate.
+ * @param {Model} [model] Set the model associated with this aggregate. If not provided, returns the already stored model.
* @return {Model}
* @api public
*/
@@ -108,11 +111,11 @@ Aggregate.prototype.model = function(model) {
this._model = model;
if (model.schema != null) {
if (this.options.readPreference == null &&
- model.schema.options.read != null) {
+ model.schema.options.read != null) {
this.options.readPreference = model.schema.options.read;
}
if (this.options.collation == null &&
- model.schema.options.collation != null) {
+ model.schema.options.collation != null) {
this.options.collation = model.schema.options.collation;
}
}
@@ -123,7 +126,7 @@ Aggregate.prototype.model = function(model) {
/**
* Appends new operators to this aggregate pipeline
*
- * ####Examples:
+ * #### Example:
*
* aggregate.append({ $project: { field: 1 }}, { $limit: 2 });
*
@@ -131,15 +134,15 @@ Aggregate.prototype.model = function(model) {
* const pipeline = [{ $match: { daw: 'Logic Audio X' }} ];
* aggregate.append(pipeline);
*
- * @param {Object} ops operator(s) to append
+ * @param {...Object|Object[]} ops operator(s) to append. Can either be a spread of objects or a single parameter of a object array.
* @return {Aggregate}
* @api public
*/
Aggregate.prototype.append = function() {
- const args = (arguments.length === 1 && util.isArray(arguments[0]))
+ const args = (arguments.length === 1 && Array.isArray(arguments[0]))
? arguments[0]
- : utils.args(arguments);
+ : [...arguments];
if (!args.every(isOperator)) {
throw new Error('Arguments must be aggregate pipeline operators');
@@ -154,8 +157,8 @@ Aggregate.prototype.append = function() {
* Appends a new $addFields operator to this aggregate pipeline.
* Requires MongoDB v3.4+ to work
*
- * ####Examples:
- *
+ * #### Example:
+ *
* // adding new fields based on existing fields
* aggregate.addFields({
* newField: '$b.nested'
@@ -169,20 +172,15 @@ Aggregate.prototype.append = function() {
* aggregate.addFields({ salary_k: { $divide: [ "$salary", 1000 ] } });
*
* @param {Object} arg field specification
- * @see $addFields https://docs.mongodb.com/manual/reference/operator/aggregation/addFields/
+ * @see $addFields https://www.mongodb.com/docs/manual/reference/operator/aggregation/addFields/
* @return {Aggregate}
* @api public
*/
Aggregate.prototype.addFields = function(arg) {
- const fields = {};
- if (typeof arg === 'object' && !util.isArray(arg)) {
- Object.keys(arg).forEach(function(field) {
- fields[field] = arg[field];
- });
- } else {
+ if (typeof arg !== 'object' || arg === null || Array.isArray(arg)) {
throw new Error('Invalid addFields() argument. Must be an object');
}
- return this.append({ $addFields: fields });
+ return this.append({ $addFields: Object.assign({}, arg) });
};
/**
@@ -190,7 +188,7 @@ Aggregate.prototype.addFields = function(arg) {
*
* Mongoose query [selection syntax](#query_Query-select) is also supported.
*
- * ####Examples:
+ * #### Example:
*
* // include a, include b, exclude _id
* aggregate.project("a b -_id");
@@ -212,7 +210,7 @@ Aggregate.prototype.addFields = function(arg) {
* aggregate.project({ salary_k: { $divide: [ "$salary", 1000 ] } });
*
* @param {Object|String} arg field specification
- * @see projection http://docs.mongodb.org/manual/reference/aggregation/project/
+ * @see projection https://www.mongodb.com/docs/manual/reference/aggregation/project/
* @return {Aggregate}
* @api public
*/
@@ -220,7 +218,7 @@ Aggregate.prototype.addFields = function(arg) {
Aggregate.prototype.project = function(arg) {
const fields = {};
- if (typeof arg === 'object' && !util.isArray(arg)) {
+ if (typeof arg === 'object' && !Array.isArray(arg)) {
Object.keys(arg).forEach(function(field) {
fields[field] = arg[field];
});
@@ -245,11 +243,11 @@ Aggregate.prototype.project = function(arg) {
/**
* Appends a new custom $group operator to this aggregate pipeline.
*
- * ####Examples:
+ * #### Example:
*
* aggregate.group({ _id: "$department" });
*
- * @see $group http://docs.mongodb.org/manual/reference/aggregation/group/
+ * @see $group https://www.mongodb.com/docs/manual/reference/aggregation/group/
* @method group
* @memberOf Aggregate
* @instance
@@ -261,11 +259,11 @@ Aggregate.prototype.project = function(arg) {
/**
* Appends a new custom $match operator to this aggregate pipeline.
*
- * ####Examples:
+ * #### Example:
*
* aggregate.match({ department: { $in: [ "sales", "engineering" ] } });
*
- * @see $match http://docs.mongodb.org/manual/reference/aggregation/match/
+ * @see $match https://www.mongodb.com/docs/manual/reference/aggregation/match/
* @method match
* @memberOf Aggregate
* @instance
@@ -277,11 +275,11 @@ Aggregate.prototype.project = function(arg) {
/**
* Appends a new $skip operator to this aggregate pipeline.
*
- * ####Examples:
+ * #### Example:
*
* aggregate.skip(10);
*
- * @see $skip http://docs.mongodb.org/manual/reference/aggregation/skip/
+ * @see $skip https://www.mongodb.com/docs/manual/reference/aggregation/skip/
* @method skip
* @memberOf Aggregate
* @instance
@@ -293,11 +291,11 @@ Aggregate.prototype.project = function(arg) {
/**
* Appends a new $limit operator to this aggregate pipeline.
*
- * ####Examples:
+ * #### Example:
*
* aggregate.limit(10);
*
- * @see $limit http://docs.mongodb.org/manual/reference/aggregation/limit/
+ * @see $limit https://www.mongodb.com/docs/manual/reference/aggregation/limit/
* @method limit
* @memberOf Aggregate
* @instance
@@ -306,26 +304,71 @@ Aggregate.prototype.project = function(arg) {
* @api public
*/
+
+/**
+ * Appends a new $densify operator to this aggregate pipeline.
+ *
+ * #### Example:
+ *
+ * aggregate.densify({
+ * field: 'timestamp',
+ * range: {
+ * step: 1,
+ * unit: 'hour',
+ * bounds: [new Date('2021-05-18T00:00:00.000Z'), new Date('2021-05-18T08:00:00.000Z')]
+ * }
+ * });
+ *
+ * @see $densify https://www.mongodb.com/docs/manual/reference/operator/aggregation/densify/
+ * @method densify
+ * @memberOf Aggregate
+ * @instance
+ * @param {Object} arg $densify operator contents
+ * @return {Aggregate}
+ * @api public
+ */
+
+/**
+ * Appends a new $fill operator to this aggregate pipeline.
+ *
+ * #### Example:
+ *
+ * aggregate.fill({
+ * output: {
+ * bootsSold: { value: 0 },
+ * sandalsSold: { value: 0 },
+ * sneakersSold: { value: 0 }
+ * }
+ * });
+ *
+ * @see $fill https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/
+ * @method fill
+ * @memberOf Aggregate
+ * @instance
+ * @param {Object} arg $fill operator contents
+ * @return {Aggregate}
+ * @api public
+ */
+
/**
* Appends a new $geoNear operator to this aggregate pipeline.
*
- * ####NOTE:
+ * #### Note:
*
* **MUST** be used as the first operator in the pipeline.
*
- * ####Examples:
+ * #### Example:
*
* aggregate.near({
- * near: [40.724, -73.997],
+ * near: { type: 'Point', coordinates: [40.724, -73.997] },
* distanceField: "dist.calculated", // required
* maxDistance: 0.008,
* query: { type: "public" },
* includeLocs: "dist.location",
- * uniqueDocs: true,
- * num: 5
+ * spherical: true,
* });
*
- * @see $geoNear http://docs.mongodb.org/manual/reference/aggregation/geoNear/
+ * @see $geoNear https://www.mongodb.com/docs/manual/reference/aggregation/geoNear/
* @method near
* @memberOf Aggregate
* @instance
@@ -344,7 +387,7 @@ Aggregate.prototype.near = function(arg) {
* define methods
*/
-'group match skip limit out'.split(' ').forEach(function($operator) {
+'group match skip limit out densify fill'.split(' ').forEach(function($operator) {
Aggregate.prototype[$operator] = function(arg) {
const op = {};
op['$' + $operator] = arg;
@@ -358,20 +401,20 @@ Aggregate.prototype.near = function(arg) {
* Note that the `$unwind` operator requires the path name to start with '$'.
* Mongoose will prepend '$' if the specified field doesn't start '$'.
*
- * ####Examples:
+ * #### Example:
*
* aggregate.unwind("tags");
* aggregate.unwind("a", "b", "c");
* aggregate.unwind({ path: '$tags', preserveNullAndEmptyArrays: true });
*
- * @see $unwind http://docs.mongodb.org/manual/reference/aggregation/unwind/
- * @param {String|Object} fields the field(s) to unwind, either as field names or as [objects with options](https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/#document-operand-with-options). If passing a string, prefixing the field name with '$' is optional. If passing an object, `path` must start with '$'.
+ * @see $unwind https://www.mongodb.com/docs/manual/reference/aggregation/unwind/
+ * @param {String|Object|String[]|Object[]} fields the field(s) to unwind, either as field names or as [objects with options](https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/#document-operand-with-options). If passing a string, prefixing the field name with '$' is optional. If passing an object, `path` must start with '$'.
* @return {Aggregate}
* @api public
*/
Aggregate.prototype.unwind = function() {
- const args = utils.args(arguments);
+ const args = [...arguments];
const res = [];
for (const arg of args) {
@@ -379,7 +422,7 @@ Aggregate.prototype.unwind = function() {
res.push({ $unwind: arg });
} else if (typeof arg === 'string') {
res.push({
- $unwind: (arg && arg.startsWith('$')) ? arg : '$' + arg
+ $unwind: (arg[0] === '$') ? arg : '$' + arg
});
} else {
throw new Error('Invalid arg "' + arg + '" to unwind(), ' +
@@ -397,14 +440,14 @@ Aggregate.prototype.unwind = function() {
* If you are passing in a string Mongoose will prepend '$' if the specified field doesn't start '$'.
* If you are passing in an object the strings in your expression will not be altered.
*
- * ####Examples:
+ * #### Example:
*
* aggregate.replaceRoot("user");
*
* aggregate.replaceRoot({ x: { $concat: ['$this', '$that'] } });
*
- * @see $replaceRoot https://docs.mongodb.org/manual/reference/operator/aggregation/replaceRoot
- * @param {String|Object} the field or document which will become the new root document
+ * @see $replaceRoot https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceRoot
+ * @param {String|Object} newRoot the field or document which will become the new root document
* @return {Aggregate}
* @api public
*/
@@ -428,18 +471,18 @@ Aggregate.prototype.replaceRoot = function(newRoot) {
/**
* Appends a new $count operator to this aggregate pipeline.
*
- * ####Examples:
+ * #### Example:
*
* aggregate.count("userCount");
*
- * @see $count https://docs.mongodb.org/manual/reference/operator/aggregation/count
- * @param {String} the name of the count field
+ * @see $count https://www.mongodb.com/docs/manual/reference/operator/aggregation/count
+ * @param {String} fieldName The name of the output field which has the count as its value. It must be a non-empty string, must not start with $ and must not contain the . character.
* @return {Aggregate}
* @api public
*/
-Aggregate.prototype.count = function(countName) {
- return this.append({ $count: countName });
+Aggregate.prototype.count = function(fieldName) {
+ return this.append({ $count: fieldName });
};
/**
@@ -449,12 +492,12 @@ Aggregate.prototype.count = function(countName) {
* Note that the `$sortByCount` operator requires the new root to start with '$'.
* Mongoose will prepend '$' if the specified field name doesn't start with '$'.
*
- * ####Examples:
+ * #### Example:
*
* aggregate.sortByCount('users');
* aggregate.sortByCount({ $mergeObjects: [ "$employee", "$business" ] })
*
- * @see $sortByCount https://docs.mongodb.com/manual/reference/operator/aggregation/sortByCount/
+ * @see $sortByCount https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortByCount/
* @param {Object|String} arg
* @return {Aggregate} this
* @api public
@@ -465,7 +508,7 @@ Aggregate.prototype.sortByCount = function(arg) {
return this.append({ $sortByCount: arg });
} else if (typeof arg === 'string') {
return this.append({
- $sortByCount: (arg && arg.startsWith('$')) ? arg : '$' + arg
+ $sortByCount: (arg[0] === '$') ? arg : '$' + arg
});
} else {
throw new TypeError('Invalid arg "' + arg + '" to sortByCount(), ' +
@@ -476,13 +519,14 @@ Aggregate.prototype.sortByCount = function(arg) {
/**
* Appends new custom $lookup operator to this aggregate pipeline.
*
- * ####Examples:
+ * #### Example:
*
* aggregate.lookup({ from: 'users', localField: 'userId', foreignField: '_id', as: 'users' });
*
- * @see $lookup https://docs.mongodb.org/manual/reference/operator/aggregation/lookup/#pipe._S_lookup
+ * @see $lookup https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/#pipe._S_lookup
* @param {Object} options to $lookup as described in the above link
- * @return {Aggregate}* @api public
+ * @return {Aggregate}
+ * @api public
*/
Aggregate.prototype.lookup = function(options) {
@@ -494,11 +538,12 @@ Aggregate.prototype.lookup = function(options) {
*
* Note that graphLookup can only consume at most 100MB of memory, and does not allow disk use even if `{ allowDiskUse: true }` is specified.
*
- * #### Examples:
+ * #### Example:
+ *
* // Suppose we have a collection of courses, where a document might look like `{ _id: 0, name: 'Calculus', prerequisite: 'Trigonometry'}` and `{ _id: 0, name: 'Trigonometry', prerequisite: 'Algebra' }`
* aggregate.graphLookup({ from: 'courses', startWith: '$prerequisite', connectFromField: 'prerequisite', connectToField: 'name', as: 'prerequisites', maxDepth: 3 }) // this will recursively search the 'courses' collection up to 3 prerequisites
*
- * @see $graphLookup https://docs.mongodb.com/manual/reference/operator/aggregation/graphLookup/#pipe._S_graphLookup
+ * @see $graphLookup https://www.mongodb.com/docs/manual/reference/operator/aggregation/graphLookup/#pipe._S_graphLookup
* @param {Object} options to $graphLookup as described in the above link
* @return {Aggregate}
* @api public
@@ -527,11 +572,11 @@ Aggregate.prototype.graphLookup = function(options) {
/**
* Appends new custom $sample operator to this aggregate pipeline.
*
- * ####Examples:
+ * #### Example:
*
* aggregate.sample(3); // Add a pipeline that picks 3 random documents
*
- * @see $sample https://docs.mongodb.org/manual/reference/operator/aggregation/sample/#pipe._S_sample
+ * @see $sample https://www.mongodb.com/docs/manual/reference/operator/aggregation/sample/#pipe._S_sample
* @param {Number} size number of random documents to pick
* @return {Aggregate}
* @api public
@@ -548,13 +593,13 @@ Aggregate.prototype.sample = function(size) {
*
* If a string is passed, it must be a space delimited list of path names. The sort order of each path is ascending unless the path name is prefixed with `-` which will be treated as descending.
*
- * ####Examples:
+ * #### Example:
*
* // these are equivalent
* aggregate.sort({ field: 'asc', test: -1 });
* aggregate.sort('field -test');
*
- * @see $sort http://docs.mongodb.org/manual/reference/aggregation/sort/
+ * @see $sort https://www.mongodb.com/docs/manual/reference/aggregation/sort/
* @param {Object|String} arg
* @return {Aggregate} this
* @api public
@@ -593,25 +638,39 @@ Aggregate.prototype.sort = function(arg) {
return this.append({ $sort: sort });
};
+/**
+ * Appends new $unionWith operator to this aggregate pipeline.
+ *
+ * #### Example:
+ *
+ * aggregate.unionWith({ coll: 'users', pipeline: [ { $match: { _id: 1 } } ] });
+ *
+ * @see $unionWith https://www.mongodb.com/docs/manual/reference/operator/aggregation/unionWith
+ * @param {Object} options to $unionWith query as described in the above link
+ * @return {Aggregate}
+ * @api public
+ */
+
+Aggregate.prototype.unionWith = function(options) {
+ return this.append({ $unionWith: options });
+};
+
+
/**
* Sets the readPreference option for the aggregation query.
*
- * ####Example:
+ * #### Example:
*
* await Model.aggregate(pipeline).read('primaryPreferred');
*
- * @param {String} pref one of the listed preference options or their aliases
- * @param {Array} [tags] optional tags for this query
+ * @param {String|ReadPreference} pref one of the listed preference options or their aliases
+ * @param {Array} [tags] optional tags for this query. DEPRECATED
* @return {Aggregate} this
* @api public
- * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference
- * @see driver http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences
+ * @see mongodb https://www.mongodb.com/docs/manual/applications/replication/#read-preference
*/
Aggregate.prototype.read = function(pref, tags) {
- if (!this.options) {
- this.options = {};
- }
read.call(this, pref, tags);
return this;
};
@@ -619,20 +678,17 @@ Aggregate.prototype.read = function(pref, tags) {
/**
* Sets the readConcern level for the aggregation query.
*
- * ####Example:
+ * #### Example:
*
* await Model.aggregate(pipeline).readConcern('majority');
*
* @param {String} level one of the listed read concern level or their aliases
- * @see mongodb https://docs.mongodb.com/manual/reference/read-concern/
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/read-concern/
* @return {Aggregate} this
* @api public
*/
Aggregate.prototype.readConcern = function(level) {
- if (!this.options) {
- this.options = {};
- }
readConcern.call(this, level);
return this;
};
@@ -643,7 +699,7 @@ Aggregate.prototype.readConcern = function(level) {
* If 3 arguments are supplied, Mongoose will wrap them with if-then-else of $cond operator respectively
* If `thenExpr` or `elseExpr` is string, make sure it starts with $$, like `$$DESCEND`, `$$PRUNE` or `$$KEEP`.
*
- * ####Example:
+ * #### Example:
*
* await Model.aggregate(pipeline).redact({
* $cond: {
@@ -660,15 +716,15 @@ Aggregate.prototype.readConcern = function(level) {
* @param {String|Object} [thenExpr] true case for the condition
* @param {String|Object} [elseExpr] false case for the condition
* @return {Aggregate} this
- * @see $redact https://docs.mongodb.com/manual/reference/operator/aggregation/redact/
+ * @see $redact https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/
* @api public
*/
Aggregate.prototype.redact = function(expression, thenExpr, elseExpr) {
if (arguments.length === 3) {
- if ((typeof thenExpr === 'string' && !thenExpr.startsWith('$$')) ||
- (typeof elseExpr === 'string' && !elseExpr.startsWith('$$'))) {
- throw new Error('If thenExpr or elseExpr is string, it must start with $$. e.g. $$DESCEND, $$PRUNE, $$KEEP');
+ if ((typeof thenExpr === 'string' && !validRedactStringValues.has(thenExpr)) ||
+ (typeof elseExpr === 'string' && !validRedactStringValues.has(elseExpr))) {
+ throw new Error('If thenExpr or elseExpr is string, it must be either $$DESCEND, $$PRUNE or $$KEEP');
}
expression = {
@@ -688,16 +744,21 @@ Aggregate.prototype.redact = function(expression, thenExpr, elseExpr) {
/**
* Execute the aggregation with explain
*
- * ####Example:
+ * #### Example:
*
* Model.aggregate(..).explain(callback)
*
- * @param {Function} callback
- * @return {Promise}
+ * @param {String} [verbosity]
+ * @param {Function} [callback] The callback function to call, if not specified, will return a Promise instead.
+ * @return {Promise} Returns a promise if no "callback" is given
*/
-Aggregate.prototype.explain = function(callback) {
+Aggregate.prototype.explain = function(verbosity, callback) {
const model = this._model;
+ if (typeof verbosity === 'function') {
+ callback = verbosity;
+ verbosity = null;
+ }
return promiseOrCallback(callback, cb => {
if (!this._pipeline.length) {
@@ -705,7 +766,7 @@ Aggregate.prototype.explain = function(callback) {
return cb(err);
}
- prepareDiscriminatorPipeline(this);
+ prepareDiscriminatorPipeline(this._pipeline, this._model.schema);
model.hooks.execPre('aggregate', this, error => {
if (error) {
@@ -715,8 +776,6 @@ Aggregate.prototype.explain = function(callback) {
});
}
- this.options.explain = true;
-
model.collection.aggregate(this._pipeline, this.options, (error, cursor) => {
if (error != null) {
const _opts = { error: error };
@@ -724,30 +783,42 @@ Aggregate.prototype.explain = function(callback) {
cb(error);
});
}
- cursor.explain((error, result) => {
- const _opts = { error: error };
- return model.hooks.execPost('aggregate', this, [result], _opts, error => {
- if (error) {
- return cb(error);
- }
- return cb(null, result);
+ if (verbosity != null) {
+ cursor.explain(verbosity, (error, result) => {
+ const _opts = { error: error };
+ return model.hooks.execPost('aggregate', this, [result], _opts, error => {
+ if (error) {
+ return cb(error);
+ }
+ return cb(null, result);
+ });
});
- });
+ } else {
+ cursor.explain((error, result) => {
+ const _opts = { error: error };
+ return model.hooks.execPost('aggregate', this, [result], _opts, error => {
+ if (error) {
+ return cb(error);
+ }
+ return cb(null, result);
+ });
+ });
+ }
});
});
}, model.events);
};
/**
- * Sets the allowDiskUse option for the aggregation query (ignored for < 2.6.0)
+ * Sets the allowDiskUse option for the aggregation query
*
- * ####Example:
+ * #### Example:
*
* await Model.aggregate([{ $match: { foo: 'bar' } }]).allowDiskUse(true);
*
* @param {Boolean} value Should tell server it can use hard drive to store data during aggregation.
- * @param {Array} [tags] optional tags for this query
- * @see mongodb http://docs.mongodb.org/manual/reference/command/aggregate/
+ * @return {Aggregate} this
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/command/aggregate/
*/
Aggregate.prototype.allowDiskUse = function(value) {
@@ -756,14 +827,15 @@ Aggregate.prototype.allowDiskUse = function(value) {
};
/**
- * Sets the hint option for the aggregation query (ignored for < 3.6.0)
+ * Sets the hint option for the aggregation query
*
- * ####Example:
+ * #### Example:
*
* Model.aggregate(..).hint({ qty: 1, category: 1 }).exec(callback)
*
* @param {Object|String} value a hint object or the index name
- * @see mongodb http://docs.mongodb.org/manual/reference/command/aggregate/
+ * @return {Aggregate} this
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/command/aggregate/
*/
Aggregate.prototype.hint = function(value) {
@@ -774,13 +846,14 @@ Aggregate.prototype.hint = function(value) {
/**
* Sets the session for this aggregation. Useful for [transactions](/docs/transactions.html).
*
- * ####Example:
+ * #### Example:
*
* const session = await Model.startSession();
* await Model.aggregate(..).session(session);
*
* @param {ClientSession} session
- * @see mongodb http://docs.mongodb.org/manual/reference/command/aggregate/
+ * @return {Aggregate} this
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/command/aggregate/
*/
Aggregate.prototype.session = function(session) {
@@ -795,17 +868,17 @@ Aggregate.prototype.session = function(session) {
/**
* Lets you set arbitrary options, for middleware or plugins.
*
- * ####Example:
+ * #### Example:
*
* const agg = Model.aggregate(..).option({ allowDiskUse: true }); // Set the `allowDiskUse` option
* agg.options; // `{ allowDiskUse: true }`
*
* @param {Object} options keys to merge into current options
- * @param [options.maxTimeMS] number limits the time this aggregation will run, see [MongoDB docs on `maxTimeMS`](https://docs.mongodb.com/manual/reference/operator/meta/maxTimeMS/)
- * @param [options.allowDiskUse] boolean if true, the MongoDB server will use the hard drive to store data during this aggregation
- * @param [options.collation] object see [`Aggregate.prototype.collation()`](./docs/api.html#aggregate_Aggregate-collation)
- * @param [options.session] ClientSession see [`Aggregate.prototype.session()`](./docs/api.html#aggregate_Aggregate-session)
- * @see mongodb http://docs.mongodb.org/manual/reference/command/aggregate/
+ * @param {Number} [options.maxTimeMS] number limits the time this aggregation will run, see [MongoDB docs on `maxTimeMS`](https://www.mongodb.com/docs/manual/reference/operator/meta/maxTimeMS/)
+ * @param {Boolean} [options.allowDiskUse] boolean if true, the MongoDB server will use the hard drive to store data during this aggregation
+ * @param {Object} [options.collation] object see [`Aggregate.prototype.collation()`](#aggregate_Aggregate-collation)
+ * @param {ClientSession} [options.session] ClientSession see [`Aggregate.prototype.session()`](#aggregate_Aggregate-session)
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/command/aggregate/
* @return {Aggregate} this
* @api public
*/
@@ -822,7 +895,7 @@ Aggregate.prototype.option = function(value) {
* Cursors are useful if you want to process the results of the aggregation one-at-a-time
* because the aggregation result is too big to fit into memory.
*
- * ####Example:
+ * #### Example:
*
* const cursor = Model.aggregate(..).cursor({ batchSize: 1000 });
* cursor.eachAsync(function(doc, i) {
@@ -830,17 +903,14 @@ Aggregate.prototype.option = function(value) {
* });
*
* @param {Object} options
- * @param {Number} options.batchSize set the cursor batch size
+ * @param {Number} [options.batchSize] set the cursor batch size
* @param {Boolean} [options.useMongooseAggCursor] use experimental mongoose-specific aggregation cursor (for `eachAsync()` and other query cursor semantics)
* @return {AggregationCursor} cursor representing this aggregation
* @api public
- * @see mongodb http://mongodb.github.io/node-mongodb-native/2.0/api/AggregationCursor.html
+ * @see mongodb https://mongodb.github.io/node-mongodb-native/4.9/classes/AggregationCursor.html
*/
Aggregate.prototype.cursor = function(options) {
- if (!this.options) {
- this.options = {};
- }
this.options.cursor = options || {};
return new AggregationCursor(this); // return this;
};
@@ -848,20 +918,17 @@ Aggregate.prototype.cursor = function(options) {
/**
* Adds a collation
*
- * ####Example:
+ * #### Example:
*
* const res = await Model.aggregate(pipeline).collation({ locale: 'en_US', strength: 1 });
*
* @param {Object} collation options
* @return {Aggregate} this
* @api public
- * @see mongodb http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#aggregate
+ * @see mongodb https://mongodb.github.io/node-mongodb-native/4.9/interfaces/CollationOptions.html
*/
Aggregate.prototype.collation = function(collation) {
- if (!this.options) {
- this.options = {};
- }
this.options.collation = collation;
return this;
};
@@ -869,18 +936,18 @@ Aggregate.prototype.collation = function(collation) {
/**
* Combines multiple aggregation pipelines.
*
- * ####Example:
+ * #### Example:
*
* const res = await Model.aggregate().facet({
* books: [{ groupBy: '$author' }],
* price: [{ $bucketAuto: { groupBy: '$price', buckets: 2 } }]
- * });
+ * });
*
* // Output: { books: [...], price: [{...}, {...}] }
*
* @param {Object} facet options
* @return {Aggregate} this
- * @see $facet https://docs.mongodb.com/v3.4/reference/operator/aggregation/facet/
+ * @see $facet https://www.mongodb.com/docs/manual/reference/operator/aggregation/facet/
* @api public
*/
@@ -889,10 +956,10 @@ Aggregate.prototype.facet = function(options) {
};
/**
- * Helper for [Atlas Text Search](https://docs.atlas.mongodb.com/reference/atlas-search/tutorial/)'s
+ * Helper for [Atlas Text Search](https://www.mongodb.com/docs/atlas/atlas-search/tutorial/)'s
* `$search` stage.
*
- * ####Example:
+ * #### Example:
*
* const res = await Model.aggregate().
* search({
@@ -906,7 +973,7 @@ Aggregate.prototype.facet = function(options) {
*
* @param {Object} $search options
* @return {Aggregate} this
- * @see $search https://docs.atlas.mongodb.com/reference/atlas-search/tutorial/
+ * @see $search https://www.mongodb.com/docs/atlas/atlas-search/tutorial/
* @api public
*/
@@ -917,15 +984,14 @@ Aggregate.prototype.search = function(options) {
/**
* Returns the current pipeline
*
- * ####Example:
+ * #### Example:
*
* MyModel.aggregate().match({ test: 1 }).pipeline(); // [{ $match: { test: 1 } }]
*
- * @return {Array}
+ * @return {Array} The current pipeline similar to the operation that will be executed
* @api public
*/
-
Aggregate.prototype.pipeline = function() {
return this._pipeline;
};
@@ -933,17 +999,15 @@ Aggregate.prototype.pipeline = function() {
/**
* Executes the aggregate pipeline on the currently bound Model.
*
- * ####Example:
+ * #### Example:
*
* aggregate.exec(callback);
*
* // Because a promise is returned, the `callback` is optional.
- * const promise = aggregate.exec();
- * promise.then(..);
+ * const result = await aggregate.exec();
*
- * @see Promise #promise_Promise
* @param {Function} [callback]
- * @return {Promise}
+ * @return {Promise} Returns a Promise if no "callback" is given.
* @api public
*/
@@ -955,13 +1019,14 @@ Aggregate.prototype.exec = function(callback) {
const collection = this._model.collection;
applyGlobalMaxTimeMS(this.options, model);
+ applyGlobalDiskUse(this.options, model);
if (this.options && this.options.cursor) {
return new AggregationCursor(this);
}
return promiseOrCallback(callback, cb => {
- prepareDiscriminatorPipeline(this);
+ prepareDiscriminatorPipeline(this._pipeline, this._model.schema);
stringifyFunctionOperators(this._pipeline);
model.hooks.execPre('aggregate', this, error => {
@@ -998,13 +1063,13 @@ Aggregate.prototype.exec = function(callback) {
};
/**
- * Provides promise for aggregate.
+ * Provides a Promise-like `then` function, which will call `.exec` without a callback
+ * Compatible with `await`.
*
- * ####Example:
+ * #### Example:
*
* Model.aggregate(..).then(successCallback, errorCallback);
*
- * @see Promise #promise_Promise
* @param {Function} [resolve] successCallback
* @param {Function} [reject] errorCallback
* @return {Promise}
@@ -1017,6 +1082,7 @@ Aggregate.prototype.then = function(resolve, reject) {
* Executes the query returning a `Promise` which will be
* resolved with either the doc(s) or rejected with the error.
* Like [`.then()`](#query_Query-then), but only takes a rejection handler.
+ * Compatible with `await`.
*
* @param {Function} [reject]
* @return {Promise}
@@ -1028,11 +1094,11 @@ Aggregate.prototype.catch = function(reject) {
};
/**
- * Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js
+ * Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js)
* You do not need to call this function explicitly, the JavaScript runtime
* will call it for you.
*
- * ####Example
+ * #### Example:
*
* const agg = Model.aggregate([{ $match: { age: { $gte: 25 } } }]);
* for await (const doc of agg) {
@@ -1046,7 +1112,7 @@ Aggregate.prototype.catch = function(reject) {
* `Symbol.asyncIterator` is undefined, that means your Node.js version does not
* support async iterators.
*
- * @method Symbol.asyncIterator
+ * @method [Symbol.asyncIterator]
* @memberOf Aggregate
* @instance
* @api public
@@ -1054,9 +1120,7 @@ Aggregate.prototype.catch = function(reject) {
if (Symbol.asyncIterator != null) {
Aggregate.prototype[Symbol.asyncIterator] = function() {
- return this.cursor({ useMongooseAggCursor: true }).
- transformNull().
- _transformForAsyncIterator();
+ return this.cursor({ useMongooseAggCursor: true }).transformNull()._transformForAsyncIterator();
};
}
@@ -1073,61 +1137,26 @@ if (Symbol.asyncIterator != null) {
*/
function isOperator(obj) {
- if (typeof obj !== 'object') {
+ if (typeof obj !== 'object' || obj === null) {
return false;
}
const k = Object.keys(obj);
- return k.length === 1 && k.some(key => { return key[0] === '$'; });
+ return k.length === 1 && k[0][0] === '$';
}
-/*!
+/**
* Adds the appropriate `$match` pipeline step to the top of an aggregate's
* pipeline, should it's model is a non-root discriminator type. This is
* analogous to the `prepareDiscriminatorCriteria` function in `lib/query.js`.
*
* @param {Aggregate} aggregate Aggregate to prepare
+ * @api private
*/
Aggregate._prepareDiscriminatorPipeline = prepareDiscriminatorPipeline;
-function prepareDiscriminatorPipeline(aggregate) {
- const schema = aggregate._model.schema;
- const discriminatorMapping = schema && schema.discriminatorMapping;
-
- if (discriminatorMapping && !discriminatorMapping.isRoot) {
- const originalPipeline = aggregate._pipeline;
- const discriminatorKey = discriminatorMapping.key;
- const discriminatorValue = discriminatorMapping.value;
-
- // If the first pipeline stage is a match and it doesn't specify a `__t`
- // key, add the discriminator key to it. This allows for potential
- // aggregation query optimizations not to be disturbed by this feature.
- if (originalPipeline[0] != null && originalPipeline[0].$match && !originalPipeline[0].$match[discriminatorKey]) {
- originalPipeline[0].$match[discriminatorKey] = discriminatorValue;
- // `originalPipeline` is a ref, so there's no need for
- // aggregate._pipeline = originalPipeline
- } else if (originalPipeline[0] != null && originalPipeline[0].$geoNear) {
- originalPipeline[0].$geoNear.query =
- originalPipeline[0].$geoNear.query || {};
- originalPipeline[0].$geoNear.query[discriminatorKey] = discriminatorValue;
- } else if (originalPipeline[0] != null && originalPipeline[0].$search) {
- if (originalPipeline[1] && originalPipeline[1].$match != null) {
- originalPipeline[1].$match[discriminatorKey] = originalPipeline[1].$match[discriminatorKey] || discriminatorValue;
- } else {
- const match = {};
- match[discriminatorKey] = discriminatorValue;
- originalPipeline.splice(1, 0, { $match: match });
- }
- } else {
- const match = {};
- match[discriminatorKey] = discriminatorValue;
- aggregate._pipeline.unshift({ $match: match });
- }
- }
-}
-
/*!
* Exports
*/
diff --git a/lib/browser.js b/lib/browser.js
index f71a4793cb4..12664eb03ca 100644
--- a/lib/browser.js
+++ b/lib/browser.js
@@ -46,7 +46,7 @@ exports.Error = require('./error/index');
/**
* The Mongoose [Schema](#schema_Schema) constructor
*
- * ####Example:
+ * #### Example:
*
* const mongoose = require('mongoose');
* const Schema = mongoose.Schema;
@@ -61,18 +61,18 @@ exports.Schema = require('./schema');
/**
* The various Mongoose Types.
*
- * ####Example:
+ * #### Example:
*
* const mongoose = require('mongoose');
* const array = mongoose.Types.Array;
*
- * ####Types:
+ * #### Types:
*
* - [Array](/docs/schematypes.html#arrays)
* - [Buffer](/docs/schematypes.html#buffers)
* - [Embedded](/docs/schematypes.html#schemas)
* - [DocumentArray](/docs/api/documentarraypath.html)
- * - [Decimal128](/docs/api.html#mongoose_Mongoose-Decimal128)
+ * - [Decimal128](/docs/api/mongoose.html#mongoose_Mongoose-Decimal128)
* - [ObjectId](/docs/schematypes.html#objectids)
* - [Map](/docs/schematypes.html#maps)
* - [Subdocument](/docs/schematypes.html#schemas)
@@ -98,12 +98,12 @@ exports.VirtualType = require('./virtualtype');
/**
* The various Mongoose SchemaTypes.
*
- * ####Note:
+ * #### Note:
*
* _Alias of mongoose.Schema.Types for backwards compatibility._
*
* @property SchemaTypes
- * @see Schema.SchemaTypes #schema_Schema.Types
+ * @see Schema.SchemaTypes #schema_Schema-Types
* @api public
*/
diff --git a/lib/browserDocument.js b/lib/browserDocument.js
index a44f3077e01..bf9b22a0bf4 100644
--- a/lib/browserDocument.js
+++ b/lib/browserDocument.js
@@ -17,9 +17,10 @@ const isObject = require('./helpers/isObject');
* Document constructor.
*
* @param {Object} obj the values to set
+ * @param {Object} schema
* @param {Object} [fields] optional object containing the fields which were selected in the query returning this document and any populated paths data
* @param {Boolean} [skipId] bool, should we auto create an ObjectId _id
- * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
+ * @inherits NodeJS EventEmitter https://nodejs.org/api/events.html#class-eventemitter
* @event `init`: Emitted on a document after it has was retrieved from the db and fully hydrated by Mongoose.
* @event `save`: Emitted when the document is successfully saved
* @api private
diff --git a/lib/cast.js b/lib/cast.js
index cb99ed6a490..e7bf5b45a05 100644
--- a/lib/cast.js
+++ b/lib/cast.js
@@ -7,6 +7,8 @@
const CastError = require('./error/cast');
const StrictModeError = require('./error/strict');
const Types = require('./schema/index');
+const cast$expr = require('./helpers/query/cast$expr');
+const castString = require('./cast/string');
const castTextSearch = require('./schema/operators/text');
const get = require('./helpers/get');
const getConstructorName = require('./helpers/getConstructorName');
@@ -23,8 +25,11 @@ const ALLOWED_GEOWITHIN_GEOJSON_TYPES = ['Polygon', 'MultiPolygon'];
*
* @param {Schema} schema
* @param {Object} obj Object to cast
- * @param {Object} options the query options
- * @param {Query} context passed to setters
+ * @param {Object} [options] the query options
+ * @param {Boolean|"throw"} [options.strict] Wheter to enable all strict options
+ * @param {Boolean|"throw"} [options.strictQuery] Enable strict Queries
+ * @param {Boolean} [options.upsert]
+ * @param {Query} [context] passed to setters
* @api private
*/
module.exports = function cast(schema, obj, options, context) {
@@ -36,14 +41,6 @@ module.exports = function cast(schema, obj, options, context) {
return obj;
}
- // bson 1.x has the unfortunate tendency to remove filters that have a top-level
- // `_bsontype` property. But we should still allow ObjectIds because
- // `Collection#find()` has a special case to support `find(objectid)`.
- // Should remove this when we upgrade to bson 4.x. See gh-8222, gh-8268
- if (obj.hasOwnProperty('_bsontype') && obj._bsontype !== 'ObjectID') {
- delete obj._bsontype;
- }
-
if (schema != null && schema.discriminators != null && obj[schema.options.discriminatorKey] != null) {
schema = getSchemaDiscriminatorByValue(schema, obj[schema.options.discriminatorKey]) || schema;
}
@@ -87,14 +84,15 @@ module.exports = function cast(schema, obj, options, context) {
continue;
} else if (path === '$expr') {
- if (typeof val !== 'object' || val == null) {
- throw new Error('`$expr` must be an object');
- }
+ val = cast$expr(val, schema);
continue;
} else if (path === '$elemMatch') {
val = cast(schema, val, options, context);
} else if (path === '$text') {
val = castTextSearch(val, path);
+ } else if (path === '$comment' && !schema.paths.hasOwnProperty('$comment')) {
+ val = castString(val, path);
+ obj[path] = val;
} else {
if (!schema) {
// no casting for Mixed types
@@ -111,15 +109,18 @@ module.exports = function cast(schema, obj, options, context) {
const pathFirstHalf = split.slice(0, j).join('.');
const pathLastHalf = split.slice(j).join('.');
const _schematype = schema.path(pathFirstHalf);
- const discriminatorKey = get(_schematype, 'schema.options.discriminatorKey');
+ const discriminatorKey = _schematype &&
+ _schematype.schema &&
+ _schematype.schema.options &&
+ _schematype.schema.options.discriminatorKey;
// gh-6027: if we haven't found the schematype but this path is
// underneath an embedded discriminator and the embedded discriminator
// key is in the query, use the embedded discriminator schema
if (_schematype != null &&
- get(_schematype, 'schema.discriminators') != null &&
- discriminatorKey != null &&
- pathLastHalf !== discriminatorKey) {
+ (_schematype.schema && _schematype.schema.discriminators) != null &&
+ discriminatorKey != null &&
+ pathLastHalf !== discriminatorKey) {
const discriminatorVal = get(obj, pathFirstHalf + '.' + discriminatorKey);
if (discriminatorVal != null) {
schematype = _schematype.schema.discriminators[discriminatorVal].
@@ -153,7 +154,13 @@ module.exports = function cast(schema, obj, options, context) {
remainingConds = {};
pathLastHalf = split.slice(j).join('.');
remainingConds[pathLastHalf] = val;
- obj[path] = cast(schematype.caster.schema, remainingConds, options, context)[pathLastHalf];
+
+ const ret = cast(schematype.caster.schema, remainingConds, options, context)[pathLastHalf];
+ if (ret === void 0) {
+ delete obj[path];
+ } else {
+ obj[path] = ret;
+ }
} else {
obj[path] = val;
}
@@ -259,16 +266,19 @@ module.exports = function cast(schema, obj, options, context) {
if (schema.nested[path]) {
continue;
}
- if (options.upsert && options.strict) {
- if (options.strict === 'throw') {
+
+ const strict = 'strict' in options ? options.strict : schema.options.strict;
+ const strictQuery = getStrictQuery(options, schema._userProvidedOptions, schema.options, context);
+ if (options.upsert && strict) {
+ if (strict === 'throw') {
throw new StrictModeError(path);
}
throw new StrictModeError(path, 'Path "' + path + '" is not in ' +
'schema, strict mode is `true`, and upsert is `true`.');
- } if (options.strictQuery === 'throw') {
+ } if (strictQuery === 'throw') {
throw new StrictModeError(path, 'Path "' + path + '" is not in ' +
'schema and strictQuery is \'throw\'.');
- } else if (options.strictQuery) {
+ } else if (strictQuery) {
delete obj[path];
}
} else if (val == null) {
@@ -366,4 +376,33 @@ function _cast(val, numbertype, context) {
}
}
}
-}
\ No newline at end of file
+}
+
+function getStrictQuery(queryOptions, schemaUserProvidedOptions, schemaOptions, context) {
+ if ('strictQuery' in queryOptions) {
+ return queryOptions.strictQuery;
+ }
+ if ('strict' in queryOptions) {
+ return queryOptions.strict;
+ }
+ if ('strictQuery' in schemaUserProvidedOptions) {
+ return schemaUserProvidedOptions.strictQuery;
+ }
+ if ('strict' in schemaUserProvidedOptions) {
+ return schemaUserProvidedOptions.strict;
+ }
+ const mongooseOptions = context &&
+ context.mongooseCollection &&
+ context.mongooseCollection.conn &&
+ context.mongooseCollection.conn.base &&
+ context.mongooseCollection.conn.base.options;
+ if (mongooseOptions) {
+ if ('strictQuery' in mongooseOptions) {
+ return mongooseOptions.strictQuery;
+ }
+ if ('strict' in mongooseOptions) {
+ return mongooseOptions.strict;
+ }
+ }
+ return schemaOptions.strictQuery;
+}
diff --git a/lib/cast/boolean.js b/lib/cast/boolean.js
index 92551d41e3e..aaa1fb066b6 100644
--- a/lib/cast/boolean.js
+++ b/lib/cast/boolean.js
@@ -2,7 +2,7 @@
const CastError = require('../error/cast');
-/*!
+/**
* Given a value, cast it to a boolean, or throw a `CastError` if the value
* cannot be casted. `null` and `undefined` are considered valid.
*
diff --git a/lib/cast/date.js b/lib/cast/date.js
index ac17006df70..c7f9b048e9d 100644
--- a/lib/cast/date.js
+++ b/lib/cast/date.js
@@ -38,4 +38,4 @@ module.exports = function castDate(value) {
}
assert.ok(false);
-};
\ No newline at end of file
+};
diff --git a/lib/cast/decimal128.js b/lib/cast/decimal128.js
index bfb1578c406..2cd9208af93 100644
--- a/lib/cast/decimal128.js
+++ b/lib/cast/decimal128.js
@@ -33,4 +33,4 @@ module.exports = function castDecimal128(value) {
}
assert.ok(false);
-};
\ No newline at end of file
+};
diff --git a/lib/cast/number.js b/lib/cast/number.js
index 7cd7f848c53..cf0362de186 100644
--- a/lib/cast/number.js
+++ b/lib/cast/number.js
@@ -2,13 +2,12 @@
const assert = require('assert');
-/*!
- * Given a value, cast it to a number, or throw a `CastError` if the value
+/**
+ * Given a value, cast it to a number, or throw an `Error` if the value
* cannot be casted. `null` and `undefined` are considered valid.
*
* @param {Any} value
- * @param {String} [path] optional the path to set on the CastError
- * @return {Boolean|null|undefined}
+ * @return {Number}
* @throws {Error} if `value` is not one of the allowed values
* @api private
*/
diff --git a/lib/cast/objectid.js b/lib/cast/objectid.js
index 67cffb54304..7321c0cccf7 100644
--- a/lib/cast/objectid.js
+++ b/lib/cast/objectid.js
@@ -1,19 +1,19 @@
'use strict';
+const isBsonType = require('../helpers/isBsonType');
const ObjectId = require('../driver').get().ObjectId;
-const assert = require('assert');
module.exports = function castObjectId(value) {
if (value == null) {
return value;
}
- if (value instanceof ObjectId) {
+ if (isBsonType(value, 'ObjectID')) {
return value;
}
if (value._id) {
- if (value._id instanceof ObjectId) {
+ if (isBsonType(value._id, 'ObjectID')) {
return value._id;
}
if (value._id.toString instanceof Function) {
@@ -25,5 +25,5 @@ module.exports = function castObjectId(value) {
return new ObjectId(value.toString());
}
- assert.ok(false);
-};
\ No newline at end of file
+ return new ObjectId(value);
+};
diff --git a/lib/cast/string.js b/lib/cast/string.js
index 4d89f8e2b74..aabb822c5e3 100644
--- a/lib/cast/string.js
+++ b/lib/cast/string.js
@@ -2,7 +2,7 @@
const CastError = require('../error/cast');
-/*!
+/**
* Given a value, cast it to a string, or throw a `CastError` if the value
* cannot be casted. `null` and `undefined` are considered valid.
*
diff --git a/lib/collection.js b/lib/collection.js
index 91206362c27..35633a6e2d3 100644
--- a/lib/collection.js
+++ b/lib/collection.js
@@ -15,7 +15,7 @@ const immediate = require('./helpers/immediate');
*
* @param {String} name name of the collection
* @param {Connection} conn A MongooseConnection instance
- * @param {Object} opts optional collection options
+ * @param {Object} [opts] optional collection options
* @api public
*/
@@ -23,13 +23,6 @@ function Collection(name, conn, opts) {
if (opts === void 0) {
opts = {};
}
- if (opts.capped === void 0) {
- opts.capped = {};
- }
-
- if (typeof opts.capped === 'number') {
- opts.capped = { size: opts.capped };
- }
this.opts = opts;
this.name = name;
diff --git a/lib/connection.js b/lib/connection.js
index f031bbe5666..1b6fcbae40f 100644
--- a/lib/connection.js
+++ b/lib/connection.js
@@ -7,12 +7,14 @@
const ChangeStream = require('./cursor/ChangeStream');
const EventEmitter = require('events').EventEmitter;
const Schema = require('./schema');
-const Collection = require('./driver').get().Collection;
const STATES = require('./connectionstate');
const MongooseError = require('./error/index');
+const DisconnectedError = require('./error/disconnected');
+const SyncIndexesError = require('./error/syncIndexes');
const PromiseProvider = require('./promise_provider');
const ServerSelectionError = require('./error/serverSelection');
const applyPlugins = require('./helpers/schema/applyPlugins');
+const driver = require('./driver');
const promiseOrCallback = require('./helpers/promiseOrCallback');
const get = require('./helpers/get');
const immediate = require('./helpers/immediate');
@@ -24,7 +26,7 @@ const processConnectionOptions = require('./helpers/processConnectionOptions');
const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol;
const sessionNewDocuments = require('./helpers/symbols').sessionNewDocuments;
-/*!
+/**
* A list of authentication mechanisms that don't require a password for authentication.
* This is used by the authMechanismDoesNotRequirePassword method.
*
@@ -40,13 +42,13 @@ const noPasswordAuthMechanisms = [
* For practical reasons, a Connection equals a Db.
*
* @param {Mongoose} base a mongoose instance
- * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
+ * @inherits NodeJS EventEmitter https://nodejs.org/api/events.html#class-eventemitter
* @event `connecting`: Emitted when `connection.openUri()` is executed on this connection.
* @event `connected`: Emitted when this connection successfully connects to the db. May be emitted _multiple_ times in `reconnected` scenarios.
- * @event `open`: Emitted after we `connected` and `onOpen` is executed on all of this connections models.
+ * @event `open`: Emitted after we `connected` and `onOpen` is executed on all of this connection's models.
* @event `disconnecting`: Emitted when `connection.close()` was executed.
* @event `disconnected`: Emitted after getting disconnected from the db.
- * @event `close`: Emitted after we `disconnected` and `onClose` executed on all of this connections models.
+ * @event `close`: Emitted after we `disconnected` and `onClose` executed on all of this connection's models.
* @event `reconnected`: Emitted after we `connected` and subsequently `disconnected`, followed by successfully another successful connection.
* @event `error`: Emitted when an error occurs on this connection.
* @event `fullsetup`: Emitted after the driver has connected to primary and all secondaries if specified in the connection string.
@@ -70,7 +72,7 @@ function Connection(base) {
if (typeof base === 'undefined' || !base.connections.length) {
this.id = 0;
} else {
- this.id = base.connections.length;
+ this.id = base.nextConnectionId;
}
this._queue = [];
}
@@ -79,7 +81,7 @@ function Connection(base) {
* Inherit from EventEmitter
*/
-Connection.prototype.__proto__ = EventEmitter.prototype;
+Object.setPrototypeOf(Connection.prototype, EventEmitter.prototype);
/**
* Connection ready state
@@ -91,7 +93,7 @@ Connection.prototype.__proto__ = EventEmitter.prototype;
*
* Each state change emits its associated event name.
*
- * ####Example
+ * #### Example:
*
* conn.on('connected', callback);
* conn.on('disconnected', callback);
@@ -130,7 +132,7 @@ Object.defineProperty(Connection.prototype, 'readyState', {
/**
* Gets the value of the option `key`. Equivalent to `conn.options[key]`
*
- * ####Example:
+ * #### Example:
*
* conn.get('test'); // returns the 'test' value
*
@@ -152,9 +154,10 @@ Connection.prototype.get = function(key) {
*
* Supported options include:
*
- * - `maxTimeMS`: Set [`maxTimeMS`](/docs/api.html#query_Query-maxTimeMS) for all queries on this connection.
+ * - `maxTimeMS`: Set [`maxTimeMS`](/docs/api/query.html#query_Query-maxTimeMS) for all queries on this connection.
+ * - 'debug': If `true`, prints the operations mongoose sends to MongoDB to the console. If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arugments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`.
*
- * ####Example:
+ * #### Example:
*
* conn.set('test', 'foo');
* conn.get('test'); // 'foo'
@@ -191,9 +194,9 @@ Connection.prototype.collections;
/**
* The name of the database this connection points to.
*
- * ####Example
+ * #### Example:
*
- * mongoose.createConnection('mongodb://localhost:27017/mydb').name; // "mydb"
+ * mongoose.createConnection('mongodb://127.0.0.1:27017/mydb').name; // "mydb"
*
* @property name
* @memberOf Connection
@@ -208,7 +211,7 @@ Connection.prototype.name;
* a map from model names to models. Contains all models that have been
* added to this connection using [`Connection#model()`](/docs/api/connection.html#connection_Connection-model).
*
- * ####Example
+ * #### Example:
*
* const conn = mongoose.createConnection();
* const Test = conn.model('Test', mongoose.Schema({ name: String }));
@@ -228,7 +231,7 @@ Connection.prototype.models;
* A number identifier for this connection. Used for debugging when
* you have [multiple connections](/docs/connections.html#multiple_connections).
*
- * ####Example
+ * #### Example:
*
* // The default connection has `id = 0`
* mongoose.connection.id; // 0
@@ -248,9 +251,9 @@ Connection.prototype.id;
/**
* The plugins that will be applied to all models created on this connection.
*
- * ####Example:
+ * #### Example:
*
- * const db = mongoose.createConnection('mongodb://localhost:27017/mydb');
+ * const db = mongoose.createConnection('mongodb://127.0.0.1:27017/mydb');
* db.plugin(() => console.log('Applied'));
* db.plugins.length; // 1
*
@@ -272,9 +275,9 @@ Object.defineProperty(Connection.prototype, 'plugins', {
* The host name portion of the URI. If multiple hosts, such as a replica set,
* this will contain the first host name in the URI
*
- * ####Example
+ * #### Example:
*
- * mongoose.createConnection('mongodb://localhost:27017/mydb').host; // "localhost"
+ * mongoose.createConnection('mongodb://127.0.0.1:27017/mydb').host; // "127.0.0.1"
*
* @property host
* @memberOf Connection
@@ -292,9 +295,9 @@ Object.defineProperty(Connection.prototype, 'host', {
* The port portion of the URI. If multiple hosts, such as a replica set,
* this will contain the port from the first host name in the URI.
*
- * ####Example
+ * #### Example:
*
- * mongoose.createConnection('mongodb://localhost:27017/mydb').port; // 27017
+ * mongoose.createConnection('mongodb://127.0.0.1:27017/mydb').port; // 27017
*
* @property port
* @memberOf Connection
@@ -311,9 +314,9 @@ Object.defineProperty(Connection.prototype, 'port', {
/**
* The username specified in the URI
*
- * ####Example
+ * #### Example:
*
- * mongoose.createConnection('mongodb://val:psw@localhost:27017/mydb').user; // "val"
+ * mongoose.createConnection('mongodb://val:psw@127.0.0.1:27017/mydb').user; // "val"
*
* @property user
* @memberOf Connection
@@ -330,9 +333,9 @@ Object.defineProperty(Connection.prototype, 'user', {
/**
* The password specified in the URI
*
- * ####Example
+ * #### Example:
*
- * mongoose.createConnection('mongodb://val:psw@localhost:27017/mydb').pass; // "psw"
+ * mongoose.createConnection('mongodb://val:psw@127.0.0.1:27017/mydb').pass; // "psw"
*
* @property pass
* @memberOf Connection
@@ -382,16 +385,16 @@ Connection.prototype.config;
/**
* Helper for `createCollection()`. Will explicitly create the given collection
- * with specified options. Used to create [capped collections](https://docs.mongodb.com/manual/core/capped-collections/)
- * and [views](https://docs.mongodb.com/manual/core/views/) from mongoose.
+ * with specified options. Used to create [capped collections](https://www.mongodb.com/docs/manual/core/capped-collections/)
+ * and [views](https://www.mongodb.com/docs/manual/core/views/) from mongoose.
*
- * Options are passed down without modification to the [MongoDB driver's `createCollection()` function](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection)
+ * Options are passed down without modification to the [MongoDB driver's `createCollection()` function](https://mongodb.github.io/node-mongodb-native/4.9/classes/Db.html#createCollection)
*
* @method createCollection
* @param {string} collection The collection to create
- * @param {Object} [options] see [MongoDB driver docs](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection)
+ * @param {Object} [options] see [MongoDB driver docs](https://mongodb.github.io/node-mongodb-native/4.9/classes/Db.html#createCollection)
* @param {Function} [callback]
- * @return {Promise}
+ * @return {Promise} Returns a Promise if no `callback` is given.
* @api public
*/
@@ -404,11 +407,11 @@ Connection.prototype.createCollection = _wrapConnHelper(function createCollectio
});
/**
- * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions)
- * for benefits like causal consistency, [retryable writes](https://docs.mongodb.com/manual/core/retryable-writes/),
- * and [transactions](http://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html).
+ * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://www.mongodb.com/docs/manual/release-notes/3.6/#client-sessions)
+ * for benefits like causal consistency, [retryable writes](https://www.mongodb.com/docs/manual/core/retryable-writes/),
+ * and [transactions](https://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html).
*
- * ####Example:
+ * #### Example:
*
* const session = await conn.startSession();
* let doc = await Person.findOne({ name: 'Ned Stark' }, null, { session });
@@ -421,7 +424,7 @@ Connection.prototype.createCollection = _wrapConnHelper(function createCollectio
*
*
* @method startSession
- * @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html#startSession)
+ * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/MongoClient.html#startSession)
* @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency
* @param {Function} [callback]
* @return {Promise<ClientSession>} promise that resolves to a MongoDB driver `ClientSession`
@@ -443,10 +446,10 @@ Connection.prototype.startSession = _wrapConnHelper(function startSession(option
* async function executes successfully and attempt to retry if
* there was a retriable error.
*
- * Calls the MongoDB driver's [`session.withTransaction()`](http://mongodb.github.io/node-mongodb-native/3.5/api/ClientSession.html#withTransaction),
+ * Calls the MongoDB driver's [`session.withTransaction()`](https://mongodb.github.io/node-mongodb-native/4.9/classes/ClientSession.html#withTransaction),
* but also handles resetting Mongoose document state as shown below.
*
- * ####Example:
+ * #### Example:
*
* const doc = new Person({ name: 'Will Riker' });
* await db.transaction(async function setRank(session) {
@@ -489,6 +492,9 @@ Connection.prototype.transaction = function transaction(fn, options) {
doc.set(doc.schema.options.versionKey, state.versionKey);
}
+ if (state.modifiedPaths.length > 0 && doc.$__.activePaths.states.modify == null) {
+ doc.$__.activePaths.states.modify = {};
+ }
for (const path of state.modifiedPaths) {
doc.$__.activePaths.paths[path] = 'modify';
doc.$__.activePaths.states.modify[path] = true;
@@ -504,6 +510,10 @@ Connection.prototype.transaction = function transaction(fn, options) {
}
delete session[sessionNewDocuments];
throw err;
+ })
+ .finally(() => {
+ session.endSession()
+ .catch(() => {});
});
});
};
@@ -515,7 +525,7 @@ Connection.prototype.transaction = function transaction(fn, options) {
* @method dropCollection
* @param {string} collection The collection to delete
* @param {Function} [callback]
- * @return {Promise}
+ * @return {Promise} Returns a Promise if no `callback` is given.
* @api public
*/
@@ -527,15 +537,15 @@ Connection.prototype.dropCollection = _wrapConnHelper(function dropCollection(co
* Helper for `dropDatabase()`. Deletes the given database, including all
* collections, documents, and indexes.
*
- * ####Example:
+ * #### Example:
*
- * const conn = mongoose.createConnection('mongodb://localhost:27017/mydb');
+ * const conn = mongoose.createConnection('mongodb://127.0.0.1:27017/mydb');
* // Deletes the entire 'mydb' database
* await conn.dropDatabase();
*
* @method dropDatabase
* @param {Function} [callback]
- * @return {Promise}
+ * @return {Promise} Returns a Promise if no `callback` is given.
* @api public
*/
@@ -543,9 +553,9 @@ Connection.prototype.dropDatabase = _wrapConnHelper(function dropDatabase(cb) {
// If `dropDatabase()` is called, this model's collection will not be
// init-ed. It is sufficiently common to call `dropDatabase()` after
// `mongoose.connect()` but before creating models that we want to
- // support this. See gh-6967
- for (const name of Object.keys(this.models)) {
- delete this.models[name].$init;
+ // support this. See gh-6796
+ for (const model of Object.values(this.models)) {
+ delete model.$init;
}
this.db.dropDatabase(cb);
});
@@ -560,8 +570,7 @@ function _wrapConnHelper(fn) {
const argsWithoutCb = typeof cb === 'function' ?
Array.prototype.slice.call(arguments, 0, arguments.length - 1) :
Array.prototype.slice.call(arguments);
- const disconnectedError = new MongooseError('Connection ' + this.id +
- ' was disconnected when calling `' + fn.name + '`');
+ const disconnectedError = new DisconnectedError(this.id, fn.name);
return promiseOrCallback(cb, cb => {
immediate(() => {
@@ -603,6 +612,8 @@ Connection.prototype._shouldBufferCommands = function _shouldBufferCommands() {
*
* @param {Error} err
* @param {Function} callback optional
+ * @emits "error" Emits the `error` event with the given `err`, unless a callback is specified
+ * @returns {Promise|null} Returns a rejected Promise if no `callback` is given.
* @api private
*/
@@ -646,24 +657,24 @@ Connection.prototype.onOpen = function() {
* Opens the connection with a URI using `MongoClient.connect()`.
*
* @param {String} uri The URI to connect with.
- * @param {Object} [options] Passed on to http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html#connect
- * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](http://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection.
+ * @param {Object} [options] Passed on to [`MongoClient.connect`](https://mongodb.github.io/node-mongodb-native/4.9/classes/MongoClient.html#connect-1)
+ * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](https://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection.
* @param {Number} [options.bufferTimeoutMS=10000] Mongoose specific option. If `bufferCommands` is true, Mongoose will throw an error after `bufferTimeoutMS` if the operation is still buffered.
* @param {String} [options.dbName] The name of the database we want to use. If not provided, use database name from connection string.
* @param {String} [options.user] username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility.
* @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility.
- * @param {Number} [options.maxPoolSize=100] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
- * @param {Number} [options.minPoolSize=0] The minimum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
+ * @param {Number} [options.maxPoolSize=100] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
+ * @param {Number} [options.minPoolSize=0] The minimum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
* @param {Number} [options.serverSelectionTimeoutMS] If `useUnifiedTopology = true`, the MongoDB driver will try to find a server to send any given operation to, and keep retrying for `serverSelectionTimeoutMS` milliseconds before erroring out. If not set, the MongoDB driver defaults to using `30000` (30 seconds).
* @param {Number} [options.heartbeatFrequencyMS] If `useUnifiedTopology = true`, the MongoDB driver sends a heartbeat every `heartbeatFrequencyMS` to check on the status of the connection. A heartbeat is subject to `serverSelectionTimeoutMS`, so the MongoDB driver will retry failed heartbeats for up to 30 seconds by default. Mongoose only emits a `'disconnected'` event after a heartbeat has failed, so you may want to decrease this setting to reduce the time between when your server goes down and when Mongoose emits `'disconnected'`. We recommend you do **not** set this setting below 1000, too many heartbeats can lead to performance degradation.
* @param {Boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection.
- * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](http://mongodb.github.io/node-mongodb-native/3.1/api/MongoClient.html).
+ * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/MongoClientOptions.html#promiseLibrary).
* @param {Number} [options.connectTimeoutMS=30000] How long the MongoDB driver will wait before killing a socket due to inactivity _during initial connection_. Defaults to 30000. This option is passed transparently to [Node.js' `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback).
* @param {Number} [options.socketTimeoutMS=30000] How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. A socket may be inactive because of either no activity or a long-running operation. This is set to `30000` by default, you should set this to 2-3x your longest running operation if you expect some of your database operations to run longer than 20 seconds. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes.
* @param {Number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0, `4`, or `6`. `4` means use IPv4 only, `6` means use IPv6 only, `0` means try both.
* @param {Boolean} [options.autoCreate=false] Set to `true` to make Mongoose automatically call `createCollection()` on every model created on this connection.
* @param {Function} [callback]
- * @returns {Connection} this
+ * @returns {Promise<Connection>} Returns a Promise if no `callback` is given.
* @api public
*/
@@ -677,7 +688,7 @@ Connection.prototype.openUri = function(uri, options, callback) {
throw new MongooseError('Mongoose 5.x no longer supports ' +
'`mongoose.connect(host, dbname, port)` or ' +
'`mongoose.createConnection(host, dbname, port)`. See ' +
- 'http://mongoosejs.com/docs/connections.html for supported connection syntax');
+ 'https://mongoosejs.com/docs/connections.html for supported connection syntax');
}
if (typeof uri !== 'string') {
@@ -692,6 +703,18 @@ Connection.prototype.openUri = function(uri, options, callback) {
typeof callback + '"');
}
+ if (this._destroyCalled) {
+ const error = 'Connection has been closed and destroyed, and cannot be used for re-opening the connection. ' +
+ 'Please create a new connection with `mongoose.createConnection()` or `mongoose.connect()`.';
+ if (typeof callback === 'function') {
+ callback(error);
+ return;
+ }
+ else {
+ throw new MongooseError(error);
+ }
+ }
+
if (this.readyState === STATES.connecting || this.readyState === STATES.connected) {
if (this._connectionString !== uri) {
throw new MongooseError('Can\'t call `openUri()` on an active connection with ' +
@@ -782,6 +805,7 @@ Connection.prototype.openUri = function(uri, options, callback) {
return reject(error);
}
_this.client = client;
+
client.setMaxListeners(0);
client.connect((error) => {
if (error) {
@@ -790,6 +814,10 @@ Connection.prototype.openUri = function(uri, options, callback) {
_setClient(_this, client, options, dbName);
+ for (const db of this.otherDbs) {
+ _setClient(db, client, {}, db.name);
+ }
+
resolve(_this);
});
});
@@ -816,6 +844,11 @@ Connection.prototype.openUri = function(uri, options, callback) {
);
}
+ for (const model of Object.values(this.models)) {
+ // Errors handled internally, so safe to ignore error
+ model.init(function $modelInitNoop() {});
+ }
+
return this.$initialConnection;
};
@@ -827,9 +860,19 @@ function _setClient(conn, client, options, dbName) {
const db = dbName != null ? client.db(dbName) : client.db();
conn.db = db;
conn.client = client;
- conn.host = get(client, 's.options.hosts.0.host', void 0);
- conn.port = get(client, 's.options.hosts.0.port', void 0);
- conn.name = dbName != null ? dbName : get(client, 's.options.dbName', void 0);
+ conn.host = client &&
+ client.s &&
+ client.s.options &&
+ client.s.options.hosts &&
+ client.s.options.hosts[0] &&
+ client.s.options.hosts[0].host || void 0;
+ conn.port = client &&
+ client.s &&
+ client.s.options &&
+ client.s.options.hosts &&
+ client.s.options.hosts[0] &&
+ client.s.options.hosts[0].port || void 0;
+ conn.name = dbName != null ? dbName : client && client.s && client.s.options && client.s.options.dbName || void 0;
conn._closeCalled = client._closeCalled;
const _handleReconnect = () => {
@@ -845,7 +888,10 @@ function _setClient(conn, client, options, dbName) {
}
};
- const type = get(client, 'topology.description.type', '');
+ const type = client &&
+ client.topology &&
+ client.topology.description &&
+ client.topology.description.type || '';
if (type === 'Single') {
client.on('serverDescriptionChanged', ev => {
@@ -878,12 +924,37 @@ function _setClient(conn, client, options, dbName) {
}
}
+/**
+ * Destory the connection (not just a alias of [`.close`](#connection_Connection-close))
+ *
+ * @param {Boolean} [force]
+ * @param {Function} [callback]
+ * @returns {Promise} Returns a Promise if no `callback` is given.
+ */
+
+Connection.prototype.destroy = function(force, callback) {
+ if (typeof force === 'function') {
+ callback = force;
+ force = false;
+ }
+
+ if (force != null && typeof force === 'object') {
+ this.$wasForceClosed = !!force.force;
+ } else {
+ this.$wasForceClosed = !!force;
+ }
+
+ return promiseOrCallback(callback, cb => {
+ this._close(force, true, cb);
+ });
+};
+
/**
* Closes the connection
*
* @param {Boolean} [force] optional
* @param {Function} [callback] optional
- * @return {Promise}
+ * @return {Promise} Returns a Promise if no `callback` is given.
* @api public
*/
@@ -893,10 +964,21 @@ Connection.prototype.close = function(force, callback) {
force = false;
}
- this.$wasForceClosed = !!force;
+ if (force != null && typeof force === 'object') {
+ this.$wasForceClosed = !!force.force;
+ } else {
+ this.$wasForceClosed = !!force;
+ }
+
+ for (const model of Object.values(this.models)) {
+ // If manually disconnecting, make sure to clear each model's `$init`
+ // promise, so Mongoose knows to re-run `init()` in case the
+ // connection is re-opened. See gh-12047.
+ delete model.$init;
+ }
return promiseOrCallback(callback, cb => {
- this._close(force, cb);
+ this._close(force, false, cb);
});
};
@@ -904,19 +986,27 @@ Connection.prototype.close = function(force, callback) {
* Handles closing the connection
*
* @param {Boolean} force
- * @param {Function} callback
+ * @param {Boolean} destroy
+ * @param {Function} [callback]
+ * @returns {Connection} this
* @api private
*/
-Connection.prototype._close = function(force, callback) {
+Connection.prototype._close = function(force, destroy, callback) {
const _this = this;
const closeCalled = this._closeCalled;
this._closeCalled = true;
+ this._destroyCalled = destroy;
if (this.client != null) {
this.client._closeCalled = true;
+ this.client._destroyCalled = destroy;
}
+ const conn = this;
switch (this.readyState) {
case STATES.disconnected:
+ if (destroy && this.base.connections.indexOf(conn) !== -1) {
+ this.base.connections.splice(this.base.connections.indexOf(conn), 1);
+ }
if (closeCalled) {
callback();
} else {
@@ -936,6 +1026,9 @@ Connection.prototype._close = function(force, callback) {
if (err) {
return callback(err);
}
+ if (destroy && _this.base.connections.indexOf(conn) !== -1) {
+ _this.base.connections.splice(_this.base.connections.indexOf(conn), 1);
+ }
_this.onClose(force);
callback(null);
});
@@ -943,12 +1036,15 @@ Connection.prototype._close = function(force, callback) {
break;
case STATES.connecting:
this.once('open', function() {
- _this.close(callback);
+ destroy ? _this.destroy(force, callback) : _this.close(force, callback);
});
break;
case STATES.disconnecting:
this.once('close', function() {
+ if (destroy && _this.base.connections.indexOf(conn) !== -1) {
+ _this.base.connections.splice(_this.base.connections.indexOf(conn), 1);
+ }
callback();
});
break;
@@ -957,6 +1053,16 @@ Connection.prototype._close = function(force, callback) {
return this;
};
+/**
+ * Abstract method that drivers must implement.
+ *
+ * @api private
+ */
+
+Connection.prototype.doClose = function() {
+ throw new Error('Connection#doClose unimplemented by driver');
+};
+
/**
* Called when the connection closes
*
@@ -977,7 +1083,7 @@ Connection.prototype.onClose = function(force) {
this.emit('close', force);
for (const db of this.otherDbs) {
- db.close(force);
+ this._destroyCalled ? db.destroy({ force: force, skipCloseClient: true }) : db.close({ force: force, skipCloseClient: true });
}
};
@@ -999,6 +1105,7 @@ Connection.prototype.collection = function(name, options) {
};
options = Object.assign({}, defaultOptions, options ? utils.clone(options) : {});
options.$wasForceClosed = this.$wasForceClosed;
+ const Collection = this.base && this.base.__driver && this.base.__driver.Collection || driver.get().Collection;
if (!(name in this.collections)) {
this.collections[name] = new Collection(name, this, options);
}
@@ -1010,8 +1117,9 @@ Connection.prototype.collection = function(name, options) {
*
* Equivalent to calling `.plugin(fn)` on each schema you create.
*
- * ####Example:
- * const db = mongoose.createConnection('mongodb://localhost:27017/mydb');
+ * #### Example:
+ *
+ * const db = mongoose.createConnection('mongodb://127.0.0.1:27017/mydb');
* db.plugin(() => console.log('Applied'));
* db.plugins.length; // 1
*
@@ -1020,7 +1128,7 @@ Connection.prototype.collection = function(name, options) {
* @param {Function} fn plugin callback
* @param {Object} [opts] optional options
* @return {Connection} this
- * @see plugins ./plugins.html
+ * @see plugins /docs/plugins.html
* @api public
*/
@@ -1038,9 +1146,9 @@ Connection.prototype.plugin = function(fn, opts) {
* const Ticket = db.model('Ticket', new Schema(..));
* const Venue = db.model('Venue');
*
- * _When no `collection` argument is passed, Mongoose produces a collection name by passing the model `name` to the [utils.toCollectionName](#utils_exports.toCollectionName) method. This method pluralizes the name. If you don't like this behavior, either pass a collection name or set your schemas collection name option._
+ * _When no `collection` argument is passed, Mongoose produces a collection name by passing the model `name` to the [utils.toCollectionName](#utils_exports-toCollectionName) method. This method pluralizes the name. If you don't like this behavior, either pass a collection name or set your schemas collection name option._
*
- * ####Example:
+ * #### Example:
*
* const schema = new Schema({ name: String }, { collection: 'actor' });
*
@@ -1082,8 +1190,12 @@ Connection.prototype.model = function(name, schema, collection, options) {
schema = false;
}
- if (utils.isObject(schema) && !(schema instanceof this.base.Schema)) {
- schema = new Schema(schema);
+ if (utils.isObject(schema)) {
+ if (!schema.instanceOfSchema) {
+ schema = new Schema(schema);
+ } else if (!(schema instanceof this.base.Schema)) {
+ schema = schema._clone(this.base.Schema);
+ }
}
if (schema && !schema.instanceOfSchema) {
throw new Error('The 2nd parameter to `mongoose.model()` should be a ' +
@@ -1129,6 +1241,14 @@ Connection.prototype.model = function(name, schema, collection, options) {
return sub;
}
+ if (arguments.length === 1) {
+ model = this.models[name];
+ if (!model) {
+ throw new MongooseError.MissingSchemaError(name);
+ }
+ return model;
+ }
+
if (!model) {
throw new MongooseError.MissingSchemaError(name);
}
@@ -1154,7 +1274,7 @@ Connection.prototype.model = function(name, schema, collection, options) {
* use this function to clean up any models you created in your tests to
* prevent OverwriteModelErrors.
*
- * ####Example:
+ * #### Example:
*
* conn.model('User', new Schema({ name: String }));
* console.log(conn.model('User')); // Model object
@@ -1200,7 +1320,7 @@ Connection.prototype.deleteModel = function(name) {
/**
* Watches the entire underlying database for changes. Similar to
- * [`Model.watch()`](/docs/api/model.html#model_Model.watch).
+ * [`Model.watch()`](/docs/api/model.html#model_Model-watch).
*
* This function does **not** trigger any middleware. In particular, it
* does **not** trigger aggregate middleware.
@@ -1212,7 +1332,7 @@ Connection.prototype.deleteModel = function(name) {
* - 'end': Emitted if the underlying stream is closed
* - 'close': Emitted if the underlying stream is closed
*
- * ####Example:
+ * #### Example:
*
* const User = conn.model('User', new Schema({ name: String }));
*
@@ -1223,13 +1343,12 @@ Connection.prototype.deleteModel = function(name) {
*
* @api public
* @param {Array} [pipeline]
- * @param {Object} [options] passed without changes to [the MongoDB driver's `Db#watch()` function](https://mongodb.github.io/node-mongodb-native/3.4/api/Db.html#watch)
+ * @param {Object} [options] passed without changes to [the MongoDB driver's `Db#watch()` function](https://mongodb.github.io/node-mongodb-native/4.9/classes/Db.html#watch)
* @return {ChangeStream} mongoose-specific change stream wrapper, inherits from EventEmitter
*/
Connection.prototype.watch = function(pipeline, options) {
- const disconnectedError = new MongooseError('Connection ' + this.id +
- ' was disconnected when calling `watch()`');
+ const disconnectedError = new DisconnectedError(this.id, 'watch');
const changeStreamThunk = cb => {
immediate(() => {
@@ -1256,8 +1375,9 @@ Connection.prototype.watch = function(pipeline, options) {
* successfully connects to MongoDB, or rejects if this connection failed
* to connect.
*
- * ####Example:
- * const conn = await mongoose.createConnection('mongodb://localhost:27017/test').
+ * #### Example:
+ *
+ * const conn = await mongoose.createConnection('mongodb://127.0.0.1:27017/test').
* asPromise();
* conn.readyState; // 1, means Mongoose is connected
*
@@ -1272,7 +1392,7 @@ Connection.prototype.asPromise = function asPromise() {
/**
* Returns an array of model names created on this connection.
* @api public
- * @return {Array}
+ * @return {String[]}
*/
Connection.prototype.modelNames = function() {
@@ -1280,9 +1400,10 @@ Connection.prototype.modelNames = function() {
};
/**
- * @brief Returns if the connection requires authentication after it is opened. Generally if a
+ * Returns if the connection requires authentication after it is opened. Generally if a
* username and password are both provided than authentication is needed, but in some cases a
* password is not required.
+ *
* @api private
* @return {Boolean} true if the connection should be authenticated after it is opened, otherwise false.
*/
@@ -1292,8 +1413,9 @@ Connection.prototype.shouldAuthenticate = function() {
};
/**
- * @brief Returns a boolean value that specifies if the current authentication mechanism needs a
+ * Returns a boolean value that specifies if the current authentication mechanism needs a
* password to authenticate according to the auth objects passed into the openUri methods.
+ *
* @api private
* @return {Boolean} true if the authentication mechanism specified in the options object requires
* a password, otherwise false.
@@ -1306,10 +1428,11 @@ Connection.prototype.authMechanismDoesNotRequirePassword = function() {
};
/**
- * @brief Returns a boolean value that specifies if the provided objects object provides enough
+ * Returns a boolean value that specifies if the provided objects object provides enough
* data to authenticate with. Generally this is true if the username and password are both specified
* but in some authentication methods, a password is not required for authentication so only a username
* is required.
+ *
* @param {Object} [options] the options object passed into the openUri methods.
* @api private
* @return {Boolean} true if the provided options object provides enough data to authenticate with,
@@ -1322,11 +1445,13 @@ Connection.prototype.optionsProvideAuthenticationData = function(options) {
};
/**
- * Returns the [MongoDB driver `MongoClient`](http://mongodb.github.io/node-mongodb-native/3.5/api/MongoClient.html) instance
+ * Returns the [MongoDB driver `MongoClient`](https://mongodb.github.io/node-mongodb-native/4.9/classes/MongoClient.html) instance
* that this connection uses to talk to MongoDB.
*
- * ####Example:
- * const conn = await mongoose.createConnection('mongodb://localhost:27017/test');
+ * #### Example:
+ *
+ * const conn = await mongoose.createConnection('mongodb://127.0.0.1:27017/test').
+ * asPromise();
*
* conn.getClient(); // MongoClient { ... }
*
@@ -1339,12 +1464,13 @@ Connection.prototype.getClient = function getClient() {
};
/**
- * Set the [MongoDB driver `MongoClient`](http://mongodb.github.io/node-mongodb-native/3.5/api/MongoClient.html) instance
+ * Set the [MongoDB driver `MongoClient`](https://mongodb.github.io/node-mongodb-native/4.9/classes/MongoClient.html) instance
* that this connection uses to talk to MongoDB. This is useful if you already have a MongoClient instance, and want to
* reuse it.
*
- * ####Example:
- * const client = await mongodb.MongoClient.connect('mongodb://localhost:27017/test');
+ * #### Example:
+ *
+ * const client = await mongodb.MongoClient.connect('mongodb://127.0.0.1:27017/test');
*
* const conn = mongoose.createConnection().setClient(client);
*
@@ -1352,6 +1478,7 @@ Connection.prototype.getClient = function getClient() {
* conn.readyState; // 1, means 'CONNECTED'
*
* @api public
+ * @param {MongClient} client The Client to set to be used.
* @return {Connection} this
*/
@@ -1359,7 +1486,7 @@ Connection.prototype.setClient = function setClient(client) {
if (!(client instanceof mongodb.MongoClient)) {
throw new MongooseError('Must call `setClient()` with an instance of MongoClient');
}
- if (this.client != null || this.readyState !== STATES.disconnected) {
+ if (this.readyState !== STATES.disconnected) {
throw new MongooseError('Cannot call `setClient()` on a connection that is already connected.');
}
if (client.topology == null) {
@@ -1367,16 +1494,68 @@ Connection.prototype.setClient = function setClient(client) {
}
this._connectionString = client.s.url;
- _setClient(this, client, { useUnifiedTopology: client.s.options.useUnifiedTopology }, client.s.options.dbName);
+ _setClient(this, client, {}, client.s.options.dbName);
+
+ for (const model of Object.values(this.models)) {
+ // Errors handled internally, so safe to ignore error
+ model.init(function $modelInitNoop() {});
+ }
return this;
};
+/**
+ * Syncs all the indexes for the models registered with this connection.
+ *
+ * @param {Object} [options]
+ * @param {Boolean} [options.continueOnError] `false` by default. If set to `true`, mongoose will not throw an error if one model syncing failed, and will return an object where the keys are the names of the models, and the values are the results/errors for each model.
+ * @return {Promise<Object>} Returns a Promise, when the Promise resolves the value is a list of the dropped indexes.
+ */
+Connection.prototype.syncIndexes = async function syncIndexes(options = {}) {
+ const result = {};
+ const errorsMap = { };
+
+ const { continueOnError } = options;
+ delete options.continueOnError;
+
+ for (const model of Object.values(this.models)) {
+ try {
+ result[model.modelName] = await model.syncIndexes(options);
+ } catch (err) {
+ if (!continueOnError) {
+ errorsMap[model.modelName] = err;
+ break;
+ } else {
+ result[model.modelName] = err;
+ }
+ }
+ }
+
+ if (!continueOnError && Object.keys(errorsMap).length) {
+ const message = Object.entries(errorsMap).map(([modelName, err]) => `${modelName}: ${err.message}`).join(', ');
+ const syncIndexesError = new SyncIndexesError(message, errorsMap);
+ throw syncIndexesError;
+ }
+
+ return result;
+};
+
/**
* Switches to a different database using the same connection pool.
*
* Returns a new connection object, with the new db.
*
+ * #### Example:
+ *
+ * // Connect to `initialdb` first
+ * const conn = await mongoose.createConnection('mongodb://127.0.0.1:27017/initialdb').asPromise();
+ *
+ * // Creates an un-cached connection to `mydb`
+ * const db = conn.useDb('mydb');
+ * // Creates a cached connection to `mydb2`. All calls to `conn.useDb('mydb2', { useCache: true })` will return the same
+ * // connection instance as opposed to creating a new connection instance
+ * const db2 = conn.useDb('mydb2', { useCache: true });
+ *
* @method useDb
* @memberOf Connection
* @param {String} name The database name
diff --git a/lib/cursor/AggregationCursor.js b/lib/cursor/AggregationCursor.js
index 932978699a0..05b68b62ab1 100644
--- a/lib/cursor/AggregationCursor.js
+++ b/lib/cursor/AggregationCursor.js
@@ -23,11 +23,10 @@ const util = require('util');
* but **not** the model's post aggregate hooks.
*
* Unless you're an advanced user, do **not** instantiate this class directly.
- * Use [`Aggregate#cursor()`](/docs/api.html#aggregate_Aggregate-cursor) instead.
+ * Use [`Aggregate#cursor()`](/docs/api/aggregate.html#aggregate_Aggregate-cursor) instead.
*
* @param {Aggregate} agg
- * @param {Object} options
- * @inherits Readable
+ * @inherits Readable https://nodejs.org/api/stream.html#class-streamreadable
* @event `cursor`: Emitted when the cursor is created
* @event `error`: Emitted when an error occurred
* @event `data`: Emitted when the stream is flowing and the next doc is ready
@@ -72,8 +71,12 @@ function _init(model, c, agg) {
}
}
-/*!
+/**
* Necessary to satisfy the Readable API
+ * @method _read
+ * @memberOf AggregationCursor
+ * @instance
+ * @api private
*/
AggregationCursor.prototype._read = function() {
@@ -97,7 +100,7 @@ AggregationCursor.prototype._read = function() {
if (Symbol.asyncIterator != null) {
const msg = 'Mongoose does not support using async iterators with an ' +
- 'existing aggregation cursor. See http://bit.ly/mongoose-async-iterate-aggregation';
+ 'existing aggregation cursor. See https://bit.ly/mongoose-async-iterate-aggregation';
AggregationCursor.prototype[Symbol.asyncIterator] = function() {
throw new MongooseError(msg);
@@ -108,7 +111,7 @@ if (Symbol.asyncIterator != null) {
* Registers a transform function which subsequently maps documents retrieved
* via the streams interface or `.next()`
*
- * ####Example
+ * #### Example:
*
* // Map documents returned by `data` events
* Thing.
@@ -133,17 +136,27 @@ if (Symbol.asyncIterator != null) {
*
* @param {Function} fn
* @return {AggregationCursor}
+ * @memberOf AggregationCursor
* @api public
* @method map
*/
-AggregationCursor.prototype.map = function(fn) {
- this._transforms.push(fn);
- return this;
-};
+Object.defineProperty(AggregationCursor.prototype, 'map', {
+ value: function(fn) {
+ this._transforms.push(fn);
+ return this;
+ },
+ enumerable: true,
+ configurable: true,
+ writable: true
+});
-/*!
+/**
* Marks this cursor as errored
+ * @method _markError
+ * @instance
+ * @memberOf AggregationCursor
+ * @api private
*/
AggregationCursor.prototype._markError = function(error) {
@@ -160,7 +173,7 @@ AggregationCursor.prototype._markError = function(error) {
* @api public
* @method close
* @emits close
- * @see MongoDB driver cursor#close http://mongodb.github.io/node-mongodb-native/2.1/api/Cursor.html#close
+ * @see AggregationCursor.close https://mongodb.github.io/node-mongodb-native/4.9/classes/AggregationCursor.html#close
*/
AggregationCursor.prototype.close = function(callback) {
@@ -218,11 +231,11 @@ AggregationCursor.prototype.eachAsync = function(fn, opts, callback) {
};
/**
- * Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js
+ * Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js)
* You do not need to call this function explicitly, the JavaScript runtime
* will call it for you.
*
- * ####Example
+ * #### Example:
*
* // Async iterator without explicitly calling `cursor()`. Mongoose still
* // creates an AggregationCursor instance internally.
@@ -244,7 +257,7 @@ AggregationCursor.prototype.eachAsync = function(fn, opts, callback) {
* `Symbol.asyncIterator` is undefined, that means your Node.js version does not
* support async iterators.
*
- * @method Symbol.asyncIterator
+ * @method [Symbol.asyncIterator]
* @memberOf AggregationCursor
* @instance
* @api public
@@ -288,7 +301,7 @@ function _transformForAsyncIterator(doc) {
}
/**
- * Adds a [cursor flag](http://mongodb.github.io/node-mongodb-native/2.2/api/Cursor.html#addCursorFlag).
+ * Adds a [cursor flag](https://mongodb.github.io/node-mongodb-native/4.9/classes/AggregationCursor.html#addCursorFlag).
* Useful for setting the `noCursorTimeout` and `tailable` flags.
*
* @param {String} flag
@@ -319,9 +332,12 @@ function _waitForCursor(ctx, cb) {
});
}
-/*!
+/**
* Get the next doc from the underlying cursor and mongooseify it
* (populate, etc.)
+ * @param {Any} ctx
+ * @param {Function} cb
+ * @api private
*/
function _next(ctx, cb) {
diff --git a/lib/cursor/ChangeStream.js b/lib/cursor/ChangeStream.js
index b3445b06bbb..6cee9f8b371 100644
--- a/lib/cursor/ChangeStream.js
+++ b/lib/cursor/ChangeStream.js
@@ -16,9 +16,17 @@ class ChangeStream extends EventEmitter {
this.driverChangeStream = null;
this.closed = false;
+ this.bindedEvents = false;
this.pipeline = pipeline;
this.options = options;
+ if (options && options.hydrate && !options.model) {
+ throw new Error(
+ 'Cannot create change stream with `hydrate: true` ' +
+ 'unless calling `Model.watch()`'
+ );
+ }
+
// This wrapper is necessary because of buffering.
changeStreamThunk((err, driverChangeStream) => {
if (err != null) {
@@ -27,21 +35,106 @@ class ChangeStream extends EventEmitter {
}
this.driverChangeStream = driverChangeStream;
- this._bindEvents();
this.emit('ready');
});
}
_bindEvents() {
+ if (this.bindedEvents) {
+ return;
+ }
+
+ this.bindedEvents = true;
+
+ if (this.driverChangeStream == null) {
+ this.once('ready', () => {
+ this.driverChangeStream.on('close', () => {
+ this.closed = true;
+ });
+
+ ['close', 'change', 'end', 'error'].forEach(ev => {
+ this.driverChangeStream.on(ev, data => {
+ // Sometimes Node driver still polls after close, so
+ // avoid any uncaught exceptions due to closed change streams
+ // See tests for gh-7022
+ if (ev === 'error' && this.closed) {
+ return;
+ }
+ if (data != null && data.fullDocument != null && this.options && this.options.hydrate) {
+ data.fullDocument = this.options.model.hydrate(data.fullDocument);
+ }
+ this.emit(ev, data);
+ });
+ });
+ });
+
+ return;
+ }
+
this.driverChangeStream.on('close', () => {
this.closed = true;
});
- ['close', 'change', 'end', 'error'].forEach(ev => {
- this.driverChangeStream.on(ev, data => this.emit(ev, data));
+ ['close', 'change', 'end', 'error', 'resumeTokenChanged'].forEach(ev => {
+ this.driverChangeStream.on(ev, data => {
+ // Sometimes Node driver still polls after close, so
+ // avoid any uncaught exceptions due to closed change streams
+ // See tests for gh-7022
+ if (ev === 'error' && this.closed) {
+ return;
+ }
+ if (data != null && data.fullDocument != null && this.options && this.options.hydrate) {
+ data.fullDocument = this.options.model.hydrate(data.fullDocument);
+ }
+ this.emit(ev, data);
+ });
});
}
+ hasNext(cb) {
+ return this.driverChangeStream.hasNext(cb);
+ }
+
+ next(cb) {
+ if (this.options && this.options.hydrate) {
+ if (cb != null) {
+ const originalCb = cb;
+ cb = (err, data) => {
+ if (err != null) {
+ return originalCb(err);
+ }
+ if (data.fullDocument != null) {
+ data.fullDocument = this.options.model.hydrate(data.fullDocument);
+ }
+ return originalCb(null, data);
+ };
+ }
+
+ let maybePromise = this.driverChangeStream.next(cb);
+ if (maybePromise && typeof maybePromise.then === 'function') {
+ maybePromise = maybePromise.then(data => {
+ if (data.fullDocument != null) {
+ data.fullDocument = this.options.model.hydrate(data.fullDocument);
+ }
+ return data;
+ });
+ }
+ return maybePromise;
+ }
+
+ return this.driverChangeStream.next(cb);
+ }
+
+ on(event, handler) {
+ this._bindEvents();
+ return super.on(event, handler);
+ }
+
+ once(event, handler) {
+ this._bindEvents();
+ return super.once(event, handler);
+ }
+
_queue(cb) {
this.once('ready', () => cb());
}
diff --git a/lib/cursor/QueryCursor.js b/lib/cursor/QueryCursor.js
index cdeaa54454e..96fbc80dd67 100644
--- a/lib/cursor/QueryCursor.js
+++ b/lib/cursor/QueryCursor.js
@@ -21,11 +21,11 @@ const util = require('util');
* from MongoDB, and the model's post `find` hooks after loading each document.
*
* Unless you're an advanced user, do **not** instantiate this class directly.
- * Use [`Query#cursor()`](/docs/api.html#query_Query-cursor) instead.
+ * Use [`Query#cursor()`](/docs/api/query.html#query_Query-cursor) instead.
*
* @param {Query} query
* @param {Object} options query options passed to `.find()`
- * @inherits Readable
+ * @inherits Readable https://nodejs.org/api/stream.html#class-streamreadable
* @event `cursor`: Emitted when the cursor is created
* @event `error`: Emitted when an error occurred
* @event `data`: Emitted when the stream is flowing and the next doc is ready
@@ -66,6 +66,7 @@ function QueryCursor(query, options) {
// Max out the number of documents we'll populate in parallel at 5000.
this.options._populateBatchSize = Math.min(this.options.batchSize, 5000);
}
+ Object.assign(this.options, query._optionsForExec());
model.collection.find(query._conditions, this.options, (err, cursor) => {
if (err != null) {
_this._markError(err);
@@ -85,8 +86,12 @@ function QueryCursor(query, options) {
util.inherits(QueryCursor, Readable);
-/*!
+/**
* Necessary to satisfy the Readable API
+ * @method _read
+ * @memberOf QueryCursor
+ * @instance
+ * @api private
*/
QueryCursor.prototype._read = function() {
@@ -112,7 +117,7 @@ QueryCursor.prototype._read = function() {
* Registers a transform function which subsequently maps documents retrieved
* via the streams interface or `.next()`
*
- * ####Example
+ * #### Example:
*
* // Map documents returned by `data` events
* Thing.
@@ -137,17 +142,27 @@ QueryCursor.prototype._read = function() {
*
* @param {Function} fn
* @return {QueryCursor}
+ * @memberOf QueryCursor
* @api public
* @method map
*/
-QueryCursor.prototype.map = function(fn) {
- this._transforms.push(fn);
- return this;
-};
+Object.defineProperty(QueryCursor.prototype, 'map', {
+ value: function(fn) {
+ this._transforms.push(fn);
+ return this;
+ },
+ enumerable: true,
+ configurable: true,
+ writable: true
+});
-/*!
+/**
* Marks this cursor as errored
+ * @method _markError
+ * @memberOf QueryCursor
+ * @instance
+ * @api private
*/
QueryCursor.prototype._markError = function(error) {
@@ -164,7 +179,7 @@ QueryCursor.prototype._markError = function(error) {
* @api public
* @method close
* @emits close
- * @see MongoDB driver cursor#close http://mongodb.github.io/node-mongodb-native/2.1/api/Cursor.html#close
+ * @see AggregationCursor.close https://mongodb.github.io/node-mongodb-native/4.9/classes/AggregationCursor.html#close
*/
QueryCursor.prototype.close = function(callback) {
@@ -180,6 +195,24 @@ QueryCursor.prototype.close = function(callback) {
}, this.model.events);
};
+/**
+ * Rewind this cursor to its uninitialized state. Any options that are present on the cursor will
+ * remain in effect. Iterating this cursor will cause new queries to be sent to the server, even
+ * if the resultant data has already been retrieved by this cursor.
+ *
+ * @return {AggregationCursor} this
+ * @api public
+ * @method rewind
+ */
+
+QueryCursor.prototype.rewind = function() {
+ const _this = this;
+ _waitForCursor(this, function() {
+ _this.cursor.rewind();
+ });
+ return this;
+};
+
/**
* Get the next document from this cursor. Will return `null` when there are
* no documents left.
@@ -206,7 +239,7 @@ QueryCursor.prototype.next = function(callback) {
* will wait for the promise to resolve before iterating on to the next one.
* Returns a promise that resolves when done.
*
- * ####Example
+ * #### Example:
*
* // Iterate over documents asynchronously
* Thing.
@@ -220,6 +253,8 @@ QueryCursor.prototype.next = function(callback) {
* @param {Function} fn
* @param {Object} [options]
* @param {Number} [options.parallel] the number of promises to execute in parallel. Defaults to 1.
+ * @param {Number} [options.batchSize] if set, will call `fn()` with arrays of documents with length at most `batchSize`
+ * @param {Boolean} [options.continueOnError=false] if true, `eachAsync()` iterates through all docs even if `fn` throws an error. If false, `eachAsync()` throws an error immediately if the given function `fn()` throws an error.
* @param {Function} [callback] executed when all docs have been processed
* @return {Promise}
* @api public
@@ -247,7 +282,7 @@ QueryCursor.prototype.eachAsync = function(fn, opts, callback) {
QueryCursor.prototype.options;
/**
- * Adds a [cursor flag](http://mongodb.github.io/node-mongodb-native/2.2/api/Cursor.html#addCursorFlag).
+ * Adds a [cursor flag](https://mongodb.github.io/node-mongodb-native/4.9/classes/FindCursor.html#addCursorFlag).
* Useful for setting the `noCursorTimeout` and `tailable` flags.
*
* @param {String} flag
@@ -293,7 +328,7 @@ QueryCursor.prototype._transformForAsyncIterator = function() {
* You do not need to call this function explicitly, the JavaScript runtime
* will call it for you.
*
- * ####Example
+ * #### Example:
*
* // Works without using `cursor()`
* for await (const doc of Model.find([{ $sort: { name: 1 } }])) {
@@ -312,8 +347,8 @@ QueryCursor.prototype._transformForAsyncIterator = function() {
* `Symbol.asyncIterator` is undefined, that means your Node.js version does not
* support async iterators.
*
- * @method Symbol.asyncIterator
- * @memberOf Query
+ * @method [Symbol.asyncIterator]
+ * @memberOf QueryCursor
* @instance
* @api public
*/
@@ -332,9 +367,12 @@ function _transformForAsyncIterator(doc) {
return doc == null ? { done: true } : { value: doc, done: false };
}
-/*!
+/**
* Get the next doc from the underlying cursor and mongooseify it
* (populate, etc.)
+ * @param {Any} ctx
+ * @param {Function} cb
+ * @api private
*/
function _next(ctx, cb) {
@@ -467,7 +505,8 @@ function _nextDoc(ctx, doc, pop, callback) {
});
}
- _create(ctx, doc, pop, (err, doc) => {
+ const { model, _fields, _userProvidedFields, options } = ctx.query;
+ helpers.createModelAndInit(model, doc, _fields, _userProvidedFields, options, pop, (err, doc) => {
if (err != null) {
return callback(err);
}
@@ -496,22 +535,4 @@ function _waitForCursor(ctx, cb) {
});
}
-/*!
- * Convert a raw doc into a full mongoose doc.
- */
-
-function _create(ctx, doc, populatedIds, cb) {
- const instance = helpers.createModel(ctx.query.model, doc, ctx.query._fields);
- const opts = populatedIds ?
- { populated: populatedIds } :
- undefined;
-
- instance.$init(doc, opts, function(err) {
- if (err) {
- return cb(err);
- }
- cb(null, instance);
- });
-}
-
module.exports = QueryCursor;
diff --git a/lib/document.js b/lib/document.js
index 31edd149e9a..e82d2e73b71 100644
--- a/lib/document.js
+++ b/lib/document.js
@@ -15,13 +15,13 @@ const Schema = require('./schema');
const StrictModeError = require('./error/strict');
const ValidationError = require('./error/validation');
const ValidatorError = require('./error/validator');
-const VirtualType = require('./virtualtype');
+const $__hasIncludedChildren = require('./helpers/projection/hasIncludedChildren');
const promiseOrCallback = require('./helpers/promiseOrCallback');
+const applyDefaults = require('./helpers/document/applyDefaults');
const cleanModifiedSubpaths = require('./helpers/document/cleanModifiedSubpaths');
const compile = require('./helpers/document/compile').compile;
const defineKey = require('./helpers/document/compile').defineKey;
const flatten = require('./helpers/common').flatten;
-const flattenObjectWithDottedPaths = require('./helpers/path/flattenObjectWithDottedPaths');
const get = require('./helpers/get');
const getEmbeddedDiscriminatorPath = require('./helpers/document/getEmbeddedDiscriminatorPath');
const getKeysInSchemaOrder = require('./helpers/schema/getKeysInSchemaOrder');
@@ -52,6 +52,7 @@ const populateModelSymbol = require('./helpers/symbols').populateModelSymbol;
const scopeSymbol = require('./helpers/symbols').scopeSymbol;
const schemaMixedSymbol = require('./schema/symbols').schemaMixedSymbol;
const parentPaths = require('./helpers/path/parentPaths');
+
let DocumentArray;
let MongooseArray;
let Embedded;
@@ -60,13 +61,13 @@ const specialProperties = utils.specialProperties;
/**
* The core Mongoose document constructor. You should not call this directly,
- * the Mongoose [Model constructor](./api.html#Model) calls this for you.
+ * the Mongoose [Model constructor](./api/model.html#Model) calls this for you.
*
* @param {Object} obj the values to set
* @param {Object} [fields] optional object containing the fields which were selected in the query returning this document and any populated paths data
* @param {Object} [options] various configuration options for the document
* @param {Boolean} [options.defaults=true] if `false`, skip applying default values to this document.
- * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
+ * @inherits NodeJS EventEmitter https://nodejs.org/api/events.html#class-eventemitter
* @event `init`: Emitted on a document after it has been retrieved from the db and fully hydrated by Mongoose.
* @event `save`: Emitted when the document is successfully saved
* @api private
@@ -90,14 +91,21 @@ function Document(obj, fields, skipId, options) {
options = arguments[4] || {};
}
- this.$__ = new InternalCache;
- this.$__.emitter = new EventEmitter();
- this.$isNew = 'isNew' in options ? options.isNew : true;
+ this.$__ = new InternalCache();
+
+ // Avoid setting `isNew` to `true`, because it is `true` by default
+ if (options.isNew != null && options.isNew !== true) {
+ this.$isNew = options.isNew;
+ }
- if ('priorDoc' in options) {
+ if (options.priorDoc != null) {
this.$__.priorDoc = options.priorDoc;
}
+ if (skipId) {
+ this.$__.skipId = skipId;
+ }
+
if (obj != null && typeof obj !== 'object') {
throw new ObjectParameterError(obj, 'obj', 'Document');
}
@@ -111,13 +119,12 @@ function Document(obj, fields, skipId, options) {
const schema = this.$__schema;
if (typeof fields === 'boolean' || fields === 'throw') {
- this.$__.strictMode = fields;
+ if (fields !== true) {
+ this.$__.strictMode = fields;
+ }
fields = undefined;
- } else {
+ } else if (schema.options.strict !== true) {
this.$__.strictMode = schema.options.strict;
- if (fields !== undefined) {
- this.$__.selected = fields;
- }
}
const requiredPaths = schema.requiredPaths(true);
@@ -125,19 +132,19 @@ function Document(obj, fields, skipId, options) {
this.$__.activePaths.require(path);
}
- this.$__.emitter.setMaxListeners(0);
-
let exclude = null;
// determine if this doc is a result of a query with
// excluded fields
- if (utils.isPOJO(fields)) {
+ if (utils.isPOJO(fields) && Object.keys(fields).length > 0) {
exclude = isExclusive(fields);
+ this.$__.selected = fields;
+ this.$__.exclude = exclude;
}
const hasIncludedChildren = exclude === false && fields ?
$__hasIncludedChildren(fields) :
- {};
+ null;
if (this._doc == null) {
this.$__buildDoc(obj, fields, skipId, exclude, hasIncludedChildren, false);
@@ -145,17 +152,15 @@ function Document(obj, fields, skipId, options) {
// By default, defaults get applied **before** setting initial values
// Re: gh-6155
if (defaults) {
- $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, true, {
- isNew: this.$isNew
- });
+ applyDefaults(this, fields, exclude, hasIncludedChildren, true, null);
}
}
if (obj) {
// Skip set hooks
if (this.$__original_set) {
- this.$__original_set(obj, undefined, true);
+ this.$__original_set(obj, undefined, true, options);
} else {
- this.$set(obj, undefined, true);
+ this.$set(obj, undefined, true, options);
}
if (obj instanceof Document) {
@@ -167,19 +172,13 @@ function Document(obj, fields, skipId, options) {
// see the full doc rather than an empty one, unless they opt out.
// Re: gh-3781, gh-6155
if (options.willInit && defaults) {
- EventEmitter.prototype.once.call(this, 'init', () => {
- $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, false, options.skipDefaults, {
- isNew: this.$isNew
- });
- });
+ if (options.skipDefaults) {
+ this.$__.skipDefaults = options.skipDefaults;
+ }
} else if (defaults) {
- $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, false, options.skipDefaults, {
- isNew: this.$isNew
- });
+ applyDefaults(this, fields, exclude, hasIncludedChildren, false, options.skipDefaults);
}
- this.$__._id = this._id;
-
if (!this.$__.strictMode && obj) {
const _this = this;
const keys = Object.keys(this._doc);
@@ -196,6 +195,59 @@ function Document(obj, fields, skipId, options) {
applyQueue(this);
}
+Document.prototype.$isMongooseDocumentPrototype = true;
+
+/**
+ * Boolean flag specifying if the document is new. If you create a document
+ * using `new`, this document will be considered "new". `$isNew` is how
+ * Mongoose determines whether `save()` should use `insertOne()` to create
+ * a new document or `updateOne()` to update an existing document.
+ *
+ * #### Example:
+ *
+ * const user = new User({ name: 'John Smith' });
+ * user.$isNew; // true
+ *
+ * await user.save(); // Sends an `insertOne` to MongoDB
+ *
+ * On the other hand, if you load an existing document from the database
+ * using `findOne()` or another [query operation](/docs/queries.html),
+ * `$isNew` will be false.
+ *
+ * #### Example:
+ *
+ * const user = await User.findOne({ name: 'John Smith' });
+ * user.$isNew; // false
+ *
+ * Mongoose sets `$isNew` to `false` immediately after `save()` succeeds.
+ * That means Mongoose sets `$isNew` to false **before** `post('save')` hooks run.
+ * In `post('save')` hooks, `$isNew` will be `false` if `save()` succeeded.
+ *
+ * #### Example:
+ *
+ * userSchema.post('save', function() {
+ * this.$isNew; // false
+ * });
+ * await User.create({ name: 'John Smith' });
+ *
+ * For subdocuments, `$isNew` is true if either the parent has `$isNew` set,
+ * or if you create a new subdocument.
+ *
+ * #### Example:
+ *
+ * // Assume `Group` has a document array `users`
+ * const group = await Group.findOne();
+ * group.users[0].$isNew; // false
+ *
+ * group.users.push({ name: 'John Smith' });
+ * group.users[1].$isNew; // true
+ *
+ * @api public
+ * @property $isNew
+ * @memberOf Document
+ * @instance
+ */
+
Object.defineProperty(Document.prototype, 'isNew', {
get: function() {
return this.$isNew;
@@ -205,6 +257,15 @@ Object.defineProperty(Document.prototype, 'isNew', {
}
});
+/**
+ * Hash containing current validation errors.
+ *
+ * @api public
+ * @property errors
+ * @memberOf Document
+ * @instance
+ */
+
Object.defineProperty(Document.prototype, 'errors', {
get: function() {
return this.$errors;
@@ -213,6 +274,13 @@ Object.defineProperty(Document.prototype, 'errors', {
this.$errors = value;
}
});
+
+/*!
+ * ignore
+ */
+
+Document.prototype.$isNew = true;
+
/*!
* Document exposes the NodeJS event emitter API, so you can use
* `on`, `once`, etc.
@@ -222,6 +290,15 @@ utils.each(
'removeAllListeners', 'addListener'],
function(emitterFn) {
Document.prototype[emitterFn] = function() {
+ // Delay creating emitter until necessary because emitters take up a lot of memory,
+ // especially for subdocuments.
+ if (!this.$__.emitter) {
+ if (emitterFn === 'emit') {
+ return;
+ }
+ this.$__.emitter = new EventEmitter();
+ this.$__.emitter.setMaxListeners(0);
+ }
return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments);
};
Document.prototype[`$${emitterFn}`] = Document.prototype[emitterFn];
@@ -260,7 +337,7 @@ Document.prototype.schema;
* is handy for passing data to middleware without conflicting with Mongoose
* internals.
*
- * ####Example:
+ * #### Example:
*
* schema.pre('save', function() {
* // Mongoose will set `isNew` to `false` if `save()` succeeds
@@ -292,24 +369,13 @@ Object.defineProperty(Document.prototype, '$locals', {
}
});
-
-/**
- * Boolean flag specifying if the document is new.
- *
- * @api public
- * @property $isNew
- * @memberOf Document
- * @instance
- */
-
-Document.prototype.$isNew;
-
/**
- * Boolean flag specifying if the document is new.
+ * Legacy alias for `$isNew`.
*
* @api public
* @property isNew
* @memberOf Document
+ * @see $isNew #document_Document-$isNew
* @instance
*/
@@ -318,7 +384,7 @@ Document.prototype.isNew;
/**
* Set this property to add additional query filters when Mongoose saves this document and `isNew` is false.
*
- * ####Example:
+ * #### Example:
*
* // Make sure `save()` never updates a soft deleted document.
* schema.pre('save', function() {
@@ -340,7 +406,7 @@ Object.defineProperty(Document.prototype, '$where', {
/**
* The string version of this documents _id.
*
- * ####Note:
+ * #### Note:
*
* This getter exists on all documents by default. The getter can be disabled by setting the `id` [option](/docs/guide.html#id) of its `Schema` to false at construction time.
*
@@ -366,22 +432,11 @@ Document.prototype.id;
Document.prototype.$errors;
-/**
- * Hash containing current validation errors.
- *
- * @api public
- * @property errors
- * @memberOf Document
- * @instance
- */
-
-Document.prototype.errors;
-
/**
* A string containing the current operation that Mongoose is executing
* on this document. May be `null`, `'save'`, `'validate'`, or `'remove'`.
*
- * ####Example:
+ * #### Example:
*
* const doc = new Model({ name: 'test' });
* doc.$op; // null
@@ -407,142 +462,6 @@ Object.defineProperty(Document.prototype, '$op', {
}
});
-/*!
- * ignore
- */
-
-function $__hasIncludedChildren(fields) {
- const hasIncludedChildren = {};
- const keys = Object.keys(fields);
-
- for (const key of keys) {
- const parts = key.split('.');
- const c = [];
-
- for (const part of parts) {
- c.push(part);
- hasIncludedChildren[c.join('.')] = 1;
- }
- }
-
- return hasIncludedChildren;
-}
-
-/*!
- * ignore
- */
-
-function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren, isBeforeSetters, pathsToSkip) {
- const paths = Object.keys(doc.$__schema.paths);
- const plen = paths.length;
-
- for (let i = 0; i < plen; ++i) {
- let def;
- let curPath = '';
- const p = paths[i];
-
- if (p === '_id' && skipId) {
- continue;
- }
-
- const type = doc.$__schema.paths[p];
- const path = type.splitPath();
- const len = path.length;
- let included = false;
- let doc_ = doc._doc;
- for (let j = 0; j < len; ++j) {
- if (doc_ == null) {
- break;
- }
-
- const piece = path[j];
- curPath += (!curPath.length ? '' : '.') + piece;
-
- if (exclude === true) {
- if (curPath in fields) {
- break;
- }
- } else if (exclude === false && fields && !included) {
- if (curPath in fields) {
- included = true;
- } else if (!hasIncludedChildren[curPath]) {
- break;
- }
- }
-
- if (j === len - 1) {
- if (doc_[piece] !== void 0) {
- break;
- }
-
- if (typeof type.defaultValue === 'function') {
- if (!type.defaultValue.$runBeforeSetters && isBeforeSetters) {
- break;
- }
- if (type.defaultValue.$runBeforeSetters && !isBeforeSetters) {
- break;
- }
- } else if (!isBeforeSetters) {
- // Non-function defaults should always run **before** setters
- continue;
- }
-
- if (pathsToSkip && pathsToSkip[curPath]) {
- break;
- }
-
- if (fields && exclude !== null) {
- if (exclude === true) {
- // apply defaults to all non-excluded fields
- if (p in fields) {
- continue;
- }
-
- try {
- def = type.getDefault(doc, false);
- } catch (err) {
- doc.invalidate(p, err);
- break;
- }
-
- if (typeof def !== 'undefined') {
- doc_[piece] = def;
- doc.$__.activePaths.default(p);
- }
- } else if (included) {
- // selected field
- try {
- def = type.getDefault(doc, false);
- } catch (err) {
- doc.invalidate(p, err);
- break;
- }
-
- if (typeof def !== 'undefined') {
- doc_[piece] = def;
- doc.$__.activePaths.default(p);
- }
- }
- } else {
- try {
- def = type.getDefault(doc, false);
- } catch (err) {
- doc.invalidate(p, err);
- break;
- }
-
- if (typeof def !== 'undefined') {
- doc_[piece] = def;
- doc.$__.activePaths.default(p);
- }
- }
- } else {
- doc_ = doc_[piece];
- }
- }
- }
-}
-
/*!
* ignore
*/
@@ -552,8 +471,6 @@ function $applyDefaultsToNested(val, path, doc) {
return;
}
- flattenObjectWithDottedPaths(val);
-
const paths = Object.keys(doc.$__schema.paths);
const plen = paths.length;
@@ -616,6 +533,8 @@ function $applyDefaultsToNested(val, path, doc) {
* @param {Object} obj
* @param {Object} [fields]
* @param {Boolean} [skipId]
+ * @param {Boolean} [exclude]
+ * @param {Object} [hasIncludedChildren]
* @api private
* @method $__buildDoc
* @memberOf Document
@@ -654,7 +573,11 @@ Document.prototype.$__buildDoc = function(obj, fields, skipId, exclude, hasInclu
for (let i = 0; i < len; ++i) {
const piece = path[i];
- curPath += (!curPath.length ? '' : '.') + piece;
+ if (!curPath.length) {
+ curPath = piece;
+ } else {
+ curPath += '.' + piece;
+ }
// support excluding intermediary levels
if (exclude === true) {
@@ -696,6 +619,8 @@ Document.prototype.toBSON = function() {
* Note that `init` hooks are [synchronous](/docs/middleware.html#synchronous).
*
* @param {Object} doc document returned by mongo
+ * @param {Object} [opts]
+ * @param {Function} [fn]
* @api public
* @memberOf Document
* @instance
@@ -716,17 +641,27 @@ Document.prototype.init = function(doc, opts, fn) {
return this;
};
+/**
+ * Alias for [`.init`](#document_Document-init)
+ *
+ * @api public
+ */
+
Document.prototype.$init = function() {
return this.constructor.prototype.init.apply(this, arguments);
};
-/*!
- * ignore
+/**
+ * Internal "init" function
+ *
+ * @param {Document} doc
+ * @param {Object} [opts]
+ * @returns {Document} this
+ * @api private
*/
Document.prototype.$__init = function(doc, opts) {
this.$isNew = false;
- this.$init = true;
opts = opts || {};
// handle docs with populated paths
@@ -760,29 +695,41 @@ Document.prototype.$__init = function(doc, opts) {
this.$emit('init', this);
this.constructor.emit('init', this);
- this.$__._id = this._id;
+ const hasIncludedChildren = this.$__.exclude === false && this.$__.selected ?
+ $__hasIncludedChildren(this.$__.selected) :
+ null;
+
+ applyDefaults(this, this.$__.selected, this.$__.exclude, hasIncludedChildren, false, this.$__.skipDefaults);
+
return this;
};
-/*!
+/**
* Init helper.
*
* @param {Object} self document instance
* @param {Object} obj raw mongodb doc
* @param {Object} doc object we are initializing
+ * @param {Object} [opts] Optional Options
+ * @param {Boolean} [opts.setters] Call `applySetters` instead of `cast`
+ * @param {String} [prefix] Prefix to add to each path
* @api private
*/
function init(self, obj, doc, opts, prefix) {
prefix = prefix || '';
+ if (obj.$__ != null) {
+ obj = obj._doc;
+ }
const keys = Object.keys(obj);
const len = keys.length;
- let schema;
+ let schemaType;
let path;
let i;
let index = 0;
const strict = self.$__.strictMode;
+ const docSchema = self.$__schema;
while (index < len) {
_init(index++);
@@ -790,42 +737,54 @@ function init(self, obj, doc, opts, prefix) {
function _init(index) {
i = keys[index];
- path = prefix + i;
- schema = self.$__schema.path(path);
+ // avoid prototype pollution
+ if (i === '__proto__' || i === 'constructor') {
+ return;
+ }
+ path = prefix ? prefix + i : i;
+ schemaType = docSchema.path(path);
// Should still work if not a model-level discriminator, but should not be
// necessary. This is *only* to catch the case where we queried using the
// base model and the discriminated model has a projection
- if (self.$__schema.$isRootDiscriminator && !self.$__isSelected(path)) {
+ if (docSchema.$isRootDiscriminator && !self.$__isSelected(path)) {
return;
}
- if (!schema && utils.isPOJO(obj[i])) {
+ const value = obj[i];
+ if (!schemaType && utils.isPOJO(value)) {
// assume nested object
if (!doc[i]) {
doc[i] = {};
+ if (!strict && !(i in docSchema.tree) && !(i in docSchema.methods) && !(i in docSchema.virtuals)) {
+ self[i] = doc[i];
+ }
}
- init(self, obj[i], doc[i], opts, path + '.');
- } else if (!schema) {
- doc[i] = obj[i];
+ init(self, value, doc[i], opts, path + '.');
+ } else if (!schemaType) {
+ doc[i] = value;
if (!strict && !prefix) {
- // Set top-level properties that aren't in the schema if strict is false
- self[i] = obj[i];
+ self[i] = value;
}
} else {
// Retain order when overwriting defaults
- if (doc.hasOwnProperty(i) && obj[i] !== void 0) {
+ if (doc.hasOwnProperty(i) && value !== void 0) {
delete doc[i];
}
- if (obj[i] === null) {
- doc[i] = schema._castNullish(null);
- } else if (obj[i] !== undefined) {
- const intCache = obj[i].$__ || {};
- const wasPopulated = intCache.wasPopulated || null;
+ if (value === null) {
+ doc[i] = schemaType._castNullish(null);
+ } else if (value !== undefined) {
+ const wasPopulated = value.$__ == null ? null : value.$__.wasPopulated;
- if (schema && !wasPopulated) {
+ if (schemaType && !wasPopulated) {
try {
- doc[i] = schema.cast(obj[i], self, true);
+ if (opts && opts.setters) {
+ // Call applySetters with `init = false` because otherwise setters are a noop
+ const overrideInit = false;
+ doc[i] = schemaType.applySetters(value, self, overrideInit);
+ } else {
+ doc[i] = schemaType.cast(value, self, true);
+ }
} catch (e) {
self.invalidate(e.path, new ValidatorError({
path: e.path,
@@ -836,7 +795,7 @@ function init(self, obj, doc, opts, prefix) {
}));
}
} else {
- doc[i] = obj[i];
+ doc[i] = value;
}
}
// mark as hydrated
@@ -850,26 +809,26 @@ function init(self, obj, doc, opts, prefix) {
/**
* Sends an update command with this document `_id` as the query selector.
*
- * ####Example:
+ * #### Example:
*
- * weirdCar.update({$inc: {wheels:1}}, { w: 1 }, callback);
+ * weirdCar.update({ $inc: { wheels:1 } }, { w: 1 }, callback);
*
- * ####Valid options:
+ * #### Valid options:
*
- * - same as in [Model.update](#model_Model.update)
+ * - same as in [Model.update](#model_Model-update)
*
- * @see Model.update #model_Model.update
- * @param {Object} doc
- * @param {Object} options
- * @param {Function} callback
- * @return {Query}
+ * @see Model.update #model_Model-update
+ * @param {...Object} ops
+ * @param {Object} [options]
+ * @param {Function} [callback]
+ * @return {Query} this
* @api public
* @memberOf Document
* @instance
*/
Document.prototype.update = function update() {
- const args = utils.args(arguments);
+ const args = [...arguments];
args.unshift({ _id: this._id });
const query = this.constructor.update.apply(this.constructor, args);
@@ -885,21 +844,21 @@ Document.prototype.update = function update() {
/**
* Sends an updateOne command with this document `_id` as the query selector.
*
- * ####Example:
+ * #### Example:
*
* weirdCar.updateOne({$inc: {wheels:1}}, { w: 1 }, callback);
*
- * ####Valid options:
+ * #### Valid options:
*
- * - same as in [Model.updateOne](#model_Model.updateOne)
+ * - same as in [Model.updateOne](#model_Model-updateOne)
*
- * @see Model.updateOne #model_Model.updateOne
+ * @see Model.updateOne #model_Model-updateOne
* @param {Object} doc
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
- * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and the [Mongoose lean tutorial](/docs/tutorials/lean.html).
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
+ * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api/query.html#query_Query-lean) and the [Mongoose lean tutorial](/docs/tutorials/lean.html).
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
- * @param {Function} callback
+ * @param {Function} [callback]
* @return {Query}
* @api public
* @memberOf Document
@@ -908,11 +867,12 @@ Document.prototype.update = function update() {
Document.prototype.updateOne = function updateOne(doc, options, callback) {
const query = this.constructor.updateOne({ _id: this._id }, doc, options);
- query.pre(cb => {
- this.constructor._middleware.execPre('updateOne', this, [this], cb);
+ const self = this;
+ query.pre(function queryPreUpdateOne(cb) {
+ self.constructor._middleware.execPre('updateOne', self, [self], cb);
});
- query.post(cb => {
- this.constructor._middleware.execPost('updateOne', this, [this], {}, cb);
+ query.post(function queryPostUpdateOne(cb) {
+ self.constructor._middleware.execPost('updateOne', self, [self], {}, cb);
});
if (this.$session() != null) {
@@ -931,14 +891,14 @@ Document.prototype.updateOne = function updateOne(doc, options, callback) {
/**
* Sends a replaceOne command with this document `_id` as the query selector.
*
- * ####Valid options:
+ * #### Valid options:
*
- * - same as in [Model.replaceOne](https://mongoosejs.com/docs/api/model.html#model_Model.replaceOne)
+ * - same as in [Model.replaceOne](https://mongoosejs.com/docs/api/model.html#model_Model-replaceOne)
*
- * @see Model.replaceOne #model_Model.replaceOne
+ * @see Model.replaceOne #model_Model-replaceOne
* @param {Object} doc
- * @param {Object} options
- * @param {Function} callback
+ * @param {Object} [options]
+ * @param {Function} [callback]
* @return {Query}
* @api public
* @memberOf Document
@@ -946,7 +906,7 @@ Document.prototype.updateOne = function updateOne(doc, options, callback) {
*/
Document.prototype.replaceOne = function replaceOne() {
- const args = utils.args(arguments);
+ const args = [...arguments];
args.unshift({ _id: this._id });
return this.constructor.replaceOne.apply(this.constructor, args);
};
@@ -956,7 +916,7 @@ Document.prototype.replaceOne = function replaceOne() {
* automatically set `session` if you `save()` a doc that you got from a
* query with an associated session.
*
- * ####Example:
+ * #### Example:
*
* const session = MyModel.startSession();
* const doc = await MyModel.findOne().session(session);
@@ -988,9 +948,13 @@ Document.prototype.$session = function $session(session) {
'called `endSession()` on the session you are passing to `$session()`.');
}
+ if (session == null && this.$__.session == null) {
+ return;
+ }
+
this.$__.session = session;
- if (!this.ownerDocument) {
+ if (!this.$isSubdocument) {
const subdocs = this.$getAllSubdocs();
for (const child of subdocs) {
child.$session(session);
@@ -1000,6 +964,48 @@ Document.prototype.$session = function $session(session) {
return session;
};
+/**
+ * Getter/setter around whether this document will apply timestamps by
+ * default when using `save()` and `bulkSave()`.
+ *
+ * #### Example:
+ *
+ * const TestModel = mongoose.model('Test', new Schema({ name: String }, { timestamps: true }));
+ * const doc = new TestModel({ name: 'John Smith' });
+ *
+ * doc.$timestamps(); // true
+ *
+ * doc.$timestamps(false);
+ * await doc.save(); // Does **not** apply timestamps
+ *
+ * @param {Boolean} [value] overwrite the current session
+ * @return {Document|boolean|undefined} When used as a getter (no argument), a boolean will be returned indicating the timestamps option state or if unset "undefined" will be used, otherwise will return "this"
+ * @method $timestamps
+ * @api public
+ * @memberOf Document
+ */
+
+Document.prototype.$timestamps = function $timestamps(value) {
+ if (arguments.length === 0) {
+ if (this.$__.timestamps != null) {
+ return this.$__.timestamps;
+ }
+
+ if (this.$__schema) {
+ return this.$__schema.options.timestamps;
+ }
+
+ return undefined;
+ }
+
+ const currentValue = this.$timestamps();
+ if (value !== currentValue) {
+ this.$__.timestamps = value;
+ }
+
+ return this;
+};
+
/**
* Overwrite all values in this document with the values of `obj`, except
* for immutable properties. Behaves similarly to `set()`, except for it
@@ -1007,10 +1013,10 @@ Document.prototype.$session = function $session(session) {
*
* @param {Object} obj the object to overwrite this document with
* @method overwrite
- * @name overwrite
* @memberOf Document
* @instance
* @api public
+ * @return {Document} this
*/
Document.prototype.overwrite = function overwrite(obj) {
@@ -1040,8 +1046,9 @@ Document.prototype.overwrite = function overwrite(obj) {
* @param {Any} val the value to set
* @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for "on-the-fly" attributes
* @param {Object} [options] optionally specify options that modify the behavior of the set
+ * @param {Boolean} [options.merge=false] if true, setting a [nested path](/docs/subdocs.html#subdocuments-versus-nested-paths) will merge existing values rather than overwrite the whole object. So `doc.set('nested', { a: 1, b: 2 })` becomes `doc.set('nested.a', 1); doc.set('nested.b', 2);`
+ * @return {Document} this
* @method $set
- * @name $set
* @memberOf Document
* @instance
* @api public
@@ -1053,11 +1060,9 @@ Document.prototype.$set = function $set(path, val, type, options) {
type = undefined;
}
- options = options || {};
- const merge = options.merge;
+ const merge = options && options.merge;
const adhoc = type && type !== true;
const constructing = type === true;
- const typeKey = this.$__schema.options.typeKey;
let adhocs;
let keys;
let i = 0;
@@ -1065,7 +1070,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
let key;
let prefix;
- const strict = 'strict' in options
+ const strict = options && 'strict' in options
? options.strict
: this.$__.strictMode;
@@ -1082,7 +1087,11 @@ Document.prototype.$set = function $set(path, val, type, options) {
if (path.$__isNested) {
path = path.toObject();
} else {
- path = path._doc;
+ // This ternary is to support gh-7898 (copying virtuals if same schema)
+ // while not breaking gh-10819, which for some reason breaks if we use toObject()
+ path = path.$__schema === this.$__schema
+ ? applyVirtuals(path, { ...path._doc })
+ : path._doc;
}
}
if (path == null) {
@@ -1096,7 +1105,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
// `_skipMinimizeTopLevel` is because we may have deleted the top-level
// nested key to ensure key order.
- const _skipMinimizeTopLevel = get(options, '_skipMinimizeTopLevel', false);
+ const _skipMinimizeTopLevel = options && options._skipMinimizeTopLevel || false;
if (len === 0 && _skipMinimizeTopLevel) {
delete options._skipMinimizeTopLevel;
if (val) {
@@ -1105,9 +1114,11 @@ Document.prototype.$set = function $set(path, val, type, options) {
return this;
}
+ options = Object.assign({}, options, { _skipMinimizeTopLevel: false });
+
for (let i = 0; i < len; ++i) {
key = keys[i];
- const pathName = prefix + key;
+ const pathName = prefix ? prefix + key : key;
pathtype = this.$__schema.pathType(pathName);
const valForKey = path[key];
@@ -1115,24 +1126,19 @@ Document.prototype.$set = function $set(path, val, type, options) {
// them to ensure we keep the user's key order.
if (type === true &&
!prefix &&
- path[key] != null &&
+ valForKey != null &&
pathtype === 'nested' &&
this._doc[key] != null) {
delete this._doc[key];
- // Make sure we set `{}` back even if we minimize re: gh-8565
- options = Object.assign({}, options, { _skipMinimizeTopLevel: true });
- } else {
- // Make sure we set `{_skipMinimizeTopLevel: false}` if don't have overwrite: gh-10441
- options = Object.assign({}, options, { _skipMinimizeTopLevel: false });
}
if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') {
- $applyDefaultsToNested(path[key], prefix + key, this);
- this.$set(prefix + key, path[key], constructing, Object.assign({}, options, { _skipMarkModified: true }));
+ this.$set(pathName, valForKey, constructing, Object.assign({}, options, { _skipMarkModified: true }));
+ $applyDefaultsToNested(this.$get(pathName), pathName, this);
continue;
} else if (strict) {
// Don't overwrite defaults with undefined keys (gh-3981) (gh-9039)
- if (constructing && path[key] === void 0 &&
+ if (constructing && valForKey === void 0 &&
this.$get(pathName) !== void 0) {
continue;
}
@@ -1142,37 +1148,33 @@ Document.prototype.$set = function $set(path, val, type, options) {
}
if (pathtype === 'real' || pathtype === 'virtual') {
- // Check for setting single embedded schema to document (gh-3535)
- let p = path[key];
- if (this.$__schema.paths[pathName] &&
- this.$__schema.paths[pathName].$isSingleNested &&
- path[key] instanceof Document) {
- p = p.toObject({ virtuals: false, transform: false });
- }
- this.$set(prefix + key, p, constructing, options);
- } else if (pathtype === 'nested' && path[key] instanceof Document) {
- this.$set(prefix + key,
- path[key].toObject({ transform: false }), constructing, options);
+ this.$set(pathName, valForKey, constructing, options);
+ } else if (pathtype === 'nested' && valForKey instanceof Document) {
+ this.$set(pathName,
+ valForKey.toObject({ transform: false }), constructing, options);
} else if (strict === 'throw') {
if (pathtype === 'nested') {
- throw new ObjectExpectedError(key, path[key]);
+ throw new ObjectExpectedError(key, valForKey);
} else {
throw new StrictModeError(key);
}
+ } else if (pathtype === 'nested' && valForKey == null) {
+ this.$set(pathName, valForKey, constructing, options);
}
- } else if (path[key] !== void 0) {
- this.$set(prefix + key, path[key], constructing, options);
+ } else if (valForKey !== void 0) {
+ this.$set(pathName, valForKey, constructing, options);
}
}
- // Ensure all properties are in correct order by deleting and recreating every property.
- for (const key of Object.keys(this.$__schema.tree)) {
- if (this._doc.hasOwnProperty(key)) {
- const val = this._doc[key];
- delete this._doc[key];
- this._doc[key] = val;
- }
+ // Ensure all properties are in correct order
+ const orderedDoc = {};
+ const orderedKeys = Object.keys(this.$__schema.tree);
+ for (let i = 0, len = orderedKeys.length; i < len; ++i) {
+ (key = orderedKeys[i]) &&
+ (this._doc.hasOwnProperty(key)) &&
+ (orderedDoc[key] = undefined);
}
+ this._doc = Object.assign(orderedDoc, this._doc);
return this;
}
@@ -1199,6 +1201,14 @@ Document.prototype.$set = function $set(path, val, type, options) {
if (pathType === 'nested' && val) {
if (typeof val === 'object' && val != null) {
+ if (val.$__ != null) {
+ val = val.toObject(internalToObjectOptions);
+ }
+ if (val == null) {
+ this.invalidate(path, new MongooseError.CastError('Object', val, path));
+ return this;
+ }
+ const wasModified = this.$isModified(path);
const hasInitialVal = this.$__.savedState != null && this.$__.savedState.hasOwnProperty(path);
if (this.$__.savedState != null && !this.$isNew && !this.$__.savedState.hasOwnProperty(path)) {
const initialVal = this.$__getValue(path);
@@ -1221,14 +1231,15 @@ Document.prototype.$set = function $set(path, val, type, options) {
this.$__setValue(path, {});
for (const key of keys) {
- this.$set(path + '.' + key, val[key], constructing, options);
+ this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true });
}
- if (priorVal != null && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) {
+ if (priorVal != null &&
+ (!wasModified || hasInitialVal) &&
+ utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) {
this.unmarkModified(path);
} else {
this.markModified(path);
}
- cleanModifiedSubpaths(this, path, { skipDocArrays: true });
return this;
}
this.invalidate(path, new MongooseError.CastError('Object', val, path));
@@ -1239,7 +1250,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
const parts = path.indexOf('.') === -1 ? [path] : path.split('.');
// Might need to change path for top-level alias
- if (typeof this.$__schema.aliases[parts[0]] == 'string') {
+ if (typeof this.$__schema.aliases[parts[0]] === 'string') {
parts[0] = this.$__schema.aliases[parts[0]];
}
@@ -1292,7 +1303,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
let curPath = '';
for (i = 0; i < parts.length - 1; ++i) {
cur = cur[parts[i]];
- curPath += (curPath.length > 0 ? '.' : '') + parts[i];
+ curPath += (curPath.length !== 0 ? '.' : '') + parts[i];
if (!cur) {
this.$set(curPath, {});
// Hack re: gh-5800. If nested field is not selected, it probably exists
@@ -1315,7 +1326,8 @@ Document.prototype.$set = function $set(path, val, type, options) {
if (parts.length <= 1) {
pathToMark = path;
} else {
- for (i = 0; i < parts.length; ++i) {
+ const len = parts.length;
+ for (i = 0; i < len; ++i) {
const subpath = parts.slice(0, i + 1).join('.');
if (this.$get(subpath, null, { getters: false }) === null) {
pathToMark = subpath;
@@ -1330,6 +1342,10 @@ Document.prototype.$set = function $set(path, val, type, options) {
if (!schema) {
this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal);
+
+ if (pathType === 'nested' && val == null) {
+ cleanModifiedSubpaths(this, path);
+ }
return this;
}
@@ -1340,7 +1356,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
_markValidSubpaths(this, path);
}
- if (schema.$isSingleNested && val != null && merge) {
+ if (val != null && merge && schema.$isSingleNested) {
if (val instanceof Document) {
val = val.toObject({ virtuals: false, transform: false });
}
@@ -1381,13 +1397,15 @@ Document.prototype.$set = function $set(path, val, type, options) {
})();
let didPopulate = false;
- if (refMatches && val instanceof Document) {
- this.$populated(path, val._id, { [populateModelSymbol]: val.constructor });
- val.$__.wasPopulated = true;
+ if (refMatches && val instanceof Document && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._id))) {
+ const unpopulatedValue = (schema && schema.$isSingleNested) ? schema.cast(val, this) : val._id;
+ this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor });
+ val.$__.wasPopulated = { value: unpopulatedValue };
didPopulate = true;
}
let popOpts;
+ const typeKey = this.$__schema.options.typeKey;
if (schema.options &&
Array.isArray(schema.options[typeKey]) &&
schema.options[typeKey].length &&
@@ -1397,22 +1415,27 @@ Document.prototype.$set = function $set(path, val, type, options) {
this.$populated(path, val.map(function(v) { return v._id; }), popOpts);
for (const doc of val) {
- doc.$__.wasPopulated = true;
+ doc.$__.wasPopulated = { value: doc._id };
}
didPopulate = true;
}
- if (this.$__schema.singleNestedPaths[path] == null) {
+ if (this.$__schema.singleNestedPaths[path] == null && (!refMatches || !schema.$isSingleNested || !val.$__)) {
// If this path is underneath a single nested schema, we'll call the setter
// later in `$__set()` because we don't take `_doc` when we iterate through
// a single nested doc. That's to make sure we get the correct context.
// Otherwise we would double-call the setter, see gh-7196.
- val = schema.applySetters(val, this, false, priorVal);
+ if (options != null && options.overwriteImmutable) {
+ val = schema.applySetters(val, this, false, priorVal, { overwriteImmutable: true });
+ } else {
+ val = schema.applySetters(val, this, false, priorVal);
+ }
}
- if (schema.$isMongooseDocumentArray &&
- Array.isArray(val) &&
- val.length > 0 &&
+ if (Array.isArray(val) &&
+ !Array.isArray(schema) &&
+ schema.$isMongooseDocumentArray &&
+ val.length !== 0 &&
val[0] != null &&
val[0].$__ != null &&
val[0].$__.populated != null) {
@@ -1438,7 +1461,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
delete this.$__.populated[path];
}
- if (schema.$isSingleNested && val != null) {
+ if (val != null && schema.$isSingleNested) {
_checkImmutableSubpaths(val, schema, priorVal);
}
@@ -1460,15 +1483,13 @@ Document.prototype.$set = function $set(path, val, type, options) {
}
if (shouldSet) {
- const doc = this.ownerDocument ? this.ownerDocument() : this;
- const savedState = doc.$__.savedState;
- const savedStatePath = this.ownerDocument ? this.$__.fullPath + '.' + path : path;
- if (savedState != null) {
- const firstDot = savedStatePath.indexOf('.');
- const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot);
- if (!savedState.hasOwnProperty(topLevelPath)) {
- savedState[topLevelPath] = utils.clone(doc.$__getValue(topLevelPath));
- }
+ let savedState = null;
+ let savedStatePath = null;
+ if (!constructing) {
+ const doc = this.$isSubdocument ? this.ownerDocument() : this;
+ savedState = doc.$__.savedState;
+ savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path;
+ doc.$__saveInitialState(savedStatePath);
}
this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal);
@@ -1515,8 +1536,9 @@ function _isManuallyPopulatedArray(val, ref) {
/**
* Sets the value of a path, or many paths.
+ * Alias for [`.$set`](#document_Document-$set).
*
- * ####Example:
+ * #### Example:
*
* // path, value
* doc.set(path, value)
@@ -1542,6 +1564,7 @@ function _isManuallyPopulatedArray(val, ref) {
* @param {Any} val the value to set
* @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for "on-the-fly" attributes
* @param {Object} [options] optionally specify options that modify the behavior of the set
+ * @return {Document} this
* @api public
* @method set
* @memberOf Document
@@ -1553,6 +1576,14 @@ Document.prototype.set = Document.prototype.$set;
/**
* Determine if we should mark this change as modified.
*
+ * @param {never} pathToMark UNUSED
+ * @param {String|Symbol} path
+ * @param {Object} options
+ * @param {Any} constructing
+ * @param {never} parts UNUSED
+ * @param {Schema} schema
+ * @param {Any} val
+ * @param {Any} priorVal
* @return {Boolean}
* @api private
* @method $__shouldModify
@@ -1561,12 +1592,16 @@ Document.prototype.set = Document.prototype.$set;
*/
Document.prototype.$__shouldModify = function(pathToMark, path, options, constructing, parts, schema, val, priorVal) {
- if (options._skipMarkModified) {
+ if (options && options._skipMarkModified) {
return false;
}
if (this.$isNew) {
return true;
}
+ // Is path already modified? If so, always modify. We may unmark modified later.
+ if (path in this.$__.activePaths.getStatePaths('modify')) {
+ return true;
+ }
// Re: the note about gh-7196, `val` is the raw value without casting or
// setters if the full path is under a single nested subdoc because we don't
@@ -1581,7 +1616,7 @@ Document.prototype.$__shouldModify = function(pathToMark, path, options, constru
return true;
}
- if (val === void 0 && path in this.$__.activePaths.states.default) {
+ if (val === void 0 && path in this.$__.activePaths.getStatePaths('default')) {
// we're just unsetting the default value which was never saved
return false;
}
@@ -1594,14 +1629,14 @@ Document.prototype.$__shouldModify = function(pathToMark, path, options, constru
return false;
}
- if (!deepEqual(val, priorVal || utils.getValue(path, this))) {
+ if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) {
return true;
}
if (!constructing &&
val !== null &&
val !== undefined &&
- path in this.$__.activePaths.states.default &&
+ path in this.$__.activePaths.getStatePaths('default') &&
deepEqual(val, schema.getDefault(this, constructing))) {
// a path with a default was $unset on the server
// and the user is setting it to the same value again
@@ -1613,6 +1648,14 @@ Document.prototype.$__shouldModify = function(pathToMark, path, options, constru
/**
* Handles the actual setting of the value and marking the path modified if appropriate.
*
+ * @param {String} pathToMark
+ * @param {String|Symbol} path
+ * @param {Object} options
+ * @param {Any} constructing
+ * @param {Array} parts
+ * @param {Schema} schema
+ * @param {Any} val
+ * @param {Any} priorVal
* @api private
* @method $__set
* @memberOf Document
@@ -1624,34 +1667,38 @@ Document.prototype.$__set = function(pathToMark, path, options, constructing, pa
const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts,
schema, val, priorVal);
- const _this = this;
if (shouldModify) {
+ if (this.$__.primitiveAtomics && this.$__.primitiveAtomics[path]) {
+ delete this.$__.primitiveAtomics[path];
+ if (Object.keys(this.$__.primitiveAtomics).length === 0) {
+ delete this.$__.primitiveAtomics;
+ }
+ }
this.markModified(pathToMark);
// handle directly setting arrays (gh-1126)
MongooseArray || (MongooseArray = require('./types/array'));
- if (val && val.isMongooseArray) {
+ if (val && utils.isMongooseArray(val)) {
val._registerAtomic('$set', val);
// Update embedded document parent references (gh-5189)
- if (val.isMongooseDocumentArray) {
+ if (utils.isMongooseDocumentArray(val)) {
val.forEach(function(item) {
item && item.__parentArray && (item.__parentArray = val);
});
}
-
- // Small hack for gh-1638: if we're overwriting the entire array, ignore
- // paths that were modified before the array overwrite
- this.$__.activePaths.forEach(function(modifiedPath) {
- if (modifiedPath.startsWith(path + '.')) {
- _this.$__.activePaths.ignore(modifiedPath);
- }
- });
}
- } else if (Array.isArray(val) && val.isMongooseArray && Array.isArray(priorVal) && priorVal.isMongooseArray) {
+ } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) {
val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol];
val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol];
+ if (utils.isMongooseDocumentArray(val)) {
+ val.forEach(doc => {
+ if (doc) {
+ doc.isNew = false;
+ }
+ });
+ }
}
let obj = this._doc;
@@ -1678,7 +1725,7 @@ Document.prototype.$__set = function(pathToMark, path, options, constructing, pa
obj = obj[parts[i]];
} else if (obj[parts[i]] && obj[parts[i]] instanceof Embedded) {
obj = obj[parts[i]];
- } else if (obj[parts[i]] && obj[parts[i]].$isSingleNested) {
+ } else if (obj[parts[i]] && !Array.isArray(obj[parts[i]]) && obj[parts[i]].$isSingleNested) {
obj = obj[parts[i]];
} else if (obj[parts[i]] && Array.isArray(obj[parts[i]])) {
obj = obj[parts[i]];
@@ -1694,6 +1741,7 @@ Document.prototype.$__set = function(pathToMark, path, options, constructing, pa
* Gets a raw value from a path (no getters)
*
* @param {String} path
+ * @return {Any} Returns the value from the given `path`.
* @api private
*/
@@ -1701,11 +1749,86 @@ Document.prototype.$__getValue = function(path) {
return utils.getValue(path, this._doc);
};
+/**
+ * Increments the numeric value at `path` by the given `val`.
+ * When you call `save()` on this document, Mongoose will send a
+ * [`$inc`](https://www.mongodb.com/docs/manual/reference/operator/update/inc/)
+ * as opposed to a `$set`.
+ *
+ * #### Example:
+ *
+ * const schema = new Schema({ counter: Number });
+ * const Test = db.model('Test', schema);
+ *
+ * const doc = await Test.create({ counter: 0 });
+ * doc.$inc('counter', 2);
+ * await doc.save(); // Sends a `{ $inc: { counter: 2 } }` to MongoDB
+ * doc.counter; // 2
+ *
+ * doc.counter += 2;
+ * await doc.save(); // Sends a `{ $set: { counter: 2 } }` to MongoDB
+ *
+ * @param {String|Array} path path or paths to update
+ * @param {Number} val increment `path` by this value
+ * @return {Document} this
+ */
+
+Document.prototype.$inc = function $inc(path, val) {
+ if (val == null) {
+ val = 1;
+ }
+
+ if (Array.isArray(path)) {
+ path.forEach((p) => this.$inc(p, val));
+ return this;
+ }
+
+ const schemaType = this.$__path(path);
+ if (schemaType == null) {
+ if (this.$__.strictMode === 'throw') {
+ throw new StrictModeError(path);
+ } else if (this.$__.strictMode === true) {
+ return this;
+ }
+ } else if (schemaType.instance !== 'Number') {
+ this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path));
+ return this;
+ }
+
+ const currentValue = this.$__getValue(path) || 0;
+ let shouldSet = false;
+ let valToSet = null;
+ let valToInc = val;
+
+ try {
+ val = schemaType.cast(val);
+ valToSet = schemaType.applySetters(currentValue + val, this);
+ valToInc = valToSet - currentValue;
+ shouldSet = true;
+ } catch (err) {
+ this.invalidate(path, new MongooseError.CastError('number', val, path, err));
+ }
+
+ if (shouldSet) {
+ this.$__.primitiveAtomics = this.$__.primitiveAtomics || {};
+ if (this.$__.primitiveAtomics[path] == null) {
+ this.$__.primitiveAtomics[path] = { $inc: valToInc };
+ } else {
+ this.$__.primitiveAtomics[path].$inc += valToInc;
+ }
+ this.markModified(path);
+ this.$__setValue(path, valToSet);
+ }
+
+ return this;
+};
+
/**
* Sets a raw value for a path (no casting, setters, transformations)
*
* @param {String} path
* @param {Object} value
+ * @return {Document} this
* @api private
*/
@@ -1717,7 +1840,7 @@ Document.prototype.$__setValue = function(path, val) {
/**
* Returns the value of a path.
*
- * ####Example
+ * #### Example:
*
* // path
* doc.get('age') // 47
@@ -1730,35 +1853,55 @@ Document.prototype.$__setValue = function(path, val) {
* @param {Object} [options]
* @param {Boolean} [options.virtuals=false] Apply virtuals before getting this path
* @param {Boolean} [options.getters=true] If false, skip applying getters and just get the raw value
+ * @return {Any}
* @api public
*/
Document.prototype.get = function(path, type, options) {
let adhoc;
- options = options || {};
+ if (options == null) {
+ options = {};
+ }
if (type) {
adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options);
}
+ const noDottedPath = options.noDottedPath;
- let schema = this.$__path(path);
+ // Fast path if we know we're just accessing top-level path on the document:
+ // just get the schema path, avoid `$__path()` because that does string manipulation
+ let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path);
if (schema == null) {
schema = this.$__schema.virtualpath(path);
+
+ if (schema != null) {
+ return schema.applyGetters(void 0, this);
+ }
+ }
+
+ if (noDottedPath) {
+ let obj = this._doc[path];
+ if (adhoc) {
+ obj = adhoc.cast(obj);
+ }
+ if (schema != null && options.getters !== false) {
+ return schema.applyGetters(obj, this);
+ }
+ return obj;
}
- if (schema instanceof MixedSchema) {
+
+ if (schema != null && schema.instance === 'Mixed') {
const virtual = this.$__schema.virtualpath(path);
if (virtual != null) {
schema = virtual;
}
}
- const pieces = path.indexOf('.') === -1 ? [path] : path.split('.');
- let obj = this._doc;
- if (schema instanceof VirtualType) {
- return schema.applyGetters(void 0, this);
- }
+ const hasDot = path.indexOf('.') !== -1;
+ let obj = this._doc;
+ const pieces = hasDot ? path.split('.') : [path];
// Might need to change path for top-level alias
- if (typeof this.$__schema.aliases[pieces[0]] == 'string') {
+ if (typeof this.$__schema.aliases[pieces[0]] === 'string') {
pieces[0] = this.$__schema.aliases[pieces[0]];
}
@@ -1798,10 +1941,12 @@ Document.prototype.get = function(path, type, options) {
Document.prototype[getSymbol] = Document.prototype.get;
Document.prototype.$get = Document.prototype.get;
+
/**
* Returns the schematype for the given `path`.
*
* @param {String} path
+ * @return {SchemaPath}
* @api private
* @method $__path
* @memberOf Document
@@ -1823,7 +1968,7 @@ Document.prototype.$__path = function(path) {
*
* _Very helpful when using [Mixed](https://mongoosejs.com/docs/schematypes.html#mixed) types._
*
- * ####Example:
+ * #### Example:
*
* doc.mixed.type = 'changed';
* doc.markModified('mixed.type');
@@ -1835,17 +1980,35 @@ Document.prototype.$__path = function(path) {
*/
Document.prototype.markModified = function(path, scope) {
+ this.$__saveInitialState(path);
+
this.$__.activePaths.modify(path);
- if (scope != null && !this.ownerDocument) {
+ if (scope != null && !this.$isSubdocument) {
this.$__.pathsToScopes = this.$__pathsToScopes || {};
this.$__.pathsToScopes[path] = scope;
}
};
+/*!
+ * ignore
+ */
+
+Document.prototype.$__saveInitialState = function $__saveInitialState(path) {
+ const savedState = this.$__.savedState;
+ const savedStatePath = path;
+ if (savedState != null) {
+ const firstDot = savedStatePath.indexOf('.');
+ const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot);
+ if (!savedState.hasOwnProperty(topLevelPath)) {
+ savedState[topLevelPath] = utils.clone(this.$__getValue(topLevelPath));
+ }
+ }
+};
+
/**
* Clears the modified state on the specified path.
*
- * ####Example:
+ * #### Example:
*
* doc.foo = 'bar';
* doc.unmarkModified('foo');
@@ -1865,7 +2028,7 @@ Document.prototype.unmarkModified = function(path) {
/**
* Don't run validation on this path or persist changes to this path.
*
- * ####Example:
+ * #### Example:
*
* doc.foo = null;
* doc.$ignore('foo');
@@ -1890,7 +2053,8 @@ Document.prototype.$ignore = function(path) {
* A path `a` may be in `modifiedPaths()` but not in `directModifiedPaths()`
* because a child of `a` was directly modified.
*
- * ####Example
+ * #### Example:
+ *
* const schema = new Schema({ foo: String, nested: { bar: String } });
* const Model = mongoose.model('Test', schema);
* await Model.create({ foo: 'original', nested: { bar: 'original' } });
@@ -1900,12 +2064,12 @@ Document.prototype.$ignore = function(path) {
* doc.directModifiedPaths(); // ['nested.bar']
* doc.modifiedPaths(); // ['nested', 'nested.bar']
*
- * @return {Array}
+ * @return {String[]}
* @api public
*/
Document.prototype.directModifiedPaths = function() {
- return Object.keys(this.$__.activePaths.states.modify);
+ return Object.keys(this.$__.activePaths.getStatePaths('modify'));
};
/**
@@ -1913,7 +2077,8 @@ Document.prototype.directModifiedPaths = function() {
* Useful for determining whether this subdoc will get stripped out by the
* [minimize option](/docs/guide.html#minimize).
*
- * ####Example:
+ * #### Example:
+ *
* const schema = new Schema({ nested: { foo: String } });
* const Model = mongoose.model('Test', schema);
* const doc = new Model({});
@@ -1924,6 +2089,7 @@ Document.prototype.directModifiedPaths = function() {
* doc.$isEmpty('nested'); // false
* doc.nested.$isEmpty(); // false
*
+ * @param {String} [path]
* @memberOf Document
* @instance
* @api public
@@ -1939,7 +2105,7 @@ Document.prototype.$isEmpty = function(path) {
transform: false
};
- if (arguments.length > 0) {
+ if (arguments.length !== 0) {
const v = this.$get(path);
if (v == null) {
return true;
@@ -1956,6 +2122,10 @@ Document.prototype.$isEmpty = function(path) {
return Object.keys(this.toObject(isEmptyOptions)).length === 0;
};
+/*!
+ * ignore
+ */
+
function _isEmpty(v) {
if (v == null) {
return true;
@@ -1976,57 +2146,66 @@ function _isEmpty(v) {
*
* @param {Object} [options]
* @param {Boolean} [options.includeChildren=false] if true, returns children of modified paths as well. For example, if false, the list of modified paths for `doc.colors = { primary: 'blue' };` will **not** contain `colors.primary`. If true, `modifiedPaths()` will return an array that contains `colors.primary`.
- * @return {Array}
+ * @return {String[]}
* @api public
*/
Document.prototype.modifiedPaths = function(options) {
options = options || {};
- const directModifiedPaths = Object.keys(this.$__.activePaths.states.modify);
- const _this = this;
- return directModifiedPaths.reduce(function(list, path) {
- const parts = path.split('.');
- list = list.concat(parts.reduce(function(chains, part, i) {
- return chains.concat(parts.slice(0, i).concat(part).join('.'));
- }, []).filter(function(chain) {
- return (list.indexOf(chain) === -1);
- }));
+
+ const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify'));
+ const result = new Set();
+
+ let i = 0;
+ let j = 0;
+ const len = directModifiedPaths.length;
+
+ for (i = 0; i < len; ++i) {
+ const path = directModifiedPaths[i];
+ const parts = parentPaths(path);
+ const pLen = parts.length;
+
+ for (j = 0; j < pLen; ++j) {
+ result.add(parts[j]);
+ }
if (!options.includeChildren) {
- return list;
+ continue;
}
- let cur = _this.$get(path);
- if (cur != null && typeof cur === 'object') {
+ let ii = 0;
+ let cur = this.$get(path);
+ if (typeof cur === 'object' && cur !== null) {
if (cur._doc) {
cur = cur._doc;
}
+ const len = cur.length;
if (Array.isArray(cur)) {
- const len = cur.length;
- for (let i = 0; i < len; ++i) {
- if (list.indexOf(path + '.' + i) === -1) {
- list.push(path + '.' + i);
- if (cur[i] != null && cur[i].$__) {
- const modified = cur[i].modifiedPaths();
- for (const childPath of modified) {
- list.push(path + '.' + i + '.' + childPath);
+ for (ii = 0; ii < len; ++ii) {
+ const subPath = path + '.' + ii;
+ if (!result.has(subPath)) {
+ result.add(subPath);
+ if (cur[ii] != null && cur[ii].$__) {
+ const modified = cur[ii].modifiedPaths();
+ let iii = 0;
+ const iiiLen = modified.length;
+ for (iii = 0; iii < iiiLen; ++iii) {
+ result.add(subPath + '.' + modified[iii]);
}
}
}
}
} else {
- Object.keys(cur).
- filter(function(key) {
- return list.indexOf(path + '.' + key) === -1;
- }).
- forEach(function(key) {
- list.push(path + '.' + key);
- });
+ const keys = Object.keys(cur);
+ let ii = 0;
+ const len = keys.length;
+ for (ii = 0; ii < len; ++ii) {
+ result.add(path + '.' + keys[ii]);
+ }
}
}
-
- return list;
- }, []);
+ }
+ return Array.from(result);
};
Document.prototype[documentModifiedPaths] = Document.prototype.modifiedPaths;
@@ -2037,7 +2216,7 @@ Document.prototype[documentModifiedPaths] = Document.prototype.modifiedPaths;
*
* If `path` is given, checks if a path or any full path containing `path` as part of its path chain has been modified.
*
- * ####Example
+ * #### Example:
*
* doc.set('documents.0.title', 'changed');
* doc.isModified() // true
@@ -2047,21 +2226,45 @@ Document.prototype[documentModifiedPaths] = Document.prototype.modifiedPaths;
* doc.isDirectModified('documents') // false
*
* @param {String} [path] optional
+ * @param {Object} [options]
+ * @param {Boolean} [options.ignoreAtomics=false] If true, doesn't return true if path is underneath an array that was modified with atomic operations like `push()`
* @return {Boolean}
* @api public
*/
-Document.prototype.isModified = function(paths, modifiedPaths) {
+Document.prototype.isModified = function(paths, options, modifiedPaths) {
if (paths) {
- if (!Array.isArray(paths)) {
- paths = paths.split(' ');
+ const ignoreAtomics = options && options.ignoreAtomics;
+ const directModifiedPathsObj = this.$__.activePaths.states.modify;
+ if (directModifiedPathsObj == null) {
+ return false;
+ }
+
+ if (typeof paths === 'string') {
+ paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' ');
}
+
+ for (const path of paths) {
+ if (directModifiedPathsObj[path] != null) {
+ return true;
+ }
+ }
+
const modified = modifiedPaths || this[documentModifiedPaths]();
- const directModifiedPaths = Object.keys(this.$__.activePaths.states.modify);
const isModifiedChild = paths.some(function(path) {
return !!~modified.indexOf(path);
});
+ let directModifiedPaths = Object.keys(directModifiedPathsObj);
+ if (ignoreAtomics) {
+ directModifiedPaths = directModifiedPaths.filter(path => {
+ const value = this.$__getValue(path);
+ if (value != null && value[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) {
+ return false;
+ }
+ return true;
+ });
+ }
return isModifiedChild || paths.some(function(path) {
return directModifiedPaths.some(function(mod) {
return mod === path || path.startsWith(mod + '.');
@@ -2072,6 +2275,14 @@ Document.prototype.isModified = function(paths, modifiedPaths) {
return this.$__.activePaths.some('modify');
};
+/**
+ * Alias of [`.isModified`](#document_Document-isModified)
+ *
+ * @method $isModified
+ * @memberOf Document
+ * @api public
+ */
+
Document.prototype.$isModified = Document.prototype.isModified;
Document.prototype[documentIsModified] = Document.prototype.isModified;
@@ -2079,7 +2290,7 @@ Document.prototype[documentIsModified] = Document.prototype.isModified;
/**
* Checks if a path is set to its default.
*
- * ####Example
+ * #### Example:
*
* MyModel = mongoose.model('test', { name: { type: String, default: 'Val '} });
* const m = new MyModel();
@@ -2099,7 +2310,7 @@ Document.prototype.$isDefault = function(path) {
}
if (typeof path === 'string' && path.indexOf(' ') === -1) {
- return this.$__.activePaths.states.default.hasOwnProperty(path);
+ return this.$__.activePaths.getStatePaths('default').hasOwnProperty(path);
}
let paths = path;
@@ -2107,13 +2318,14 @@ Document.prototype.$isDefault = function(path) {
paths = paths.split(' ');
}
- return paths.some(path => this.$__.activePaths.states.default.hasOwnProperty(path));
+ return paths.some(path => this.$__.activePaths.getStatePaths('default').hasOwnProperty(path));
};
/**
* Getter/setter, determines whether the document was removed or not.
*
- * ####Example:
+ * #### Example:
+ *
* const product = await product.remove();
* product.$isDeleted(); // true
* product.remove(); // no-op, doesn't send anything to the db
@@ -2124,7 +2336,7 @@ Document.prototype.$isDefault = function(path) {
*
*
* @param {Boolean} [val] optional, overrides whether mongoose thinks the doc is deleted
- * @return {Boolean} whether mongoose thinks this doc is deleted.
+ * @return {Boolean|Document} whether mongoose thinks this doc is deleted.
* @method $isDeleted
* @memberOf Document
* @instance
@@ -2143,13 +2355,13 @@ Document.prototype.$isDeleted = function(val) {
/**
* Returns true if `path` was directly set and modified, else false.
*
- * ####Example
+ * #### Example:
*
* doc.set('documents.0.title', 'changed');
* doc.isDirectModified('documents.0.title') // true
* doc.isDirectModified('documents') // false
*
- * @param {String|Array<String>} path
+ * @param {String|String[]} [path]
* @return {Boolean}
* @api public
*/
@@ -2160,7 +2372,7 @@ Document.prototype.isDirectModified = function(path) {
}
if (typeof path === 'string' && path.indexOf(' ') === -1) {
- return this.$__.activePaths.states.modify.hasOwnProperty(path);
+ return this.$__.activePaths.getStatePaths('modify').hasOwnProperty(path);
}
let paths = path;
@@ -2168,13 +2380,13 @@ Document.prototype.isDirectModified = function(path) {
paths = paths.split(' ');
}
- return paths.some(path => this.$__.activePaths.states.modify.hasOwnProperty(path));
+ return paths.some(path => this.$__.activePaths.getStatePaths('modify').hasOwnProperty(path));
};
/**
* Checks if `path` is in the `init` state, that is, it was set by `Document#init()` and not modified since.
*
- * @param {String} path
+ * @param {String} [path]
* @return {Boolean}
* @api public
*/
@@ -2185,7 +2397,7 @@ Document.prototype.isInit = function(path) {
}
if (typeof path === 'string' && path.indexOf(' ') === -1) {
- return this.$__.activePaths.states.init.hasOwnProperty(path);
+ return this.$__.activePaths.getStatePaths('init').hasOwnProperty(path);
}
let paths = path;
@@ -2193,19 +2405,19 @@ Document.prototype.isInit = function(path) {
paths = paths.split(' ');
}
- return paths.some(path => this.$__.activePaths.states.init.hasOwnProperty(path));
+ return paths.some(path => this.$__.activePaths.getStatePaths('init').hasOwnProperty(path));
};
/**
* Checks if `path` was selected in the source query which initialized this document.
*
- * ####Example
+ * #### Example:
*
* const doc = await Thing.findOne().select('name');
* doc.isSelected('name') // true
* doc.isSelected('age') // false
*
- * @param {String|Array<String>} path
+ * @param {String|String[]} path
* @return {Boolean}
* @api public
*/
@@ -2214,7 +2426,9 @@ Document.prototype.isSelected = function isSelected(path) {
if (this.$__.selected == null) {
return true;
}
-
+ if (!path) {
+ return false;
+ }
if (path === '_id') {
return this.$__.selected._id !== 0;
}
@@ -2278,7 +2492,7 @@ Document.prototype.$__isSelected = Document.prototype.isSelected;
* Checks if `path` was explicitly selected. If no projection, always returns
* true.
*
- * ####Example
+ * #### Example:
*
* Thing.findOne().select('nested.name').exec(function (err, doc) {
* doc.isDirectSelected('nested.name') // true
@@ -2340,11 +2554,11 @@ Document.prototype.isDirectSelected = function isDirectSelected(path) {
/**
* Executes registered validation rules for this document.
*
- * ####Note:
+ * #### Note:
*
* This method is called `pre` save and if a validation rule is violated, [save](#model_Model-save) is aborted and the error is returned to your `callback`.
*
- * ####Example:
+ * #### Example:
*
* doc.validate(function (err) {
* if (err) handleError(err);
@@ -2356,7 +2570,7 @@ Document.prototype.isDirectSelected = function isDirectSelected(path) {
* @param {Boolean} [options.validateModifiedOnly=false] if `true` mongoose validates only modified paths.
* @param {Array|string} [options.pathsToSkip] list of paths to skip. If set, Mongoose will validate every modified path that is not in this list.
* @param {Function} [callback] optional callback called after validation completes, passing an error if one occurred
- * @return {Promise} Promise
+ * @return {Promise} Returns a Promise if no `callback` is given.
* @api public
*/
@@ -2364,7 +2578,7 @@ Document.prototype.validate = function(pathsToValidate, options, callback) {
let parallelValidate;
this.$op = 'validate';
- if (this.ownerDocument != null) {
+ if (this.$isSubdocument != null) {
// Skip parallel validate check for subdocuments
} else if (this.$__.validating) {
parallelValidate = new ParallelValidateError(this, {
@@ -2406,11 +2620,20 @@ Document.prototype.validate = function(pathsToValidate, options, callback) {
this.$__validate(pathsToValidate, options, (error) => {
this.$op = null;
+ this.$__.validating = null;
cb(error);
});
}, this.constructor.events);
};
+/**
+ * Alias of [`.validate`](#document_Document-validate)
+ *
+ * @method $validate
+ * @memberOf Document
+ * @api public
+ */
+
Document.prototype.$validate = Document.prototype.validate;
/*!
@@ -2418,7 +2641,12 @@ Document.prototype.$validate = Document.prototype.validate;
*/
function _evaluateRequiredFunctions(doc) {
- Object.keys(doc.$__.activePaths.states.require).forEach(path => {
+ const requiredFields = Object.keys(doc.$__.activePaths.getStatePaths('require'));
+ let i = 0;
+ const len = requiredFields.length;
+ for (i = 0; i < len; ++i) {
+ const path = requiredFields[i];
+
const p = doc.$__schema.path(path);
if (p != null && typeof p.originalRequiredValue === 'function') {
@@ -2429,7 +2657,7 @@ function _evaluateRequiredFunctions(doc) {
doc.invalidate(path, err);
}
}
- });
+ }
}
/*!
@@ -2437,11 +2665,11 @@ function _evaluateRequiredFunctions(doc) {
*/
function _getPathsToValidate(doc) {
- const skipSchemaValidators = {};
+ const doValidateOptions = {};
_evaluateRequiredFunctions(doc);
// only validate required fields when necessary
- let paths = new Set(Object.keys(doc.$__.activePaths.states.require).filter(function(path) {
+ let paths = new Set(Object.keys(doc.$__.activePaths.getStatePaths('require')).filter(function(path) {
if (!doc.$__isSelected(path) && !doc.$isModified(path)) {
return false;
}
@@ -2451,32 +2679,69 @@ function _getPathsToValidate(doc) {
return true;
}));
-
- Object.keys(doc.$__.activePaths.states.init).forEach(addToPaths);
- Object.keys(doc.$__.activePaths.states.modify).forEach(addToPaths);
- Object.keys(doc.$__.activePaths.states.default).forEach(addToPaths);
+ Object.keys(doc.$__.activePaths.getStatePaths('init')).forEach(addToPaths);
+ Object.keys(doc.$__.activePaths.getStatePaths('modify')).forEach(addToPaths);
+ Object.keys(doc.$__.activePaths.getStatePaths('default')).forEach(addToPaths);
function addToPaths(p) { paths.add(p); }
const subdocs = doc.$getAllSubdocs();
const modifiedPaths = doc.modifiedPaths();
for (const subdoc of subdocs) {
if (subdoc.$basePath) {
+ const fullPathToSubdoc = subdoc.$__fullPathWithIndexes();
+
// Remove child paths for now, because we'll be validating the whole
- // subdoc
- for (const p of paths) {
- if (p === null || p.startsWith(subdoc.$basePath + '.')) {
- paths.delete(p);
+ // subdoc.
+ // The following is a faster take on looping through every path in `paths`
+ // and checking if the path starts with `fullPathToSubdoc` re: gh-13191
+ for (const modifiedPath of subdoc.modifiedPaths()) {
+ paths.delete(fullPathToSubdoc + '.' + modifiedPath);
+ }
+
+ if (doc.$isModified(fullPathToSubdoc, null, modifiedPaths) &&
+ !doc.isDirectModified(fullPathToSubdoc) &&
+ !doc.$isDefault(fullPathToSubdoc)) {
+ paths.add(fullPathToSubdoc);
+ if (doc.$__.pathsToScopes == null) {
+ doc.$__.pathsToScopes = {};
+ }
+ doc.$__.pathsToScopes[fullPathToSubdoc] = subdoc.$isDocumentArrayElement ?
+ subdoc.__parentArray :
+ subdoc.$parent();
+
+ doValidateOptions[fullPathToSubdoc] = { skipSchemaValidators: true };
+ if (subdoc.$isDocumentArrayElement && subdoc.__index != null) {
+ doValidateOptions[fullPathToSubdoc].index = subdoc.__index;
}
}
+ }
+ }
- if (doc.$isModified(subdoc.$basePath, modifiedPaths) &&
- !doc.isDirectModified(subdoc.$basePath) &&
- !doc.$isDefault(subdoc.$basePath)) {
- paths.add(subdoc.$basePath);
+ for (const path of paths) {
+ const _pathType = doc.$__schema.path(path);
+ if (!_pathType) {
+ continue;
+ }
- skipSchemaValidators[subdoc.$basePath] = true;
+ if (_pathType.$isMongooseDocumentArray) {
+ for (const p of paths) {
+ if (p == null || p.startsWith(_pathType.path + '.')) {
+ paths.delete(p);
+ }
}
}
+
+ // Optimization: if primitive path with no validators, or array of primitives
+ // with no validators, skip validating this path entirely.
+ if (!_pathType.caster && _pathType.validators.length === 0) {
+ paths.delete(path);
+ } else if (_pathType.$isMongooseArray &&
+ !_pathType.$isMongooseDocumentArray && // Skip document arrays...
+ !_pathType.$embeddedSchemaType.$isMongooseArray && // and arrays of arrays
+ _pathType.validators.length === 0 && // and arrays with top-level validators
+ _pathType.$embeddedSchemaType.validators.length === 0) {
+ paths.delete(path);
+ }
}
// from here on we're not removing items from paths
@@ -2485,12 +2750,26 @@ function _getPathsToValidate(doc) {
// the children as well
for (const path of paths) {
const _pathType = doc.$__schema.path(path);
- if (!_pathType ||
- !_pathType.$isMongooseArray ||
+ if (!_pathType) {
+ continue;
+ }
+
+ if (!_pathType.$isMongooseArray ||
// To avoid potential performance issues, skip doc arrays whose children
// are not required. `getPositionalPathType()` may be slow, so avoid
// it unless we have a case of #6364
- (_pathType.$isMongooseDocumentArray && !get(_pathType, 'schemaOptions.required'))) {
+ (!Array.isArray(_pathType) &&
+ _pathType.$isMongooseDocumentArray &&
+ !(_pathType && _pathType.schemaOptions && _pathType.schemaOptions.required))) {
+ continue;
+ }
+
+ // gh-11380: optimization. If the array isn't a document array and there's no validators
+ // on the array type, there's no need to run validation on the individual array elements.
+ if (_pathType.$isMongooseArray &&
+ !_pathType.$isMongooseDocumentArray && // Skip document arrays...
+ !_pathType.$embeddedSchemaType.$isMongooseArray && // and arrays of arrays
+ _pathType.$embeddedSchemaType.validators.length === 0) {
continue;
}
@@ -2546,7 +2825,7 @@ function _getPathsToValidate(doc) {
}
paths = Array.from(paths);
- return [paths, skipSchemaValidators];
+ return [paths, doValidateOptions];
}
/*!
@@ -2567,7 +2846,7 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) {
(typeof options === 'object') &&
('validateModifiedOnly' in options);
- const pathsToSkip = get(options, 'pathsToSkip', null);
+ const pathsToSkip = (options && options.pathsToSkip) || null;
let shouldValidateModifiedOnly;
if (hasValidateModifiedOnlyOption) {
@@ -2579,7 +2858,8 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) {
const _this = this;
const _complete = () => {
let validationError = this.$__.validationError;
- this.$__.validationError = undefined;
+ this.$__.validationError = null;
+ this.$__.validating = null;
if (shouldValidateModifiedOnly && validationError != null) {
// Remove any validation errors that aren't from modified paths
@@ -2616,7 +2896,7 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) {
let paths = shouldValidateModifiedOnly ?
pathDetails[0].filter((path) => this.$isModified(path)) :
pathDetails[0];
- const skipSchemaValidators = pathDetails[1];
+ const doValidateOptionsByPath = pathDetails[1];
if (typeof pathsToValidate === 'string') {
pathsToValidate = pathsToValidate.split(' ');
}
@@ -2625,6 +2905,7 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) {
} else if (pathsToSkip) {
paths = _handlePathsToSkip(paths, pathsToSkip);
}
+
if (paths.length === 0) {
return immediate(function() {
const error = _complete();
@@ -2688,7 +2969,7 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) {
_this;
const doValidateOptions = {
- skipSchemaValidators: skipSchemaValidators[path],
+ ...doValidateOptionsByPath[path],
path: path,
validateModifiedOnly: shouldValidateModifiedOnly
};
@@ -2696,8 +2977,8 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) {
schemaType.doValidate(val, function(err) {
if (err) {
const isSubdoc = schemaType.$isSingleNested ||
- schemaType.$isArraySubdocument ||
- schemaType.$isMongooseDocumentArray;
+ schemaType.$isArraySubdocument ||
+ schemaType.$isMongooseDocumentArray;
if (isSubdoc && err instanceof ValidationError) {
return --total || complete();
}
@@ -2756,6 +3037,7 @@ function _handlePathsToValidate(paths, pathsToValidate) {
/*!
* ignore
*/
+
function _handlePathsToSkip(paths, pathsToSkip) {
pathsToSkip = new Set(pathsToSkip);
paths = paths.filter(p => !pathsToSkip.has(p));
@@ -2765,11 +3047,11 @@ function _handlePathsToSkip(paths, pathsToSkip) {
/**
* Executes registered validation rules (skipping asynchronous validators) for this document.
*
- * ####Note:
+ * #### Note:
*
* This method is useful if you need synchronous validation.
*
- * ####Example:
+ * #### Example:
*
* const err = doc.validateSync();
* if (err) {
@@ -2778,7 +3060,7 @@ function _handlePathsToSkip(paths, pathsToSkip) {
* // validation passed
* }
*
- * @param {Array|string} pathsToValidate only validate the given paths
+ * @param {Array|string} [pathsToValidate] only validate the given paths
* @param {Object} [options] options for validation
* @param {Boolean} [options.validateModifiedOnly=false] If `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths.
* @param {Array|string} [options.pathsToSkip] list of paths to skip. If set, Mongoose will validate every modified path that is not in this list.
@@ -2828,19 +3110,21 @@ Document.prototype.validateSync = function(pathsToValidate, options) {
}
const validating = {};
- paths.forEach(function(path) {
+ for (let i = 0, len = paths.length; i < len; ++i) {
+ const path = paths[i];
+
if (validating[path]) {
- return;
+ continue;
}
validating[path] = true;
const p = _this.$__schema.path(path);
if (!p) {
- return;
+ continue;
}
if (!_this.$isValid(path)) {
- return;
+ continue;
}
const val = _this.$__getValue(path);
@@ -2854,11 +3138,11 @@ Document.prototype.validateSync = function(pathsToValidate, options) {
p.$isArraySubdocument ||
p.$isMongooseDocumentArray;
if (isSubdoc && err instanceof ValidationError) {
- return;
+ continue;
}
_this.invalidate(path, err, undefined, true);
}
- });
+ }
const err = _this.$__.validationError;
_this.$__.validationError = undefined;
@@ -2885,7 +3169,7 @@ Document.prototype.validateSync = function(pathsToValidate, options) {
* The `value` argument (if passed) will be available through the `ValidationError.value` property.
*
* doc.invalidate('size', 'must be less than 20', 14);
-
+ *
* doc.validate(function (err) {
* console.log(err)
* // prints
@@ -2901,8 +3185,8 @@ Document.prototype.validateSync = function(pathsToValidate, options) {
* })
*
* @param {String} path the field to invalidate. For array elements, use the `array.i.field` syntax, where `i` is the 0-based index in the array.
- * @param {String|Error} errorMsg the error which states the reason `path` was invalid
- * @param {Object|String|Number|any} value optional invalid value
+ * @param {String|Error} err the error which states the reason `path` was invalid
+ * @param {Object|String|Number|any} val optional invalid value
* @param {String} [kind] optional `kind` property for the error
* @return {ValidationError} the current ValidationError, with all currently invalidated paths
* @api public
@@ -2999,10 +3283,10 @@ function _checkImmutableSubpaths(subdoc, schematype, priorVal) {
}
/**
- * Saves this document by inserting a new document into the database if [document.isNew](/docs/api.html#document_Document-isNew) is `true`,
- * or sends an [updateOne](/docs/api.html#document_Document-updateOne) operation **only** with the modifications to the database, it does not replace the whole document in the latter case.
+ * Saves this document by inserting a new document into the database if [document.isNew](#document_Document-isNew) is `true`,
+ * or sends an [updateOne](#document_Document-updateOne) operation **only** with the modifications to the database, it does not replace the whole document in the latter case.
*
- * ####Example:
+ * #### Example:
*
* product.sold = Date.now();
* product = await product.save();
@@ -3010,35 +3294,35 @@ function _checkImmutableSubpaths(subdoc, schematype, priorVal) {
* If save is successful, the returned promise will fulfill with the document
* saved.
*
- * ####Example:
+ * #### Example:
*
* const newProduct = await product.save();
* newProduct === product; // true
*
* @param {Object} [options] options optional options
- * @param {Session} [options.session=null] the [session](https://docs.mongodb.com/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](api.html#document_Document-$session).
- * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](http://mongoosejs.com//docs/guide.html#safe). Use the `w` option instead.
+ * @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](#document_Document-$session).
+ * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](https://mongoosejs.com//docs/guide.html#safe). Use the `w` option instead.
* @param {Boolean} [options.validateBeforeSave] set to false to save without validating.
* @param {Boolean} [options.validateModifiedOnly=false] If `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths.
- * @param {Number|String} [options.w] set the [write concern](https://docs.mongodb.com/manual/reference/write-concern/#w-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern)
- * @param {Boolean} [options.j] set to true for MongoDB to wait until this `save()` has been [journaled before resolving the returned promise](https://docs.mongodb.com/manual/reference/write-concern/#j-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern)
- * @param {Number} [options.wtimeout] sets a [timeout for the write concern](https://docs.mongodb.com/manual/reference/write-concern/#wtimeout). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern).
- * @param {Boolean} [options.checkKeys=true] the MongoDB driver prevents you from saving keys that start with '$' or contain '.' by default. Set this option to `false` to skip that check. See [restrictions on field names](https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names)
- * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](./guide.html#timestamps) are enabled, skip timestamps for this `save()`.
+ * @param {Number|String} [options.w] set the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#w-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern)
+ * @param {Boolean} [options.j] set to true for MongoDB to wait until this `save()` has been [journaled before resolving the returned promise](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern)
+ * @param {Number} [options.wtimeout] sets a [timeout for the write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern).
+ * @param {Boolean} [options.checkKeys=true] the MongoDB driver prevents you from saving keys that start with '$' or contain '.' by default. Set this option to `false` to skip that check. See [restrictions on field names](https://www.mongodb.com/docs/manual/reference/limits/#Restrictions-on-Field-Names)
+ * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this `save()`.
* @param {Function} [fn] optional callback
* @method save
* @memberOf Document
* @instance
- * @throws {DocumentNotFoundError} if this [save updates an existing document](api.html#document_Document-isNew) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating).
+ * @throws {DocumentNotFoundError} if this [save updates an existing document](#document_Document-isNew) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating).
* @return {Promise|undefined} Returns undefined if used with callback or a Promise otherwise.
* @api public
- * @see middleware http://mongoosejs.com/docs/middleware.html
+ * @see middleware https://mongoosejs.com/docs/middleware.html
*/
/**
* Checks if a path is invalid
*
- * @param {String|Array<String>} path the field to check
+ * @param {String|String[]} [path] the field to check. If unset will always return "false"
* @method $isValid
* @memberOf Document
* @instance
@@ -3067,7 +3351,7 @@ Document.prototype.$isValid = function(path) {
* Resets the internal modified state of this document.
*
* @api private
- * @return {Document}
+ * @return {Document} this
* @method $__reset
* @memberOf Document
* @instance
@@ -3075,48 +3359,48 @@ Document.prototype.$isValid = function(path) {
Document.prototype.$__reset = function reset() {
let _this = this;
- DocumentArray || (DocumentArray = require('./types/DocumentArray'));
- this.$__.activePaths
- .map('init', 'modify', function(i) {
- return _this.$__getValue(i);
- })
- .filter(function(val) {
- return val && val instanceof Array && val.isMongooseDocumentArray && val.length;
- })
- .forEach(function(array) {
- let i = array.length;
- while (i--) {
- const doc = array[i];
- if (!doc) {
- continue;
+ // Skip for subdocuments
+ const subdocs = this.$parent() === this ? this.$getAllSubdocs() : [];
+ const resetArrays = new Set();
+ for (const subdoc of subdocs) {
+ const fullPathWithIndexes = subdoc.$__fullPathWithIndexes();
+ if (this.isModified(fullPathWithIndexes) || isParentInit(fullPathWithIndexes)) {
+ subdoc.$__reset();
+ if (subdoc.$isDocumentArrayElement) {
+ if (!resetArrays.has(subdoc.parentArray())) {
+ const array = subdoc.parentArray();
+ this.$__.activePaths.clearPath(fullPathWithIndexes.replace(/\.\d+$/, '').slice(-subdoc.$basePath - 1));
+ array[arrayAtomicsBackupSymbol] = array[arrayAtomicsSymbol];
+ array[arrayAtomicsSymbol] = {};
+
+ resetArrays.add(array);
+ }
+ } else {
+ const parent = subdoc.$parent();
+ if (parent === this) {
+ this.$__.activePaths.clearPath(subdoc.$basePath);
+ } else if (parent != null && parent.$isSubdocument) {
+ // If map path underneath subdocument, may end up with a case where
+ // map path is modified but parent still needs to be reset. See gh-10295
+ parent.$__reset();
}
- doc.$__reset();
}
+ }
+ }
- _this.$__.activePaths.init(array.$path());
-
- array[arrayAtomicsBackupSymbol] = array[arrayAtomicsSymbol];
- array[arrayAtomicsSymbol] = {};
- });
-
- this.$__.activePaths.
- map('init', 'modify', function(i) {
- return _this.$__getValue(i);
- }).
- filter(function(val) {
- return val && val.$isSingleNested;
- }).
- forEach(function(doc) {
- doc.$__reset();
- if (doc.$parent() === _this) {
- _this.$__.activePaths.init(doc.$basePath);
- } else if (doc.$parent() != null && doc.$parent().ownerDocument) {
- // If map path underneath subdocument, may end up with a case where
- // map path is modified but parent still needs to be reset. See gh-10295
- doc.$parent().$__reset();
+ function isParentInit(path) {
+ path = path.indexOf('.') === -1 ? [path] : path.split('.');
+ let cur = '';
+ for (let i = 0; i < path.length; ++i) {
+ cur += (cur.length ? '.' : '') + path[i];
+ if (_this.$__.activePaths[cur] === 'init') {
+ return true;
}
- });
+ }
+
+ return false;
+ }
// clear atomics
this.$__dirty().forEach(function(dirt) {
@@ -3130,8 +3414,8 @@ Document.prototype.$__reset = function reset() {
this.$__.backup = {};
this.$__.backup.activePaths = {
- modify: Object.assign({}, this.$__.activePaths.states.modify),
- default: Object.assign({}, this.$__.activePaths.states.default)
+ modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')),
+ default: Object.assign({}, this.$__.activePaths.getStatePaths('default'))
};
this.$__.backup.validationError = this.$__.validationError;
this.$__.backup.errors = this.$errors;
@@ -3180,6 +3464,7 @@ Document.prototype.$__undoReset = function $__undoReset() {
/**
* Returns this documents dirty paths / vals.
*
+ * @return {Array}
* @api private
* @method $__dirty
* @memberOf Document
@@ -3195,6 +3480,7 @@ Document.prototype.$__dirty = function() {
schema: _this.$__path(path)
};
});
+
// gh-2558: if we had to set a default and the value is not undefined,
// we have to save as well
all = all.concat(this.$__.activePaths.map('default', function(path) {
@@ -3270,6 +3556,7 @@ Document.prototype.$__setSchema = function(schema) {
/**
* Get active path that were changed and are arrays
*
+ * @return {Array}
* @api private
* @method $__getArrayPathsToValidate
* @memberOf Document
@@ -3285,7 +3572,7 @@ Document.prototype.$__getArrayPathsToValidate = function() {
return this.$__getValue(i);
}.bind(this))
.filter(function(val) {
- return val && val instanceof Array && val.isMongooseDocumentArray && val.length;
+ return val && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length;
}).reduce(function(seed, array) {
return seed.concat(array);
}, [])
@@ -3298,6 +3585,7 @@ Document.prototype.$__getArrayPathsToValidate = function() {
/**
* Get all subdocs (by bfs)
*
+ * @return {Array}
* @api public
* @method $getAllSubdocs
* @memberOf Document
@@ -3327,12 +3615,12 @@ Document.prototype.$getAllSubdocs = function() {
seed = Array.from(val.keys()).reduce(function(seed, path) {
return docReducer(val.get(path), seed, null);
}, seed);
- } else if (val && val.$isSingleNested) {
+ } else if (val && !Array.isArray(val) && val.$isSingleNested) {
seed = Object.keys(val._doc).reduce(function(seed, path) {
- return docReducer(val._doc, seed, path);
+ return docReducer(val, seed, path);
}, seed);
seed.push(val);
- } else if (val && val.isMongooseDocumentArray) {
+ } else if (val && utils.isMongooseDocumentArray(val)) {
val.forEach(function _docReduce(doc) {
if (!doc || !doc._doc) {
return;
@@ -3393,6 +3681,7 @@ Document.prototype.$__handleReject = function handleReject(err) {
/**
* Internal helper for toObject() and toJSON() that doesn't manipulate options
*
+ * @return {Object}
* @api private
* @method $toObject
* @memberOf Document
@@ -3406,16 +3695,19 @@ Document.prototype.$toObject = function(options, json) {
};
const path = json ? 'toJSON' : 'toObject';
- const baseOptions = get(this, 'constructor.base.options.' + path, {});
- const schemaOptions = get(this, '$__schema.options', {});
+ const baseOptions = this.constructor &&
+ this.constructor.base &&
+ this.constructor.base.options &&
+ get(this.constructor.base.options, path) || {};
+ const schemaOptions = this.$__schema && this.$__schema.options || {};
// merge base default options with Schema's set default options if available.
// `clone` is necessary here because `utils.options` directly modifies the second input.
defaultOptions = utils.options(defaultOptions, clone(baseOptions));
defaultOptions = utils.options(defaultOptions, clone(schemaOptions[path] || {}));
// If options do not exist or is not an object, set it to empty object
- options = utils.isPOJO(options) ? clone(options) : {};
- options._calledWithOptions = options._calledWithOptions || clone(options);
+ options = utils.isPOJO(options) ? { ...options } : {};
+ options._calledWithOptions = options._calledWithOptions || { ...options };
let _minimize;
if (options._calledWithOptions.minimize != null) {
@@ -3439,11 +3731,12 @@ Document.prototype.$toObject = function(options, json) {
// `clone()` will recursively call `$toObject()` on embedded docs, so we
// need the original options the user passed in, plus `_isNested` and
// `_parentOptions` for checking whether we need to depopulate.
- const cloneOptions = Object.assign(utils.clone(options), {
+ const cloneOptions = Object.assign({}, options, {
_isNested: true,
json: json,
minimize: _minimize,
- flattenMaps: flattenMaps
+ flattenMaps: flattenMaps,
+ _seen: (options && options._seen) || new Map()
});
if (utils.hasUserDefinedProperty(options, 'getters')) {
@@ -3454,12 +3747,11 @@ Document.prototype.$toObject = function(options, json) {
}
const depopulate = options.depopulate ||
- get(options, '_parentOptions.depopulate', false);
+ (options._parentOptions && options._parentOptions.depopulate || false);
// _isNested will only be true if this is not the top level document, we
- // should never depopulate
+ // should never depopulate the top-level document
if (depopulate && options._isNested && this.$__.wasPopulated) {
- // populated paths that we set to a document
- return clone(this._id, cloneOptions);
+ return clone(this.$__.wasPopulated.value || this._id, cloneOptions);
}
// merge default options with input options.
@@ -3469,10 +3761,10 @@ Document.prototype.$toObject = function(options, json) {
options.minimize = _minimize;
cloneOptions._parentOptions = options;
- cloneOptions._skipSingleNestedGetters = true;
+ cloneOptions._skipSingleNestedGetters = false;
const gettersOptions = Object.assign({}, cloneOptions);
- gettersOptions._skipSingleNestedGetters = false;
+ gettersOptions._skipSingleNestedGetters = true;
// remember the root transform function
// to save it from being overwritten by sub-transform functions
@@ -3534,21 +3826,9 @@ Document.prototype.$toObject = function(options, json) {
/**
* Converts this document into a plain-old JavaScript object ([POJO](https://masteringjs.io/tutorials/fundamentals/pojo)).
*
- * Buffers are converted to instances of [mongodb.Binary](http://mongodb.github.com/node-mongodb-native/api-bson-generated/binary.html) for proper storage.
- *
- * ####Options:
+ * Buffers are converted to instances of [mongodb.Binary](https://mongodb.github.io/node-mongodb-native/4.9/classes/Binary.html) for proper storage.
*
- * - `getters` apply all getters (path and virtual getters), defaults to false
- * - `aliases` apply all aliases if `virtuals=true`, defaults to true
- * - `virtuals` apply virtual getters (can override `getters` option), defaults to false
- * - `minimize` remove empty objects, defaults to true
- * - `transform` a transform function to apply to the resulting document before returning
- * - `depopulate` depopulate any populated paths, replacing them with their original refs, defaults to false
- * - `versionKey` whether to include the version key, defaults to true
- * - `flattenMaps` convert Maps to POJOs. Useful if you want to JSON.stringify() the result of toObject(), defaults to false
- * - `useProjection` set to `true` to omit fields that are excluded in this document's projection. Unless you specified a projection, this will omit any field that has `select: false` in the schema.
- *
- * ####Getters/Virtuals
+ * #### Getters/Virtuals
*
* Example of only applying path getters
*
@@ -3566,7 +3846,7 @@ Document.prototype.$toObject = function(options, json) {
*
* schema.set('toObject', { virtuals: true })
*
- * ####Transform
+ * #### Transform:
*
* We may need to perform a transformation of the resulting object based on some criteria, say to remove some sensitive information or return a custom object. In this case we set the optional `transform` function.
*
@@ -3578,7 +3858,7 @@ Document.prototype.$toObject = function(options, json) {
* - `ret` The plain object representation which has been converted
* - `options` The options in use (either schema options or the options passed inline)
*
- * ####Example
+ * #### Example:
*
* // specify the transform schema option
* if (!schema.options.toObject) schema.options.toObject = {};
@@ -3672,8 +3952,8 @@ Document.prototype.$toObject = function(options, json) {
* @param {Boolean} [options.versionKey=true] if false, exclude the version key (`__v` by default) from the output
* @param {Boolean} [options.flattenMaps=false] if true, convert Maps to POJOs. Useful if you want to `JSON.stringify()` the result of `toObject()`.
* @param {Boolean} [options.useProjection=false] - If true, omits fields that are excluded in this document's projection. Unless you specified a projection, this will omit any field that has `select: false` in the schema.
- * @return {Object} js object
- * @see mongodb.Binary http://mongodb.github.com/node-mongodb-native/api-bson-generated/binary.html
+ * @return {Object} js object (not a POJO)
+ * @see mongodb.Binary https://mongodb.github.io/node-mongodb-native/4.9/classes/Binary.html
* @api public
* @memberOf Document
* @instance
@@ -3683,11 +3963,12 @@ Document.prototype.toObject = function(options) {
return this.$toObject(options);
};
-/*!
+/**
* Minimizes an object, removing undefined values and empty objects
*
* @param {Object} object to minimize
* @return {Object}
+ * @api private
*/
function minimize(obj) {
@@ -3731,13 +4012,15 @@ function applyVirtuals(self, json, options, toObjectOptions) {
let assignPath;
let cur = self._doc;
let v;
- const aliases = get(toObjectOptions, 'aliases', true);
+ const aliases = typeof (toObjectOptions && toObjectOptions.aliases) === 'boolean'
+ ? toObjectOptions.aliases
+ : true;
+ options = options || {};
let virtualsToApply = null;
if (Array.isArray(options.virtuals)) {
virtualsToApply = new Set(options.virtuals);
- }
- else if (options.virtuals && options.virtuals.pathsToSkip) {
+ } else if (options.virtuals && options.virtuals.pathsToSkip) {
virtualsToApply = new Set(paths);
for (let i = 0; i < options.virtuals.pathsToSkip.length; i++) {
if (virtualsToApply.has(options.virtuals.pathsToSkip[i])) {
@@ -3750,7 +4033,6 @@ function applyVirtuals(self, json, options, toObjectOptions) {
return json;
}
- options = options || {};
for (i = 0; i < numPaths; ++i) {
path = paths[i];
@@ -3771,7 +4053,7 @@ function applyVirtuals(self, json, options, toObjectOptions) {
if (!path.startsWith(options.path + '.')) {
continue;
}
- assignPath = path.substr(options.path.length + 1);
+ assignPath = path.substring(options.path.length + 1);
}
const parts = assignPath.split('.');
v = clone(self.get(path), options);
@@ -3791,12 +4073,14 @@ function applyVirtuals(self, json, options, toObjectOptions) {
}
-/*!
+/**
* Applies virtuals properties to `json`.
*
* @param {Document} self
* @param {Object} json
+ * @param {Object} [options]
* @return {Object} `json`
+ * @api private
*/
function applyGetters(self, json, options) {
@@ -3815,6 +4099,7 @@ function applyGetters(self, json, options) {
path = paths[i];
const parts = path.split('.');
+
const plen = parts.length;
const last = plen - 1;
let branch = json;
@@ -3828,7 +4113,12 @@ function applyGetters(self, json, options) {
for (let ii = 0; ii < plen; ++ii) {
part = parts[ii];
v = cur[part];
- if (ii === last) {
+ // If we've reached a non-object part of the branch, continuing would
+ // cause "Cannot create property 'foo' on string 'bar'" error.
+ // Necessary for mongoose-intl plugin re: gh-14446
+ if (branch != null && typeof branch !== 'object') {
+ break;
+ } else if (ii === last) {
const val = self.$get(path);
branch[part] = clone(val, options);
} else if (v == null) {
@@ -3846,12 +4136,13 @@ function applyGetters(self, json, options) {
return json;
}
-/*!
+/**
* Applies schema type transforms to `json`.
*
* @param {Document} self
* @param {Object} json
* @return {Object} `json`
+ * @api private
*/
function applySchemaTypeTransforms(self, json) {
@@ -3867,12 +4158,19 @@ function applySchemaTypeTransforms(self, json) {
const schematype = schema.paths[path];
if (typeof schematype.options.transform === 'function') {
const val = self.$get(path);
+ if (val === undefined) {
+ continue;
+ }
const transformedValue = schematype.options.transform.call(self, val);
throwErrorIfPromise(path, transformedValue);
utils.setValue(path, transformedValue, json);
} else if (schematype.$embeddedSchemaType != null &&
typeof schematype.$embeddedSchemaType.options.transform === 'function') {
- const vals = [].concat(self.$get(path));
+ const val = self.$get(path);
+ if (val === undefined) {
+ continue;
+ }
+ const vals = [].concat(val);
const transform = schematype.$embeddedSchemaType.options.transform;
for (let i = 0; i < vals.length; ++i) {
const transformedValue = transform.call(self, vals[i]);
@@ -3925,15 +4223,20 @@ function omitDeselectedFields(self, json) {
}
/**
- * The return value of this method is used in calls to JSON.stringify(doc).
+ * The return value of this method is used in calls to [`JSON.stringify(doc)`](https://thecodebarbarian.com/the-80-20-guide-to-json-stringify-in-javascript#the-tojson-function).
*
* This method accepts the same options as [Document#toObject](#document_Document-toObject). To apply the options to every document of your schema by default, set your [schemas](#schema_Schema) `toJSON` option to the same argument.
*
- * schema.set('toJSON', { virtuals: true })
+ * schema.set('toJSON', { virtuals: true });
*
- * See [schema options](/docs/guide.html#toJSON) for details.
+ * There is one difference between `toJSON()` and `toObject()` options.
+ * When you call `toJSON()`, the [`flattenMaps` option](./document.html#document_Document-toObject) defaults to `true`, because `JSON.stringify()` doesn't convert maps to objects by default.
+ * When you call `toObject()`, the `flattenMaps` option is `false` by default.
+ *
+ * See [schema options](/docs/guide.html#toJSON) for more information on setting `toJSON` option defaults.
*
* @param {Object} options
+ * @param {Boolean} [options.flattenMaps=true] if true, convert Maps to [POJOs](https://masteringjs.io/tutorials/fundamentals/pojo). Useful if you want to `JSON.stringify()` the result.
* @return {Object}
* @see Document#toObject #document_Document-toObject
* @see JSON.stringify() in JavaScript https://thecodebarbarian.com/the-80-20-guide-to-json-stringify-in-javascript.html
@@ -3946,10 +4249,17 @@ Document.prototype.toJSON = function(options) {
return this.$toObject(options, true);
};
+
+Document.prototype.ownerDocument = function() {
+ return this;
+};
+
+
/**
* If this document is a subdocument or populated document, returns the document's
- * parent. Returns `undefined` otherwise.
+ * parent. Returns the original document if there is no parent.
*
+ * @return {Document}
* @api public
* @method parent
* @memberOf Document
@@ -3957,13 +4267,17 @@ Document.prototype.toJSON = function(options) {
*/
Document.prototype.parent = function() {
- return this.$__.parent;
+ if (this.$isSubdocument || this.$__.wasPopulated) {
+ return this.$__.parent;
+ }
+ return this;
};
/**
- * Alias for `parent()`. If this document is a subdocument or populated
+ * Alias for [`parent()`](#document_Document-parent). If this document is a subdocument or populated
* document, returns the document's parent. Returns `undefined` otherwise.
*
+ * @return {Document}
* @api public
* @method $parent
* @memberOf Document
@@ -3975,6 +4289,7 @@ Document.prototype.$parent = Document.prototype.parent;
/**
* Helper for console.log
*
+ * @return {String}
* @api public
* @method inspect
* @memberOf Document
@@ -4000,16 +4315,14 @@ Document.prototype.inspect = function(options) {
};
if (inspect.custom) {
- /*!
- * Avoid Node deprecation warning DEP0079
- */
-
+ // Avoid Node deprecation warning DEP0079
Document.prototype[inspect.custom] = Document.prototype.inspect;
}
/**
* Helper for console.log
*
+ * @return {String}
* @api public
* @method toString
* @memberOf Document
@@ -4031,7 +4344,7 @@ Document.prototype.toString = function() {
* document has an `_id`, in which case this function falls back to using
* `deepEqual()`.
*
- * @param {Document} doc a document to compare
+ * @param {Document} [doc] a document to compare. If falsy, will always return "false".
* @return {Boolean}
* @api public
* @memberOf Document
@@ -4056,8 +4369,9 @@ Document.prototype.equals = function(doc) {
/**
* Populates paths on an existing document.
*
- * ####Example:
+ * #### Example:
*
+ * // Given a document, `populate()` lets you pull in referenced docs
* await doc.populate([
* 'stories',
* { path: 'fans', sort: { name: -1 } }
@@ -4066,12 +4380,15 @@ Document.prototype.equals = function(doc) {
* doc.stories[0].title; // 'Casino Royale'
* doc.populated('fans'); // Array of ObjectIds
*
- * await doc.populate('fans', '-email');
- * doc.fans[0].email // not populated
+ * // If the referenced doc has been deleted, `populate()` will
+ * // remove that entry from the array.
+ * await Story.delete({ title: 'Casino Royale' });
+ * await doc.populate('stories'); // Empty array
*
- * await doc.populate('author fans', '-email');
- * doc.author.email // not populated
- * doc.fans[0].email // not populated
+ * // You can also pass additional query options to `populate()`,
+ * // like projections:
+ * await doc.populate('fans', '-email');
+ * doc.fans[0].email // undefined because of 2nd param `select`
*
* @param {String|Object|Array} path either the path to populate or an object specifying all parameters, or either an array of those
* @param {Object|String} [select] Field selection for the population query
@@ -4079,28 +4396,29 @@ Document.prototype.equals = function(doc) {
* @param {Object} [match] Conditions for the population query
* @param {Object} [options] Options for the population query (sort, etc)
* @param {String} [options.path=null] The path to populate.
+ * @param {string|PopulateOptions} [options.populate=null] Recursively populate paths in the populated documents. See [deep populate docs](/docs/populate.html#deep-populate).
* @param {boolean} [options.retainNullValues=false] by default, Mongoose removes null and undefined values from populated arrays. Use this option to make `populate()` retain `null` and `undefined` array entries.
* @param {boolean} [options.getters=false] if true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](/docs/schematypes.html#schematype-options).
* @param {boolean} [options.clone=false] When you do `BlogPost.find().populate('author')`, blog posts with the same author will share 1 copy of an `author` doc. Enable this option to make Mongoose clone populated docs before assigning them.
- * @param {Object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://docs.mongodb.com/manual/tutorial/query-documents/), or a function that returns a filter object.
+ * @param {Object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://www.mongodb.com/docs/manual/tutorial/query-documents/), or a function that returns a filter object.
* @param {Function} [options.transform=null] Function that Mongoose will call on every populated document that allows you to transform the populated document.
* @param {Object} [options.options=null] Additional options like `limit` and `lean`.
* @param {Function} [callback] Callback
- * @see population ./populate.html
+ * @see population /docs/populate.html
* @see Query#select #query_Query-select
- * @see Model.populate #model_Model.populate
+ * @see Model.populate #model_Model-populate
* @memberOf Document
* @instance
- * @return {Promise|null}
+ * @return {Promise|null} Returns a Promise if no `callback` is given.
* @api public
*/
Document.prototype.populate = function populate() {
const pop = {};
- const args = utils.args(arguments);
+ const args = [...arguments];
let fn;
- if (args.length > 0) {
+ if (args.length !== 0) {
if (typeof args[args.length - 1] === 'function') {
fn = args.pop();
}
@@ -4148,10 +4466,12 @@ Document.prototype.populate = function populate() {
* Gets all populated documents associated with this document.
*
* @api public
- * @return {Array<Document>} array of populated documents. Empty array if there are no populated documents associated with this document.
+ * @return {Document[]} array of populated documents. Empty array if there are no populated documents associated with this document.
* @memberOf Document
+ * @method $getPopulatedDocs
* @instance
*/
+
Document.prototype.$getPopulatedDocs = function $getPopulatedDocs() {
let keys = [];
if (this.$__.populated != null) {
@@ -4172,16 +4492,18 @@ Document.prototype.$getPopulatedDocs = function $getPopulatedDocs() {
/**
* Gets _id(s) used during population of the given `path`.
*
- * ####Example:
+ * #### Example:
*
- * Model.findOne().populate('author').exec(function (err, doc) {
- * console.log(doc.author.name) // Dr.Seuss
- * console.log(doc.populated('author')) // '5144cf8050f071d979c118a7'
- * })
+ * const doc = await Model.findOne().populate('author');
+ *
+ * console.log(doc.author.name); // Dr.Seuss
+ * console.log(doc.populated('author')); // '5144cf8050f071d979c118a7'
*
* If the path was not populated, returns `undefined`.
*
* @param {String} path
+ * @param {Any} [val]
+ * @param {Object} [options]
* @return {Array|ObjectId|Number|Buffer|String|undefined}
* @memberOf Document
* @instance
@@ -4229,12 +4551,61 @@ Document.prototype.populated = function(path, val, options) {
return val;
};
+/**
+ * Alias of [`.populated`](#document_Document-populated).
+ *
+ * @method $populated
+ * @memberOf Document
+ * @api public
+ */
+
Document.prototype.$populated = Document.prototype.populated;
+/**
+ * Throws an error if a given path is not populated
+ *
+ * #### Example:
+ *
+ * const doc = await Model.findOne().populate('author');
+ *
+ * doc.$assertPopulated('author'); // does not throw
+ * doc.$assertPopulated('other path'); // throws an error
+ *
+ * // Manually populate and assert in one call. The following does
+ * // `doc.$set({ likes })` before asserting.
+ * doc.$assertPopulated('likes', { likes });
+ *
+ *
+ * @param {String|String[]} path path or array of paths to check. `$assertPopulated` throws if any of the given paths is not populated.
+ * @param {Object} [values] optional values to `$set()`. Convenient if you want to manually populate a path and assert that the path was populated in 1 call.
+ * @return {Document} this
+ * @memberOf Document
+ * @method $assertPopulated
+ * @instance
+ * @api public
+ */
+
+Document.prototype.$assertPopulated = function $assertPopulated(path, values) {
+ if (Array.isArray(path)) {
+ path.forEach(p => this.$assertPopulated(p, values));
+ return this;
+ }
+
+ if (arguments.length > 1) {
+ this.$set(values);
+ }
+
+ if (!this.$populated(path)) {
+ throw new MongooseError(`Expected path "${path}" to be populated`);
+ }
+
+ return this;
+};
+
/**
* Takes a populated field and returns it to its unpopulated state.
*
- * ####Example:
+ * #### Example:
*
* Model.findOne().populate('author').exec(function (err, doc) {
* console.log(doc.author.name); // Dr.Seuss
@@ -4244,7 +4615,7 @@ Document.prototype.$populated = Document.prototype.populated;
*
* If the path was not provided, then all populated fields are returned to their unpopulated state.
*
- * @param {String} path
+ * @param {String|String[]} [path] Specific Path to depopulate. If unset, will depopulate all paths on the Document. Or multiple space-delimited paths.
* @return {Document} this
* @see Document.populate #document_Document-populate
* @api public
@@ -4259,7 +4630,7 @@ Document.prototype.depopulate = function(path) {
let populatedIds;
const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : [];
- const populated = get(this, '$__.populated', {});
+ const populated = this.$__ && this.$__.populated || {};
if (arguments.length === 0) {
// Depopulate all
@@ -4360,6 +4731,38 @@ Document.prototype.getChanges = function() {
return changes;
};
+/**
+ * Returns a copy of this document with a deep clone of `_doc` and `$__`.
+ *
+ * @return {Document} a copy of this document
+ * @api public
+ * @method $clone
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.$clone = function() {
+ const Model = this.constructor;
+ const clonedDoc = new Model();
+ clonedDoc.$isNew = this.$isNew;
+ if (this._doc) {
+ clonedDoc._doc = clone(this._doc);
+ }
+ if (this.$__) {
+ const Cache = this.$__.constructor;
+ const clonedCache = new Cache();
+ for (const key of Object.getOwnPropertyNames(this.$__)) {
+ if (key === 'activePaths') {
+ continue;
+ }
+ clonedCache[key] = clone(this.$__[key]);
+ }
+ Object.assign(clonedCache.activePaths, clone({ ...this.$__.activePaths }));
+ clonedDoc.$__ = clonedCache;
+ }
+ return clonedDoc;
+};
+
/*!
* Module exports.
*/
diff --git a/lib/drivers/browser/objectid.js b/lib/drivers/browser/objectid.js
index b1e603d714e..d847afe3b8e 100644
--- a/lib/drivers/browser/objectid.js
+++ b/lib/drivers/browser/objectid.js
@@ -9,8 +9,9 @@
const ObjectId = require('bson').ObjectID;
-/*!
+/**
* Getter for convenience with populate, see gh-6115
+ * @api private
*/
Object.defineProperty(ObjectId.prototype, '_id', {
diff --git a/lib/drivers/node-mongodb-native/ReadPreference.js b/lib/drivers/node-mongodb-native/ReadPreference.js
index 024ee181a55..bb999ebd684 100644
--- a/lib/drivers/node-mongodb-native/ReadPreference.js
+++ b/lib/drivers/node-mongodb-native/ReadPreference.js
@@ -7,7 +7,7 @@
const mongodb = require('mongodb');
const ReadPref = mongodb.ReadPreference;
-/*!
+/**
* Converts arguments to ReadPrefs the driver
* can understand.
*
diff --git a/lib/drivers/node-mongodb-native/collection.js b/lib/drivers/node-mongodb-native/collection.js
index 7fa28780d9b..8f874542574 100644
--- a/lib/drivers/node-mongodb-native/collection.js
+++ b/lib/drivers/node-mongodb-native/collection.js
@@ -8,9 +8,7 @@ const MongooseCollection = require('../../collection');
const MongooseError = require('../../error/mongooseError');
const Collection = require('mongodb').Collection;
const ObjectId = require('./objectid');
-const get = require('../../helpers/get');
const getConstructorName = require('../../helpers/getConstructorName');
-const sliced = require('sliced');
const stream = require('stream');
const util = require('util');
@@ -19,7 +17,7 @@ const util = require('util');
*
* All methods methods from the [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) driver are copied and wrapped in queue management.
*
- * @inherits Collection
+ * @inherits Collection https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html
* @api private
*/
@@ -36,7 +34,7 @@ function NativeCollection(name, conn, options) {
* Inherit from abstract Collection.
*/
-NativeCollection.prototype.__proto__ = MongooseCollection.prototype;
+Object.setPrototypeOf(NativeCollection.prototype, MongooseCollection.prototype);
/**
* Called when the connection opens.
@@ -45,11 +43,9 @@ NativeCollection.prototype.__proto__ = MongooseCollection.prototype;
*/
NativeCollection.prototype.onOpen = function() {
- const _this = this;
-
- _this.collection = _this.conn.db.collection(_this.name);
- MongooseCollection.prototype.onOpen.call(_this);
- return _this.collection;
+ this.collection = this.conn.db.collection(this.name);
+ MongooseCollection.prototype.onOpen.call(this);
+ return this.collection;
};
/**
@@ -62,22 +58,52 @@ NativeCollection.prototype.onClose = function(force) {
MongooseCollection.prototype.onClose.call(this, force);
};
+/**
+ * Helper to get the collection, in case `this.collection` isn't set yet.
+ * May happen if `bufferCommands` is false and created the model when
+ * Mongoose was disconnected.
+ *
+ * @api private
+ */
+
+NativeCollection.prototype._getCollection = function _getCollection() {
+ if (this.collection) {
+ return this.collection;
+ }
+ if (this.conn.db != null) {
+ this.collection = this.conn.db.collection(this.name);
+ return this.collection;
+ }
+ return null;
+};
+
/*!
* ignore
*/
const syncCollectionMethods = { watch: true, find: true, aggregate: true };
-/*!
+/**
* Copy the collection methods and make them subject to queues
+ * @param {Number|String} I
+ * @api private
*/
function iter(i) {
NativeCollection.prototype[i] = function() {
- const collection = this.collection;
+ const collection = this._getCollection();
const args = Array.from(arguments);
const _this = this;
- const debug = get(_this, 'conn.base.options.debug');
+ const globalDebug = _this &&
+ _this.conn &&
+ _this.conn.base &&
+ _this.conn.base.options &&
+ _this.conn.base.options.debug;
+ const connectionDebug = _this &&
+ _this.conn &&
+ _this.conn.options &&
+ _this.conn.options.debug;
+ const debug = connectionDebug == null ? globalDebug : connectionDebug;
const lastArg = arguments[arguments.length - 1];
const opId = new ObjectId();
@@ -85,7 +111,7 @@ function iter(i) {
if (this.conn.$wasForceClosed) {
const error = new MongooseError('Connection was force closed');
if (args.length > 0 &&
- typeof args[args.length - 1] === 'function') {
+ typeof args[args.length - 1] === 'function') {
args[args.length - 1](error);
return;
} else {
@@ -112,10 +138,23 @@ function iter(i) {
let _args = args;
let promise = null;
let timeout = null;
- if (syncCollectionMethods[i]) {
- this.addQueue(() => {
- lastArg.call(this, null, this[i].apply(this, _args.slice(0, _args.length - 1)));
- }, []);
+ if (syncCollectionMethods[i] && typeof lastArg === 'function') {
+ this.addQueue(i, _args);
+ callback = lastArg;
+ } else if (syncCollectionMethods[i]) {
+ promise = new this.Promise((resolve, reject) => {
+ callback = function collectionOperationCallback(err, res) {
+ if (timeout != null) {
+ clearTimeout(timeout);
+ }
+ if (err != null) {
+ return reject(err);
+ }
+ resolve(res);
+ };
+ _args = args.concat([callback]);
+ this.addQueue(i, _args);
+ });
} else if (typeof lastArg === 'function') {
callback = function collectionOperationCallback() {
if (timeout != null) {
@@ -172,8 +211,14 @@ function iter(i) {
if (debug) {
if (typeof debug === 'function') {
+ let argsToAdd = null;
+ if (typeof args[args.length - 1] == 'function') {
+ argsToAdd = args.slice(0, args.length - 1);
+ } else {
+ argsToAdd = args;
+ }
debug.apply(_this,
- [_this.name, i].concat(sliced(args, 0, args.length - 1)));
+ [_this.name, i].concat(argsToAdd));
} else if (debug instanceof stream.Writable) {
this.$printToStream(_this.name, i, args, debug);
} else {
@@ -294,8 +339,10 @@ NativeCollection.prototype.$format = function(arg, color, shell) {
return format(arg, false, color, shell);
};
-/*!
+/**
* Debug print helper
+ * @param {Any} representation
+ * @api private
*/
function inspectable(representation) {
@@ -369,7 +416,12 @@ function format(obj, sub, color, shell) {
formatDate(x, key, shell);
} else if (_constructorName === 'ClientSession') {
x[key] = inspectable('ClientSession("' +
- get(x[key], 'id.id.buffer', '').toString('hex') + '")');
+ (
+ x[key] &&
+ x[key].id &&
+ x[key].id.id &&
+ x[key].id.id.buffer || ''
+ ).toString('hex') + '")');
} else if (Array.isArray(x[key])) {
x[key] = x[key].map(map);
} else if (error != null) {
diff --git a/lib/drivers/node-mongodb-native/connection.js b/lib/drivers/node-mongodb-native/connection.js
index f976c55f11f..5c6976d92b4 100644
--- a/lib/drivers/node-mongodb-native/connection.js
+++ b/lib/drivers/node-mongodb-native/connection.js
@@ -32,7 +32,7 @@ NativeConnection.STATES = STATES;
* Inherits from Connection.
*/
-NativeConnection.prototype.__proto__ = MongooseConnection.prototype;
+Object.setPrototypeOf(NativeConnection.prototype, MongooseConnection.prototype);
/**
* Switches to a different database using the same connection pool.
@@ -71,6 +71,7 @@ NativeConnection.prototype.useDb = function(name, options) {
newConn._closeCalled = this._closeCalled;
newConn._hasOpened = this._hasOpened;
newConn._listening = false;
+ newConn._parent = this;
newConn.host = this.host;
newConn.port = this.port;
@@ -136,6 +137,17 @@ NativeConnection.prototype.doClose = function(force, fn) {
return this;
}
+ let skipCloseClient = false;
+ if (force != null && typeof force === 'object') {
+ skipCloseClient = force.skipCloseClient;
+ force = force.force;
+ }
+
+ if (skipCloseClient) {
+ immediate(() => fn());
+ return this;
+ }
+
this.client.close(force, (err, res) => {
// Defer because the driver will wait at least 1ms before finishing closing
// the pool, see https://github.com/mongodb-js/mongodb-core/blob/a8f8e4ce41936babc3b9112bf42d609779f03b39/lib/connection/pool.js#L1026-L1030.
@@ -146,6 +158,7 @@ NativeConnection.prototype.doClose = function(force, fn) {
return this;
};
+
/*!
* Module exports.
*/
diff --git a/lib/drivers/node-mongodb-native/index.js b/lib/drivers/node-mongodb-native/index.js
index 2ed9eb0a5bc..e6714679f50 100644
--- a/lib/drivers/node-mongodb-native/index.js
+++ b/lib/drivers/node-mongodb-native/index.js
@@ -9,4 +9,4 @@ exports.Collection = require('./collection');
exports.Decimal128 = require('./decimal128');
exports.ObjectId = require('./objectid');
exports.ReadPreference = require('./ReadPreference');
-exports.getConnection = () => require('./connection');
\ No newline at end of file
+exports.getConnection = () => require('./connection');
diff --git a/lib/error/browserMissingSchema.js b/lib/error/browserMissingSchema.js
index fe492d53ccc..3f271499d4d 100644
--- a/lib/error/browserMissingSchema.js
+++ b/lib/error/browserMissingSchema.js
@@ -8,7 +8,7 @@ const MongooseError = require('./');
class MissingSchemaError extends MongooseError {
- /*!
+ /**
* MissingSchema Error constructor.
*/
constructor() {
diff --git a/lib/error/cast.js b/lib/error/cast.js
index dc604b58211..c42e8216691 100644
--- a/lib/error/cast.js
+++ b/lib/error/cast.js
@@ -5,7 +5,6 @@
*/
const MongooseError = require('./mongooseError');
-const get = require('../helpers/get');
const util = require('util');
/**
@@ -24,7 +23,7 @@ class CastError extends MongooseError {
const stringValue = getStringValue(value);
const valueType = getValueType(value);
const messageFormat = getMessageFormat(schemaType);
- const msg = formatMessage(null, type, stringValue, path, messageFormat, valueType);
+ const msg = formatMessage(null, type, stringValue, path, messageFormat, valueType, reason);
super(msg);
this.init(type, value, path, reason, schemaType);
} else {
@@ -57,9 +56,10 @@ class CastError extends MongooseError {
this.valueType = getValueType(value);
}
- /*!
+ /**
* ignore
* @param {Readonly<CastError>} other
+ * @api private
*/
copy(other) {
this.messageFormat = other.messageFormat;
@@ -111,7 +111,9 @@ function getValueType(value) {
}
function getMessageFormat(schemaType) {
- const messageFormat = get(schemaType, 'options.cast', null);
+ const messageFormat = schemaType &&
+ schemaType.options &&
+ schemaType.options.cast || null;
if (typeof messageFormat === 'string') {
return messageFormat;
}
@@ -121,7 +123,7 @@ function getMessageFormat(schemaType) {
* ignore
*/
-function formatMessage(model, kind, stringValue, path, messageFormat, valueType) {
+function formatMessage(model, kind, stringValue, path, messageFormat, valueType, reason) {
if (messageFormat != null) {
let ret = messageFormat.
replace('{KIND}', kind).
@@ -139,6 +141,12 @@ function formatMessage(model, kind, stringValue, path, messageFormat, valueType)
if (model != null) {
ret += ' for model "' + model.modelName + '"';
}
+ if (reason != null &&
+ typeof reason.constructor === 'function' &&
+ reason.constructor.name !== 'AssertionError' &&
+ reason.constructor.name !== 'Error') {
+ ret += ' because of "' + reason.constructor.name + '"';
+ }
return ret;
}
}
diff --git a/lib/error/disconnected.js b/lib/error/disconnected.js
index 777a1def270..9e8c6125c14 100644
--- a/lib/error/disconnected.js
+++ b/lib/error/disconnected.js
@@ -16,10 +16,9 @@ class DisconnectedError extends MongooseError {
/**
* @param {String} connectionString
*/
- constructor(connectionString) {
- super('Ran out of retries trying to reconnect to "' +
- connectionString + '". Try setting `server.reconnectTries` and ' +
- '`server.reconnectInterval` to something higher.');
+ constructor(id, fnName) {
+ super('Connection ' + id +
+ ' was disconnected when calling `' + fnName + '()`');
}
}
diff --git a/lib/error/divergentArray.js b/lib/error/divergentArray.js
index ed86caf1731..7a8d1da5d34 100644
--- a/lib/error/divergentArray.js
+++ b/lib/error/divergentArray.js
@@ -8,9 +8,10 @@
const MongooseError = require('./');
class DivergentArrayError extends MongooseError {
- /*!
+ /**
* DivergentArrayError constructor.
* @param {Array<String>} paths
+ * @api private
*/
constructor(paths) {
const msg = 'For your own good, using `document.save()` to update an array '
diff --git a/lib/error/eachAsyncMultiError.js b/lib/error/eachAsyncMultiError.js
new file mode 100644
index 00000000000..9c04020312b
--- /dev/null
+++ b/lib/error/eachAsyncMultiError.js
@@ -0,0 +1,41 @@
+/*!
+ * Module dependencies.
+ */
+
+'use strict';
+
+const MongooseError = require('./');
+
+
+/**
+ * If `eachAsync()` is called with `continueOnError: true`, there can be
+ * multiple errors. This error class contains an `errors` property, which
+ * contains an array of all errors that occurred in `eachAsync()`.
+ *
+ * @api private
+ */
+
+class EachAsyncMultiError extends MongooseError {
+ /**
+ * @param {String} connectionString
+ */
+ constructor(errors) {
+ let preview = errors.map(e => e.message).join(', ');
+ if (preview.length > 50) {
+ preview = preview.slice(0, 50) + '...';
+ }
+ super(`eachAsync() finished with ${errors.length} errors: ${preview}`);
+
+ this.errors = errors;
+ }
+}
+
+Object.defineProperty(EachAsyncMultiError.prototype, 'name', {
+ value: 'EachAsyncMultiError'
+});
+
+/*!
+ * exports
+ */
+
+module.exports = EachAsyncMultiError;
diff --git a/lib/error/index.js b/lib/error/index.js
index ec4188d61c9..d2a93849f5a 100644
--- a/lib/error/index.js
+++ b/lib/error/index.js
@@ -4,12 +4,13 @@
* MongooseError constructor. MongooseError is the base class for all
* Mongoose-specific errors.
*
- * ####Example:
- * const Model = mongoose.model('Test', new Schema({ answer: Number }));
+ * #### Example:
+ *
+ * const Model = mongoose.model('Test', new mongoose.Schema({ answer: Number }));
* const doc = new Model({ answer: 'not a number' });
* const err = doc.validateSync();
*
- * err instanceof mongoose.Error; // true
+ * err instanceof mongoose.Error.ValidationError; // true
*
* @constructor Error
* @param {String} msg Error message
@@ -24,19 +25,19 @@ const MongooseError = require('./mongooseError');
*
* - `MongooseError`: general Mongoose error
* - `CastError`: Mongoose could not convert a value to the type defined in the schema path. May be in a `ValidationError` class' `errors` property.
- * - `DisconnectedError`: This [connection](connections.html) timed out in trying to reconnect to MongoDB and will not successfully reconnect to MongoDB unless you explicitly reconnect.
+ * - `DisconnectedError`: This [connection](/docs/connections.html) timed out in trying to reconnect to MongoDB and will not successfully reconnect to MongoDB unless you explicitly reconnect.
* - `DivergentArrayError`: You attempted to `save()` an array that was modified after you loaded it with a `$elemMatch` or similar projection
- * - `MissingSchemaError`: You tried to access a model with [`mongoose.model()`](api.html#mongoose_Mongoose-model) that was not defined
- * - `DocumentNotFoundError`: The document you tried to [`save()`](api.html#document_Document-save) was not found
+ * - `MissingSchemaError`: You tried to access a model with [`mongoose.model()`](mongoose.html#mongoose_Mongoose-model) that was not defined
+ * - `DocumentNotFoundError`: The document you tried to [`save()`](document.html#document_Document-save) was not found
* - `ValidatorError`: error from an individual schema path's validator
- * - `ValidationError`: error returned from [`validate()`](api.html#document_Document-validate) or [`validateSync()`](api.html#document_Document-validateSync). Contains zero or more `ValidatorError` instances in `.errors` property.
+ * - `ValidationError`: error returned from [`validate()`](document.html#document_Document-validate) or [`validateSync()`](document.html#document_Document-validateSync). Contains zero or more `ValidatorError` instances in `.errors` property.
* - `MissingSchemaError`: You called `mongoose.Document()` without a schema
- * - `ObjectExpectedError`: Thrown when you set a nested path to a non-object value with [strict mode set](guide.html#strict).
+ * - `ObjectExpectedError`: Thrown when you set a nested path to a non-object value with [strict mode set](/docs/guide.html#strict).
* - `ObjectParameterError`: Thrown when you pass a non-object value to a function which expects an object as a paramter
- * - `OverwriteModelError`: Thrown when you call [`mongoose.model()`](api.html#mongoose_Mongoose-model) to re-define a model that was already defined.
- * - `ParallelSaveError`: Thrown when you call [`save()`](api.html#model_Model-save) on a document when the same document instance is already saving.
- * - `StrictModeError`: Thrown when you set a path that isn't the schema and [strict mode](guide.html#strict) is set to `throw`.
- * - `VersionError`: Thrown when the [document is out of sync](guide.html#versionKey)
+ * - `OverwriteModelError`: Thrown when you call [`mongoose.model()`](mongoose.html#mongoose_Mongoose-model) to re-define a model that was already defined.
+ * - `ParallelSaveError`: Thrown when you call [`save()`](model.html#model_Model-save) on a document when the same document instance is already saving.
+ * - `StrictModeError`: Thrown when you set a path that isn't the schema and [strict mode](/docs/guide.html#strict) is set to `throw`.
+ * - `VersionError`: Thrown when the [document is out of sync](/docs/guide.html#versionKey)
*
* @api public
* @property {String} name
@@ -56,7 +57,7 @@ module.exports = exports = MongooseError;
* @see Error.messages #error_messages_MongooseError-messages
* @api public
* @memberOf Error
- * @static messages
+ * @static
*/
MongooseError.messages = require('./messages');
@@ -73,7 +74,7 @@ MongooseError.Messages = MongooseError.messages;
*
* @api public
* @memberOf Error
- * @static DocumentNotFoundError
+ * @static
*/
MongooseError.DocumentNotFoundError = require('./notFound');
@@ -84,7 +85,7 @@ MongooseError.DocumentNotFoundError = require('./notFound');
*
* @api public
* @memberOf Error
- * @static CastError
+ * @static
*/
MongooseError.CastError = require('./cast');
@@ -96,7 +97,7 @@ MongooseError.CastError = require('./cast');
*
* @api public
* @memberOf Error
- * @static ValidationError
+ * @static
*/
MongooseError.ValidationError = require('./validation');
@@ -105,7 +106,7 @@ MongooseError.ValidationError = require('./validation');
* A `ValidationError` has a hash of `errors` that contain individual
* `ValidatorError` instances.
*
- * ####Example:
+ * #### Example:
*
* const schema = Schema({ name: { type: String, required: true } });
* const Model = mongoose.model('Test', schema);
@@ -131,7 +132,7 @@ MongooseError.ValidationError = require('./validation');
*
* @api public
* @memberOf Error
- * @static ValidatorError
+ * @static
*/
MongooseError.ValidatorError = require('./validator');
@@ -143,7 +144,7 @@ MongooseError.ValidatorError = require('./validator');
*
* @api public
* @memberOf Error
- * @static VersionError
+ * @static
*/
MongooseError.VersionError = require('./version');
@@ -155,7 +156,7 @@ MongooseError.VersionError = require('./version');
*
* @api public
* @memberOf Error
- * @static ParallelSaveError
+ * @static
*/
MongooseError.ParallelSaveError = require('./parallelSave');
@@ -166,7 +167,7 @@ MongooseError.ParallelSaveError = require('./parallelSave');
*
* @api public
* @memberOf Error
- * @static OverwriteModelError
+ * @static
*/
MongooseError.OverwriteModelError = require('./overwriteModel');
@@ -176,30 +177,52 @@ MongooseError.OverwriteModelError = require('./overwriteModel');
*
* @api public
* @memberOf Error
- * @static MissingSchemaError
+ * @static
*/
MongooseError.MissingSchemaError = require('./missingSchema');
+/**
+ * Thrown when the MongoDB Node driver can't connect to a valid server
+ * to send an operation to.
+ *
+ * @api public
+ * @memberOf Error
+ * @static
+ */
+
+MongooseError.MongooseServerSelectionError = require('./serverSelection');
+
/**
* An instance of this error will be returned if you used an array projection
* and then modified the array in an unsafe way.
*
* @api public
* @memberOf Error
- * @static DivergentArrayError
+ * @static
*/
MongooseError.DivergentArrayError = require('./divergentArray');
/**
- * Thrown when your try to pass values to model contrtuctor that
+ * Thrown when your try to pass values to model constructor that
* were not specified in schema or change immutable properties when
* `strict` mode is `"throw"`
*
* @api public
* @memberOf Error
- * @static StrictModeError
+ * @static
*/
MongooseError.StrictModeError = require('./strict');
+
+/**
+ * An instance of this error class will be returned when mongoose failed to
+ * populate with a path that is not existing.
+ *
+ * @api public
+ * @memberOf Error
+ * @static
+ */
+
+MongooseError.StrictPopulateError = require('./strictPopulate');
diff --git a/lib/error/messages.js b/lib/error/messages.js
index ac0294a39a4..b750d5d5ec2 100644
--- a/lib/error/messages.js
+++ b/lib/error/messages.js
@@ -16,8 +16,8 @@
*
* Click the "show code" link below to see all defaults.
*
- * @static messages
- * @receiver MongooseError
+ * @static
+ * @memberOf MongooseError
* @api public
*/
diff --git a/lib/error/missingSchema.js b/lib/error/missingSchema.js
index ca306b7e611..50c81054a90 100644
--- a/lib/error/missingSchema.js
+++ b/lib/error/missingSchema.js
@@ -8,9 +8,10 @@
const MongooseError = require('./');
class MissingSchemaError extends MongooseError {
- /*!
+ /**
* MissingSchema Error constructor.
* @param {String} name
+ * @api private
*/
constructor(name) {
const msg = 'Schema hasn\'t been registered for model "' + name + '".\n'
diff --git a/lib/error/notFound.js b/lib/error/notFound.js
index 0e8386b8a25..e1064bb89d9 100644
--- a/lib/error/notFound.js
+++ b/lib/error/notFound.js
@@ -8,8 +8,9 @@ const MongooseError = require('./');
const util = require('util');
class DocumentNotFoundError extends MongooseError {
- /*!
+ /**
* OverwriteModel Error constructor.
+ * @api private
*/
constructor(filter, model, numAffected, result) {
let msg;
diff --git a/lib/error/objectParameter.js b/lib/error/objectParameter.js
index 5881a481aea..295582e2484 100644
--- a/lib/error/objectParameter.js
+++ b/lib/error/objectParameter.js
@@ -18,7 +18,7 @@ class ObjectParameterError extends MongooseError {
*/
constructor(value, paramName, fnName) {
super('Parameter "' + paramName + '" to ' + fnName +
- '() must be an object, got ' + value.toString());
+ '() must be an object, got "' + value.toString() + '" (type ' + typeof value + ')');
}
}
diff --git a/lib/error/overwriteModel.js b/lib/error/overwriteModel.js
index d509e19551e..1ff180b0498 100644
--- a/lib/error/overwriteModel.js
+++ b/lib/error/overwriteModel.js
@@ -9,9 +9,10 @@ const MongooseError = require('./');
class OverwriteModelError extends MongooseError {
- /*!
+ /**
* OverwriteModel Error constructor.
* @param {String} name
+ * @api private
*/
constructor(name) {
super('Cannot overwrite `' + name + '` model once compiled.');
diff --git a/lib/error/parallelValidate.js b/lib/error/parallelValidate.js
index 18697b6bc91..477954dc870 100644
--- a/lib/error/parallelValidate.js
+++ b/lib/error/parallelValidate.js
@@ -28,4 +28,4 @@ Object.defineProperty(ParallelValidateError.prototype, 'name', {
* exports
*/
-module.exports = ParallelValidateError;
\ No newline at end of file
+module.exports = ParallelValidateError;
diff --git a/lib/error/serverSelection.js b/lib/error/serverSelection.js
index 162e2fceb43..743eb96a31b 100644
--- a/lib/error/serverSelection.js
+++ b/lib/error/serverSelection.js
@@ -16,7 +16,7 @@ const isSSLError = require('../helpers/topology/isSSLError');
const atlasMessage = 'Could not connect to any servers in your MongoDB Atlas cluster. ' +
'One common reason is that you\'re trying to access the database from ' +
'an IP that isn\'t whitelisted. Make sure your current IP address is on your Atlas ' +
- 'cluster\'s IP whitelist: https://docs.atlas.mongodb.com/security-whitelist/';
+ 'cluster\'s IP whitelist: https://www.mongodb.com/docs/atlas/security-whitelist/';
const sslMessage = 'Mongoose is connecting with SSL enabled, but the server is ' +
'not accepting SSL connections. Please ensure that the MongoDB server you are ' +
diff --git a/lib/error/setOptionError.js b/lib/error/setOptionError.js
new file mode 100644
index 00000000000..b38a0d30244
--- /dev/null
+++ b/lib/error/setOptionError.js
@@ -0,0 +1,101 @@
+/*!
+ * Module requirements
+ */
+
+'use strict';
+
+const MongooseError = require('./mongooseError');
+const util = require('util');
+const combinePathErrors = require('../helpers/error/combinePathErrors');
+
+class SetOptionError extends MongooseError {
+ /**
+ * Mongoose.set Error
+ *
+ * @api private
+ * @inherits MongooseError
+ */
+ constructor() {
+ super('');
+
+ this.errors = {};
+ }
+
+ /**
+ * Console.log helper
+ */
+ toString() {
+ return combinePathErrors(this);
+ }
+
+ /**
+ * inspect helper
+ * @api private
+ */
+ inspect() {
+ return Object.assign(new Error(this.message), this);
+ }
+
+ /**
+ * add message
+ * @param {String} key
+ * @param {String|Error} error
+ * @api private
+ */
+ addError(key, error) {
+ if (error instanceof SetOptionError) {
+ const { errors } = error;
+ for (const optionKey of Object.keys(errors)) {
+ this.addError(optionKey, errors[optionKey]);
+ }
+
+ return;
+ }
+
+ this.errors[key] = error;
+ this.message = combinePathErrors(this);
+ }
+}
+
+
+if (util.inspect.custom) {
+ // Avoid Node deprecation warning DEP0079
+ SetOptionError.prototype[util.inspect.custom] = SetOptionError.prototype.inspect;
+}
+
+/**
+ * Helper for JSON.stringify
+ * Ensure `name` and `message` show up in toJSON output re: gh-9847
+ * @api private
+ */
+Object.defineProperty(SetOptionError.prototype, 'toJSON', {
+ enumerable: false,
+ writable: false,
+ configurable: true,
+ value: function() {
+ return Object.assign({}, this, { name: this.name, message: this.message });
+ }
+});
+
+
+Object.defineProperty(SetOptionError.prototype, 'name', {
+ value: 'SetOptionError'
+});
+
+class SetOptionInnerError extends MongooseError {
+ /**
+ * Error for the "errors" array in "SetOptionError" with consistent message
+ * @param {String} key
+ */
+ constructor(key) {
+ super(`"${key}" is not a valid option to set`);
+ }
+}
+
+SetOptionError.SetOptionInnerError = SetOptionInnerError;
+
+/*!
+ * Module exports
+ */
+
+module.exports = SetOptionError;
diff --git a/lib/error/strictPopulate.js b/lib/error/strictPopulate.js
new file mode 100644
index 00000000000..f7addfa5287
--- /dev/null
+++ b/lib/error/strictPopulate.js
@@ -0,0 +1,29 @@
+/*!
+ * Module dependencies.
+ */
+
+'use strict';
+
+const MongooseError = require('./');
+
+class StrictPopulateError extends MongooseError {
+ /**
+ * Strict mode error constructor
+ *
+ * @param {String} path
+ * @param {String} [msg]
+ * @inherits MongooseError
+ * @api private
+ */
+ constructor(path, msg) {
+ msg = msg || 'Cannot populate path `' + path + '` because it is not in your schema. ' + 'Set the `strictPopulate` option to false to override.';
+ super(msg);
+ this.path = path;
+ }
+}
+
+Object.defineProperty(StrictPopulateError.prototype, 'name', {
+ value: 'StrictPopulateError'
+});
+
+module.exports = StrictPopulateError;
diff --git a/lib/error/syncIndexes.js b/lib/error/syncIndexes.js
new file mode 100644
index 00000000000..54f345ba701
--- /dev/null
+++ b/lib/error/syncIndexes.js
@@ -0,0 +1,30 @@
+'use strict';
+
+/*!
+ * Module dependencies.
+ */
+
+const MongooseError = require('./mongooseError');
+
+/**
+ * SyncIndexes Error constructor.
+ *
+ * @param {String} message
+ * @param {String} errorsMap
+ * @inherits MongooseError
+ * @api private
+ */
+
+class SyncIndexesError extends MongooseError {
+ constructor(message, errorsMap) {
+ super(message);
+ this.errors = errorsMap;
+ }
+}
+
+Object.defineProperty(SyncIndexesError.prototype, 'name', {
+ value: 'SyncIndexesError'
+});
+
+
+module.exports = SyncIndexesError;
diff --git a/lib/error/validation.js b/lib/error/validation.js
index 5ca993a341d..5e222e980f9 100644
--- a/lib/error/validation.js
+++ b/lib/error/validation.js
@@ -7,6 +7,7 @@
const MongooseError = require('./mongooseError');
const getConstructorName = require('../helpers/getConstructorName');
const util = require('util');
+const combinePathErrors = require('../helpers/error/combinePathErrors');
class ValidationError extends MongooseError {
/**
@@ -38,37 +39,48 @@ class ValidationError extends MongooseError {
* Console.log helper
*/
toString() {
- return this.name + ': ' + _generateMessage(this);
+ return this.name + ': ' + combinePathErrors(this);
}
- /*!
+ /**
* inspect helper
+ * @api private
*/
inspect() {
return Object.assign(new Error(this.message), this);
}
- /*!
+ /**
* add message
+ * @param {String} path
+ * @param {String|Error} error
+ * @api private
*/
addError(path, error) {
+ if (error instanceof ValidationError) {
+ const { errors } = error;
+ for (const errorPath of Object.keys(errors)) {
+ this.addError(`${path}.${errorPath}`, errors[errorPath]);
+ }
+
+ return;
+ }
+
this.errors[path] = error;
- this.message = this._message + ': ' + _generateMessage(this);
+ this.message = this._message + ': ' + combinePathErrors(this);
}
}
if (util.inspect.custom) {
- /*!
- * Avoid Node deprecation warning DEP0079
- */
-
+ // Avoid Node deprecation warning DEP0079
ValidationError.prototype[util.inspect.custom] = ValidationError.prototype.inspect;
}
-/*!
+/**
* Helper for JSON.stringify
* Ensure `name` and `message` show up in toJSON output re: gh-9847
+ * @api private
*/
Object.defineProperty(ValidationError.prototype, 'toJSON', {
enumerable: false,
@@ -84,27 +96,6 @@ Object.defineProperty(ValidationError.prototype, 'name', {
value: 'ValidationError'
});
-/*!
- * ignore
- */
-
-function _generateMessage(err) {
- const keys = Object.keys(err.errors || {});
- const len = keys.length;
- const msgs = [];
- let key;
-
- for (let i = 0; i < len; ++i) {
- key = keys[i];
- if (err === err.errors[key]) {
- continue;
- }
- msgs.push(key + ': ' + err.errors[key].message);
- }
-
- return msgs.join(', ');
-}
-
/*!
* Module exports
*/
diff --git a/lib/error/validator.js b/lib/error/validator.js
index f880e2b5820..4ca7316d7bf 100644
--- a/lib/error/validator.js
+++ b/lib/error/validator.js
@@ -12,15 +12,16 @@ class ValidatorError extends MongooseError {
* Schema validator error
*
* @param {Object} properties
+ * @param {Document} doc
* @api private
*/
- constructor(properties) {
+ constructor(properties, doc) {
let msg = properties.message;
if (!msg) {
msg = MongooseError.messages.general.default;
}
- const message = formatMessage(msg, properties);
+ const message = formatMessage(msg, properties, doc);
super(message);
properties = Object.assign({}, properties, { message: message });
@@ -31,16 +32,18 @@ class ValidatorError extends MongooseError {
this.reason = properties.reason;
}
- /*!
+ /**
* toString helper
* TODO remove? This defaults to `${this.name}: ${this.message}`
+ * @api private
*/
toString() {
return this.message;
}
- /*!
+ /**
* Ensure `name` and `message` show up in toJSON output re: gh-9296
+ * @api private
*/
toJSON() {
@@ -53,9 +56,10 @@ Object.defineProperty(ValidatorError.prototype, 'name', {
value: 'ValidatorError'
});
-/*!
+/**
* The object used to define this validator. Not enumerable to hide
* it from `require('util').inspect()` output re: gh-3925
+ * @api private
*/
Object.defineProperty(ValidatorError.prototype, 'properties', {
@@ -67,13 +71,14 @@ Object.defineProperty(ValidatorError.prototype, 'properties', {
// Exposed for testing
ValidatorError.prototype.formatMessage = formatMessage;
-/*!
+/**
* Formats error messages
+ * @api private
*/
-function formatMessage(msg, properties) {
+function formatMessage(msg, properties, doc) {
if (typeof msg === 'function') {
- return msg(properties);
+ return msg(properties, doc);
}
const propertyNames = Object.keys(properties);
diff --git a/lib/helpers/aggregate/prepareDiscriminatorPipeline.js b/lib/helpers/aggregate/prepareDiscriminatorPipeline.js
new file mode 100644
index 00000000000..f720cfb6c28
--- /dev/null
+++ b/lib/helpers/aggregate/prepareDiscriminatorPipeline.js
@@ -0,0 +1,39 @@
+'use strict';
+
+module.exports = function prepareDiscriminatorPipeline(pipeline, schema, prefix) {
+ const discriminatorMapping = schema && schema.discriminatorMapping;
+ prefix = prefix || '';
+
+ if (discriminatorMapping && !discriminatorMapping.isRoot) {
+ const originalPipeline = pipeline;
+ const filterKey = (prefix.length > 0 ? prefix + '.' : prefix) + discriminatorMapping.key;
+ const discriminatorValue = discriminatorMapping.value;
+
+ // If the first pipeline stage is a match and it doesn't specify a `__t`
+ // key, add the discriminator key to it. This allows for potential
+ // aggregation query optimizations not to be disturbed by this feature.
+ if (originalPipeline[0] != null &&
+ originalPipeline[0].$match &&
+ (originalPipeline[0].$match[filterKey] === undefined || originalPipeline[0].$match[filterKey] === discriminatorValue)) {
+ originalPipeline[0].$match[filterKey] = discriminatorValue;
+ // `originalPipeline` is a ref, so there's no need for
+ // aggregate._pipeline = originalPipeline
+ } else if (originalPipeline[0] != null && originalPipeline[0].$geoNear) {
+ originalPipeline[0].$geoNear.query =
+ originalPipeline[0].$geoNear.query || {};
+ originalPipeline[0].$geoNear.query[filterKey] = discriminatorValue;
+ } else if (originalPipeline[0] != null && originalPipeline[0].$search) {
+ if (originalPipeline[1] && originalPipeline[1].$match != null) {
+ originalPipeline[1].$match[filterKey] = originalPipeline[1].$match[filterKey] || discriminatorValue;
+ } else {
+ const match = {};
+ match[filterKey] = discriminatorValue;
+ originalPipeline.splice(1, 0, { $match: match });
+ }
+ } else {
+ const match = {};
+ match[filterKey] = discriminatorValue;
+ originalPipeline.unshift({ $match: match });
+ }
+ }
+};
diff --git a/lib/helpers/aggregate/stringifyFunctionOperators.js b/lib/helpers/aggregate/stringifyFunctionOperators.js
index 124418efd68..1f5d1e395c4 100644
--- a/lib/helpers/aggregate/stringifyFunctionOperators.js
+++ b/lib/helpers/aggregate/stringifyFunctionOperators.js
@@ -47,4 +47,4 @@ function handleAccumulator(operator) {
operator.$accumulator[key] = String(operator.$accumulator[key]);
}
}
-}
\ No newline at end of file
+}
diff --git a/lib/helpers/arrayDepth.js b/lib/helpers/arrayDepth.js
index e55de7ffbea..16b92c13afb 100644
--- a/lib/helpers/arrayDepth.js
+++ b/lib/helpers/arrayDepth.js
@@ -30,4 +30,4 @@ function arrayDepth(arr) {
res.max = res.max + 1;
return res;
-}
\ No newline at end of file
+}
diff --git a/lib/helpers/clone.js b/lib/helpers/clone.js
index 4cdd9ecac5f..f7ee5d8d2a8 100644
--- a/lib/helpers/clone.js
+++ b/lib/helpers/clone.js
@@ -1,7 +1,5 @@
'use strict';
-
-const cloneRegExp = require('regexp-clone');
const Decimal = require('../types/decimal128');
const ObjectId = require('../types/objectid');
const specialProperties = require('./specialProperties');
@@ -14,7 +12,7 @@ const trustedSymbol = require('./query/trusted').trustedSymbol;
const utils = require('../utils');
-/*!
+/**
* Object clone with Mongoose natives support.
*
* If options.minimize is true, creates a minimal data object. Empty objects and undefined values will not be cloned. This makes the data payload sent to MongoDB as small as possible.
@@ -34,7 +32,7 @@ function clone(obj, options, isArrayChild) {
}
if (Array.isArray(obj)) {
- return cloneArray(obj, options);
+ return cloneArray(utils.isMongooseArray(obj) ? obj.__array : obj, options);
}
if (isMongooseObject(obj)) {
@@ -43,23 +41,34 @@ function clone(obj, options, isArrayChild) {
if (options && options._skipSingleNestedGetters && obj.$isSingleNested) {
options = Object.assign({}, options, { getters: false });
}
+ const isSingleNested = obj.$isSingleNested;
if (utils.isPOJO(obj) && obj.$__ != null && obj._doc != null) {
return obj._doc;
}
+ let ret;
if (options && options.json && typeof obj.toJSON === 'function') {
- return obj.toJSON(options);
+ ret = obj.toJSON(options);
+ } else {
+ ret = obj.toObject(options);
+ }
+
+ if (options && options.minimize && isSingleNested && Object.keys(ret).length === 0) {
+ return undefined;
}
- return obj.toObject(options);
+
+ return ret;
}
- if (obj.constructor) {
- switch (getFunctionName(obj.constructor)) {
+ const objConstructor = obj.constructor;
+
+ if (objConstructor) {
+ switch (getFunctionName(objConstructor)) {
case 'Object':
return cloneObject(obj, options, isArrayChild);
case 'Date':
- return new obj.constructor(+obj);
+ return new objConstructor(+obj);
case 'RegExp':
return cloneRegExp(obj);
default:
@@ -68,7 +77,7 @@ function clone(obj, options, isArrayChild) {
}
}
- if (obj instanceof ObjectId) {
+ if (isBsonType(obj, 'ObjectID')) {
return new ObjectId(obj.id);
}
@@ -79,12 +88,12 @@ function clone(obj, options, isArrayChild) {
return Decimal.fromString(obj.toString());
}
- if (!obj.constructor && isObject(obj)) {
- // object created with Object.create(null)
+ // object created with Object.create(null)
+ if (!objConstructor && isObject(obj)) {
return cloneObject(obj, options, isArrayChild);
}
- if (obj[symbols.schemaTypeSymbol]) {
+ if (typeof obj === 'object' && obj[symbols.schemaTypeSymbol]) {
return obj.clone();
}
@@ -95,7 +104,7 @@ function clone(obj, options, isArrayChild) {
return obj;
}
- if (obj.valueOf != null) {
+ if (typeof obj.valueOf === 'function') {
return obj.valueOf();
}
@@ -109,28 +118,38 @@ module.exports = clone;
function cloneObject(obj, options, isArrayChild) {
const minimize = options && options.minimize;
+ const omitUndefined = options && options.omitUndefined;
+ const seen = options && options._seen;
const ret = {};
let hasKeys;
- if (obj[trustedSymbol]) {
+ if (seen && seen.has(obj)) {
+ return seen.get(obj);
+ } else if (seen) {
+ seen.set(obj, ret);
+ }
+ if (trustedSymbol in obj) {
ret[trustedSymbol] = obj[trustedSymbol];
}
- for (const k of Object.keys(obj)) {
- if (specialProperties.has(k)) {
+ let i = 0;
+ let key = '';
+ const keys = Object.keys(obj);
+ const len = keys.length;
+
+ for (i = 0; i < len; ++i) {
+ if (specialProperties.has(key = keys[i])) {
continue;
}
// Don't pass `isArrayChild` down
- const val = clone(obj[k], options);
-
- if (!minimize || (typeof val !== 'undefined')) {
- if (minimize === false && typeof val === 'undefined') {
- delete ret[k];
- } else {
- hasKeys || (hasKeys = true);
- ret[k] = val;
- }
+ const val = clone(obj[key], options, false);
+
+ if ((minimize === false || omitUndefined) && typeof val === 'undefined') {
+ delete ret[key];
+ } else if (minimize !== true || (typeof val !== 'undefined')) {
+ hasKeys || (hasKeys = true);
+ ret[key] = val;
}
}
@@ -138,11 +157,21 @@ function cloneObject(obj, options, isArrayChild) {
}
function cloneArray(arr, options) {
- const ret = [];
-
- for (const item of arr) {
- ret.push(clone(item, options, true));
+ let i = 0;
+ const len = arr.length;
+ const ret = new Array(len);
+ for (i = 0; i < len; ++i) {
+ ret[i] = clone(arr[i], options, true);
}
return ret;
-}
\ No newline at end of file
+}
+
+function cloneRegExp(regexp) {
+ const ret = new RegExp(regexp.source, regexp.flags);
+
+ if (ret.lastIndex !== regexp.lastIndex) {
+ ret.lastIndex = regexp.lastIndex;
+ }
+ return ret;
+}
diff --git a/lib/helpers/common.js b/lib/helpers/common.js
index a191b8ae29b..ec7524da4c9 100644
--- a/lib/helpers/common.js
+++ b/lib/helpers/common.js
@@ -5,9 +5,10 @@
*/
const Binary = require('../driver').get().Binary;
-const Decimal128 = require('../types/decimal128');
-const ObjectId = require('../types/objectid');
+const isBsonType = require('./isBsonType');
const isMongooseObject = require('./isMongooseObject');
+const MongooseError = require('../error');
+const util = require('util');
exports.flatten = flatten;
exports.modifiedPaths = modifiedPaths;
@@ -19,7 +20,7 @@ exports.modifiedPaths = modifiedPaths;
function flatten(update, path, options, schema) {
let keys;
if (update && isMongooseObject(update) && !Buffer.isBuffer(update)) {
- keys = Object.keys(update.toObject({ transform: false, virtuals: false }));
+ keys = Object.keys(update.toObject({ transform: false, virtuals: false }) || {});
} else {
keys = Object.keys(update || {});
}
@@ -68,7 +69,25 @@ function flatten(update, path, options, schema) {
* ignore
*/
-function modifiedPaths(update, path, result) {
+function modifiedPaths(update, path, result, recursion = null) {
+ if (update == null || typeof update !== 'object') {
+ return;
+ }
+
+ if (recursion == null) {
+ recursion = {
+ raw: { update, path },
+ trace: new WeakSet()
+ };
+ }
+
+ if (recursion.trace.has(update)) {
+ throw new MongooseError(`a circular reference in the update value, updateValue:
+${util.inspect(recursion.raw.update, { showHidden: false, depth: 1 })}
+updatePath: '${recursion.raw.path}'`);
+ }
+ recursion.trace.add(update);
+
const keys = Object.keys(update || {});
const numKeys = keys.length;
result = result || {};
@@ -80,21 +99,14 @@ function modifiedPaths(update, path, result) {
const _path = path + key;
result[_path] = true;
- if (_path.indexOf('.') !== -1) {
- const sp = _path.split('.');
- let cur = sp[0];
- for (let i = 1; i < sp.length; ++i) {
- result[cur] = true;
- cur += '.' + sp[i];
- }
- }
- if (isMongooseObject(val) && !Buffer.isBuffer(val)) {
+ if (!Buffer.isBuffer(val) && isMongooseObject(val)) {
val = val.toObject({ transform: false, virtuals: false });
}
if (shouldFlatten(val)) {
- modifiedPaths(val, path + key, result);
+ modifiedPaths(val, path + key, result, recursion);
}
}
+ recursion.trace.delete(update);
return result;
}
@@ -105,11 +117,11 @@ function modifiedPaths(update, path, result) {
function shouldFlatten(val) {
return val &&
- typeof val === 'object' &&
- !(val instanceof Date) &&
- !(val instanceof ObjectId) &&
- (!Array.isArray(val) || val.length > 0) &&
- !(val instanceof Buffer) &&
- !(val instanceof Decimal128) &&
- !(val instanceof Binary);
+ typeof val === 'object' &&
+ !(val instanceof Date) &&
+ !isBsonType(val, 'ObjectID') &&
+ (!Array.isArray(val) || val.length !== 0) &&
+ !(val instanceof Buffer) &&
+ !isBsonType(val, 'Decimal128') &&
+ !(val instanceof Binary);
}
diff --git a/lib/helpers/cursor/eachAsync.js b/lib/helpers/cursor/eachAsync.js
index 2ef0346e0d4..e3f6f8a24f4 100644
--- a/lib/helpers/cursor/eachAsync.js
+++ b/lib/helpers/cursor/eachAsync.js
@@ -4,6 +4,7 @@
* Module dependencies.
*/
+const EachAsyncMultiError = require('../../error/eachAsyncMultiError');
const immediate = require('../immediate');
const promiseOrCallback = require('../promiseOrCallback');
@@ -15,6 +16,9 @@ const promiseOrCallback = require('../promiseOrCallback');
* @param {Function} next the thunk to call to get the next document
* @param {Function} fn
* @param {Object} options
+ * @param {Number} [options.batchSize=null] if set, Mongoose will call `fn` with an array of at most `batchSize` documents, instead of a single document
+ * @param {Number} [options.parallel=1] maximum number of `fn` calls that Mongoose will run in parallel
+ * @param {AbortSignal} [options.signal] allow cancelling this eachAsync(). Once the abort signal is fired, `eachAsync()` will immediately fulfill the returned promise (or call the callback) and not fetch any more documents.
* @param {Function} [callback] executed when all docs have been processed
* @return {Promise}
* @api public
@@ -24,106 +28,155 @@ const promiseOrCallback = require('../promiseOrCallback');
module.exports = function eachAsync(next, fn, options, callback) {
const parallel = options.parallel || 1;
const batchSize = options.batchSize;
+ const signal = options.signal;
+ const continueOnError = options.continueOnError;
+ const aggregatedErrors = [];
const enqueue = asyncQueue();
+ let aborted = false;
+
return promiseOrCallback(callback, cb => {
+ if (signal != null) {
+ if (signal.aborted) {
+ return cb(null);
+ }
+
+ signal.addEventListener('abort', () => {
+ aborted = true;
+ return cb(null);
+ }, { once: true });
+ }
+
if (batchSize != null) {
if (typeof batchSize !== 'number') {
throw new TypeError('batchSize must be a number');
- }
- if (batchSize < 1) {
+ } else if (!Number.isInteger(batchSize)) {
+ throw new TypeError('batchSize must be an integer');
+ } else if (batchSize < 1) {
throw new TypeError('batchSize must be at least 1');
}
- if (batchSize !== Math.floor(batchSize)) {
- throw new TypeError('batchSize must be a positive integer');
- }
}
iterate(cb);
});
function iterate(finalCallback) {
- let drained = false;
let handleResultsInProgress = 0;
let currentDocumentIndex = 0;
- let documentsBatch = [];
let error = null;
for (let i = 0; i < parallel; ++i) {
- enqueue(fetch);
+ enqueue(createFetch());
}
- function fetch(done) {
- if (drained || error) {
- return done();
- }
+ function createFetch() {
+ let documentsBatch = [];
+ let drained = false;
- next(function(err, doc) {
- if (drained || error != null) {
- return done();
- }
- if (err != null) {
- error = err;
- finalCallback(err);
+ return fetch;
+
+ function fetch(done) {
+ if (drained || aborted) {
return done();
- }
- if (doc == null) {
- drained = true;
- if (handleResultsInProgress <= 0) {
- finalCallback(null);
- } else if (batchSize != null && documentsBatch.length) {
- handleNextResult(documentsBatch, currentDocumentIndex++, handleNextResultCallBack);
- }
+ } else if (error) {
return done();
}
- ++handleResultsInProgress;
-
- // Kick off the subsequent `next()` before handling the result, but
- // make sure we know that we still have a result to handle re: #8422
- immediate(() => done());
-
- if (batchSize != null) {
- documentsBatch.push(doc);
- }
+ next(function(err, doc) {
+ if (error != null) {
+ return done();
+ }
+ if (err != null) {
+ if (err.name === 'MongoCursorExhaustedError') {
+ // We may end up calling `next()` multiple times on an exhausted
+ // cursor, which leads to an error. In case cursor is exhausted,
+ // just treat it as if the cursor returned no document, which is
+ // how a cursor indicates it is exhausted.
+ doc = null;
+ } else if (continueOnError) {
+ aggregatedErrors.push(err);
+ } else {
+ error = err;
+ finalCallback(err);
+ return done();
+ }
+ }
+ if (doc == null) {
+ drained = true;
+ if (handleResultsInProgress <= 0) {
+ const finalErr = continueOnError ?
+ createEachAsyncMultiError(aggregatedErrors) :
+ error;
+
+ finalCallback(finalErr);
+ } else if (batchSize && documentsBatch.length) {
+ handleNextResult(documentsBatch, currentDocumentIndex++, handleNextResultCallBack);
+ }
+ return done();
+ }
- // If the current documents size is less than the provided patch size don't process the documents yet
- if (batchSize != null && documentsBatch.length !== batchSize) {
- setTimeout(() => enqueue(fetch), 0);
- return;
- }
+ ++handleResultsInProgress;
- const docsToProcess = batchSize != null ? documentsBatch : doc;
+ // Kick off the subsequent `next()` before handling the result, but
+ // make sure we know that we still have a result to handle re: #8422
+ immediate(() => done());
- function handleNextResultCallBack(err) {
- if (batchSize != null) {
- handleResultsInProgress -= documentsBatch.length;
- documentsBatch = [];
- } else {
- --handleResultsInProgress;
+ if (batchSize) {
+ documentsBatch.push(doc);
}
- if (err != null) {
- error = err;
- return finalCallback(err);
- }
- if (drained && handleResultsInProgress <= 0) {
- return finalCallback(null);
+
+ // If the current documents size is less than the provided batch size don't process the documents yet
+ if (batchSize && documentsBatch.length !== batchSize) {
+ immediate(() => enqueue(fetch));
+ return;
}
- setTimeout(() => enqueue(fetch), 0);
- }
+ const docsToProcess = batchSize ? documentsBatch : doc;
+
+ function handleNextResultCallBack(err) {
+ if (batchSize) {
+ handleResultsInProgress -= documentsBatch.length;
+ documentsBatch = [];
+ } else {
+ --handleResultsInProgress;
+ }
+ if (err != null) {
+ if (continueOnError) {
+ aggregatedErrors.push(err);
+ } else {
+ error = err;
+ return finalCallback(err);
+ }
+ }
+ if ((drained || aborted) && handleResultsInProgress <= 0) {
+ const finalErr = continueOnError ?
+ createEachAsyncMultiError(aggregatedErrors) :
+ error;
+ return finalCallback(finalErr);
+ }
+
+ immediate(() => enqueue(fetch));
+ }
- handleNextResult(docsToProcess, currentDocumentIndex++, handleNextResultCallBack);
- });
+ handleNextResult(docsToProcess, currentDocumentIndex++, handleNextResultCallBack);
+ });
+ }
}
}
function handleNextResult(doc, i, callback) {
- const promise = fn(doc, i);
- if (promise && typeof promise.then === 'function') {
- promise.then(
+ let maybePromise;
+ try {
+ maybePromise = fn(doc, i);
+ } catch (err) {
+ return callback(err);
+ }
+ if (maybePromise && typeof maybePromise.then === 'function') {
+ maybePromise.then(
function() { callback(null); },
- function(error) { callback(error || new Error('`eachAsync()` promise rejected without error')); });
+ function(error) {
+ callback(error || new Error('`eachAsync()` promise rejected without error'));
+ });
} else {
callback(null);
}
@@ -139,7 +192,10 @@ function asyncQueue() {
let id = 0;
return function enqueue(fn) {
- if (_queue.length === 0 && inProgress == null) {
+ if (
+ inProgress === null &&
+ _queue.length === 0
+ ) {
inProgress = id++;
return fn(_step);
}
@@ -147,11 +203,20 @@ function asyncQueue() {
};
function _step() {
- inProgress = null;
- if (_queue.length > 0) {
+ if (_queue.length !== 0) {
inProgress = id++;
const fn = _queue.shift();
fn(_step);
+ } else {
+ inProgress = null;
}
}
}
+
+function createEachAsyncMultiError(aggregatedErrors) {
+ if (aggregatedErrors.length === 0) {
+ return null;
+ }
+
+ return new EachAsyncMultiError(aggregatedErrors);
+}
diff --git a/lib/helpers/discriminator/areDiscriminatorValuesEqual.js b/lib/helpers/discriminator/areDiscriminatorValuesEqual.js
index 87b2408e6db..68ea9390698 100644
--- a/lib/helpers/discriminator/areDiscriminatorValuesEqual.js
+++ b/lib/helpers/discriminator/areDiscriminatorValuesEqual.js
@@ -1,6 +1,6 @@
'use strict';
-const ObjectId = require('../../types/objectid');
+const isBsonType = require('../isBsonType');
module.exports = function areDiscriminatorValuesEqual(a, b) {
if (typeof a === 'string' && typeof b === 'string') {
@@ -9,8 +9,8 @@ module.exports = function areDiscriminatorValuesEqual(a, b) {
if (typeof a === 'number' && typeof b === 'number') {
return a === b;
}
- if (a instanceof ObjectId && b instanceof ObjectId) {
+ if (isBsonType(a, 'ObjectID') && isBsonType(b, 'ObjectID')) {
return a.toString() === b.toString();
}
return false;
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/discriminator/checkEmbeddedDiscriminatorKeyProjection.js b/lib/helpers/discriminator/checkEmbeddedDiscriminatorKeyProjection.js
index 755de88f1ff..4eb56de4feb 100644
--- a/lib/helpers/discriminator/checkEmbeddedDiscriminatorKeyProjection.js
+++ b/lib/helpers/discriminator/checkEmbeddedDiscriminatorKeyProjection.js
@@ -9,4 +9,4 @@ module.exports = function checkEmbeddedDiscriminatorKeyProjection(userProjection
addedPaths[0] === _discriminatorKey) {
selected.splice(selected.indexOf(_discriminatorKey), 1);
}
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/discriminator/getConstructor.js b/lib/helpers/discriminator/getConstructor.js
index 728da3b25ff..7a821c5c99f 100644
--- a/lib/helpers/discriminator/getConstructor.js
+++ b/lib/helpers/discriminator/getConstructor.js
@@ -2,8 +2,9 @@
const getDiscriminatorByValue = require('./getDiscriminatorByValue');
-/*!
+/**
* Find the correct constructor, taking into account discriminators
+ * @api private
*/
module.exports = function getConstructor(Constructor, value) {
@@ -22,4 +23,4 @@ module.exports = function getConstructor(Constructor, value) {
}
return Constructor;
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/discriminator/getDiscriminatorByValue.js b/lib/helpers/discriminator/getDiscriminatorByValue.js
index 099aaefddfd..af7605b39d4 100644
--- a/lib/helpers/discriminator/getDiscriminatorByValue.js
+++ b/lib/helpers/discriminator/getDiscriminatorByValue.js
@@ -2,12 +2,13 @@
const areDiscriminatorValuesEqual = require('./areDiscriminatorValuesEqual');
-/*!
-* returns discriminator by discriminatorMapping.value
-*
-* @param {Model} model
-* @param {string} value
-*/
+/**
+ * returns discriminator by discriminatorMapping.value
+ *
+ * @param {Object} discriminators
+ * @param {string} value
+ * @api private
+ */
module.exports = function getDiscriminatorByValue(discriminators, value) {
if (discriminators == null) {
@@ -24,4 +25,4 @@ module.exports = function getDiscriminatorByValue(discriminators, value) {
}
}
return null;
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/discriminator/getSchemaDiscriminatorByValue.js b/lib/helpers/discriminator/getSchemaDiscriminatorByValue.js
index b29fb6521e1..a04e17c1b1b 100644
--- a/lib/helpers/discriminator/getSchemaDiscriminatorByValue.js
+++ b/lib/helpers/discriminator/getSchemaDiscriminatorByValue.js
@@ -2,12 +2,13 @@
const areDiscriminatorValuesEqual = require('./areDiscriminatorValuesEqual');
-/*!
-* returns discriminator by discriminatorMapping.value
-*
-* @param {Schema} schema
-* @param {string} value
-*/
+/**
+ * returns discriminator by discriminatorMapping.value
+ *
+ * @param {Schema} schema
+ * @param {string} value
+ * @api private
+ */
module.exports = function getSchemaDiscriminatorByValue(schema, value) {
if (schema == null || schema.discriminators == null) {
@@ -23,4 +24,4 @@ module.exports = function getSchemaDiscriminatorByValue(schema, value) {
}
}
return null;
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/discriminator/mergeDiscriminatorSchema.js b/lib/helpers/discriminator/mergeDiscriminatorSchema.js
new file mode 100644
index 00000000000..bbe4503f15c
--- /dev/null
+++ b/lib/helpers/discriminator/mergeDiscriminatorSchema.js
@@ -0,0 +1,64 @@
+'use strict';
+const schemaMerge = require('../schema/merge');
+const specialProperties = require('../../helpers/specialProperties');
+const isBsonType = require('../../helpers/isBsonType');
+const ObjectId = require('../../types/objectid');
+const isObject = require('../../helpers/isObject');
+/**
+ * Merges `from` into `to` without overwriting existing properties.
+ *
+ * @param {Object} to
+ * @param {Object} from
+ * @param {String} [path]
+ * @api private
+ */
+
+module.exports = function mergeDiscriminatorSchema(to, from, path) {
+ const keys = Object.keys(from);
+ let i = 0;
+ const len = keys.length;
+ let key;
+
+ path = path || '';
+
+ while (i < len) {
+ key = keys[i++];
+ if (key === 'discriminators' || key === 'base' || key === '_applyDiscriminators') {
+ continue;
+ }
+ if (path === 'tree' && from != null && from.instanceOfSchema) {
+ continue;
+ }
+ if (specialProperties.has(key)) {
+ continue;
+ }
+ if (to[key] == null) {
+ to[key] = from[key];
+ } else if (isObject(from[key])) {
+ if (!isObject(to[key])) {
+ to[key] = {};
+ }
+ if (from[key] != null) {
+ // Skip merging schemas if we're creating a discriminator schema and
+ // base schema has a given path as a single nested but discriminator schema
+ // has the path as a document array, or vice versa (gh-9534)
+ if ((from[key].$isSingleNested && to[key].$isMongooseDocumentArray) ||
+ (from[key].$isMongooseDocumentArray && to[key].$isSingleNested) ||
+ (from[key].$isMongooseDocumentArrayElement && to[key].$isMongooseDocumentArrayElement)) {
+ continue;
+ } else if (from[key].instanceOfSchema) {
+ if (to[key].instanceOfSchema) {
+ schemaMerge(to[key], from[key].clone(), true);
+ } else {
+ to[key] = from[key].clone();
+ }
+ continue;
+ } else if (isBsonType(from[key], 'ObjectID')) {
+ to[key] = new ObjectId(from[key]);
+ continue;
+ }
+ }
+ mergeDiscriminatorSchema(to[key], from[key], path ? path + '.' + key : key);
+ }
+ }
+};
diff --git a/lib/helpers/document/applyDefaults.js b/lib/helpers/document/applyDefaults.js
new file mode 100644
index 00000000000..631d5151e5b
--- /dev/null
+++ b/lib/helpers/document/applyDefaults.js
@@ -0,0 +1,128 @@
+'use strict';
+
+const isNestedProjection = require('../projection/isNestedProjection');
+
+module.exports = function applyDefaults(doc, fields, exclude, hasIncludedChildren, isBeforeSetters, pathsToSkip) {
+ const paths = Object.keys(doc.$__schema.paths);
+ const plen = paths.length;
+
+ for (let i = 0; i < plen; ++i) {
+ let def;
+ let curPath = '';
+ const p = paths[i];
+
+ if (p === '_id' && doc.$__.skipId) {
+ continue;
+ }
+
+ const type = doc.$__schema.paths[p];
+ const path = type.splitPath();
+ const len = path.length;
+ let included = false;
+ let doc_ = doc._doc;
+ for (let j = 0; j < len; ++j) {
+ if (doc_ == null) {
+ break;
+ }
+
+ const piece = path[j];
+ curPath += (!curPath.length ? '' : '.') + piece;
+
+ if (exclude === true) {
+ if (curPath in fields) {
+ break;
+ }
+ } else if (exclude === false && fields && !included) {
+ const hasSubpaths = type.$isSingleNested || type.$isMongooseDocumentArray;
+ if ((curPath in fields && !isNestedProjection(fields[curPath])) || (j === len - 1 && hasSubpaths && hasIncludedChildren != null && hasIncludedChildren[curPath])) {
+ included = true;
+ } else if (hasIncludedChildren != null && !hasIncludedChildren[curPath]) {
+ break;
+ }
+ }
+
+ if (j === len - 1) {
+ if (doc_[piece] !== void 0) {
+ break;
+ }
+
+ if (isBeforeSetters != null) {
+ if (typeof type.defaultValue === 'function') {
+ if (!type.defaultValue.$runBeforeSetters && isBeforeSetters) {
+ break;
+ }
+ if (type.defaultValue.$runBeforeSetters && !isBeforeSetters) {
+ break;
+ }
+ } else if (!isBeforeSetters) {
+ // Non-function defaults should always run **before** setters
+ continue;
+ }
+ }
+
+ if (pathsToSkip && pathsToSkip[curPath]) {
+ break;
+ }
+
+ if (fields && exclude !== null) {
+ if (exclude === true) {
+ // apply defaults to all non-excluded fields
+ if (p in fields) {
+ continue;
+ }
+
+ try {
+ def = type.getDefault(doc, false);
+ } catch (err) {
+ doc.invalidate(p, err);
+ break;
+ }
+
+ if (typeof def !== 'undefined') {
+ doc_[piece] = def;
+ applyChangeTracking(doc, p);
+ }
+ } else if (included) {
+ // selected field
+ try {
+ def = type.getDefault(doc, false);
+ } catch (err) {
+ doc.invalidate(p, err);
+ break;
+ }
+
+ if (typeof def !== 'undefined') {
+ doc_[piece] = def;
+ applyChangeTracking(doc, p);
+ }
+ }
+ } else {
+ try {
+ def = type.getDefault(doc, false);
+ } catch (err) {
+ doc.invalidate(p, err);
+ break;
+ }
+
+ if (typeof def !== 'undefined') {
+ doc_[piece] = def;
+ applyChangeTracking(doc, p);
+ }
+ }
+ } else {
+ doc_ = doc_[piece];
+ }
+ }
+ }
+};
+
+/*!
+ * ignore
+ */
+
+function applyChangeTracking(doc, fullPath) {
+ doc.$__.activePaths.default(fullPath);
+ if (doc.$isSubdocument && doc.$isSingleNested && doc.$parent() != null) {
+ doc.$parent().$__.activePaths.default(doc.$__pathRelativeToParent(fullPath));
+ }
+}
diff --git a/lib/helpers/document/cleanModifiedSubpaths.js b/lib/helpers/document/cleanModifiedSubpaths.js
index 98de475364f..43c225e4fd2 100644
--- a/lib/helpers/document/cleanModifiedSubpaths.js
+++ b/lib/helpers/document/cleanModifiedSubpaths.js
@@ -12,7 +12,8 @@ module.exports = function cleanModifiedSubpaths(doc, path, options) {
if (!doc) {
return deleted;
}
- for (const modifiedPath of Object.keys(doc.$__.activePaths.states.modify)) {
+
+ for (const modifiedPath of Object.keys(doc.$__.activePaths.getStatePaths('modify'))) {
if (skipDocArrays) {
const schemaType = doc.$__schema.path(modifiedPath);
if (schemaType && schemaType.$isMongooseDocumentArray) {
@@ -20,8 +21,14 @@ module.exports = function cleanModifiedSubpaths(doc, path, options) {
}
}
if (modifiedPath.startsWith(path + '.')) {
- delete doc.$__.activePaths.states.modify[modifiedPath];
+ doc.$__.activePaths.clearPath(modifiedPath);
++deleted;
+
+ if (doc.$isSubdocument) {
+ const owner = doc.ownerDocument();
+ const fullPath = doc.$__fullPath(modifiedPath);
+ owner.$__.activePaths.clearPath(fullPath);
+ }
}
}
return deleted;
diff --git a/lib/helpers/document/compile.js b/lib/helpers/document/compile.js
index 9981e8f9f01..248211bdb66 100644
--- a/lib/helpers/document/compile.js
+++ b/lib/helpers/document/compile.js
@@ -1,7 +1,6 @@
'use strict';
const documentSchemaSymbol = require('../../helpers/symbols').documentSchemaSymbol;
-const get = require('../../helpers/get');
const internalToObjectOptions = require('../../options').internalToObjectOptions;
const utils = require('../../utils');
@@ -9,6 +8,8 @@ let Document;
const getSymbol = require('../../helpers/symbols').getSymbol;
const scopeSymbol = require('../../helpers/symbols').scopeSymbol;
+const isPOJO = utils.isPOJO;
+
/*!
* exports
*/
@@ -16,32 +17,58 @@ const scopeSymbol = require('../../helpers/symbols').scopeSymbol;
exports.compile = compile;
exports.defineKey = defineKey;
-/*!
+const _isEmptyOptions = Object.freeze({
+ minimize: true,
+ virtuals: false,
+ getters: false,
+ transform: false
+});
+
+const noDottedPathGetOptions = Object.freeze({
+ noDottedPath: true
+});
+
+/**
* Compiles schemas.
+ * @param {Object} tree
+ * @param {Any} proto
+ * @param {String} prefix
+ * @param {Object} options
+ * @api private
*/
function compile(tree, proto, prefix, options) {
Document = Document || require('../../document');
+ const typeKey = options.typeKey;
for (const key of Object.keys(tree)) {
const limb = tree[key];
- const hasSubprops = utils.isPOJO(limb) && Object.keys(limb).length &&
- (!limb[options.typeKey] || (options.typeKey === 'type' && limb.type.type));
+ const hasSubprops = isPOJO(limb) &&
+ Object.keys(limb).length > 0 &&
+ (!limb[typeKey] || (typeKey === 'type' && isPOJO(limb.type) && limb.type.type));
const subprops = hasSubprops ? limb : null;
defineKey({ prop: key, subprops: subprops, prototype: proto, prefix: prefix, options: options });
}
}
-/*!
+/**
* Defines the accessor named prop on the incoming prototype.
+ * @param {Object} options
+ * @param {String} options.prop
+ * @param {Boolean} options.subprops
+ * @param {Any} options.prototype
+ * @param {String} [options.prefix]
+ * @param {Object} options.options
+ * @api private
*/
function defineKey({ prop, subprops, prototype, prefix, options }) {
Document = Document || require('../../document');
const path = (prefix ? prefix + '.' : '') + prop;
prefix = prefix || '';
+ const useGetOptions = prefix ? Object.freeze({}) : noDottedPathGetOptions;
if (subprops) {
Object.defineProperty(prototype, prop, {
@@ -89,7 +116,11 @@ function defineKey({ prop, subprops, prototype, prefix, options }) {
writable: false,
value: function() {
return utils.clone(_this.get(path, null, {
- virtuals: get(this, 'schema.options.toObject.virtuals', null)
+ virtuals: this &&
+ this.schema &&
+ this.schema.options &&
+ this.schema.options.toObject &&
+ this.schema.options.toObject.virtuals || null
}));
}
});
@@ -100,7 +131,7 @@ function defineKey({ prop, subprops, prototype, prefix, options }) {
writable: false,
value: function() {
return _this.get(path, null, {
- virtuals: get(this, 'schema.options.toObject.virtuals', null)
+ virtuals: this && this.schema && this.schema.options && this.schema.options.toObject && this.schema.options.toObject.virtuals || null
});
}
});
@@ -111,7 +142,7 @@ function defineKey({ prop, subprops, prototype, prefix, options }) {
writable: false,
value: function() {
return _this.get(path, null, {
- virtuals: get(_this, 'schema.options.toJSON.virtuals', null)
+ virtuals: this && this.schema && this.schema.options && this.schema.options.toJSON && this.schema.options.toJSON.virtuals || null
});
}
});
@@ -123,12 +154,6 @@ function defineKey({ prop, subprops, prototype, prefix, options }) {
value: true
});
- const _isEmptyOptions = Object.freeze({
- minimize: true,
- virtuals: false,
- getters: false,
- transform: false
- });
Object.defineProperty(nested, '$isEmpty', {
enumerable: false,
configurable: true,
@@ -168,7 +193,12 @@ function defineKey({ prop, subprops, prototype, prefix, options }) {
enumerable: true,
configurable: true,
get: function() {
- return this[getSymbol].call(this.$__[scopeSymbol] || this, path);
+ return this[getSymbol].call(
+ this.$__[scopeSymbol] || this,
+ path,
+ null,
+ useGetOptions
+ );
},
set: function(v) {
this.$set.call(this.$__[scopeSymbol] || this, path, v);
diff --git a/lib/helpers/document/getEmbeddedDiscriminatorPath.js b/lib/helpers/document/getEmbeddedDiscriminatorPath.js
index 3f4d22a216b..ecbedabc3fa 100644
--- a/lib/helpers/document/getEmbeddedDiscriminatorPath.js
+++ b/lib/helpers/document/getEmbeddedDiscriminatorPath.js
@@ -3,9 +3,13 @@
const get = require('../get');
const getSchemaDiscriminatorByValue = require('../discriminator/getSchemaDiscriminatorByValue');
-/*!
+/**
* Like `schema.path()`, except with a document, because impossible to
* determine path type without knowing the embedded discriminator key.
+ * @param {Document} doc
+ * @param {String} path
+ * @param {Object} [options]
+ * @api private
*/
module.exports = function getEmbeddedDiscriminatorPath(doc, path, options) {
diff --git a/lib/helpers/document/handleSpreadDoc.js b/lib/helpers/document/handleSpreadDoc.js
index d3075e41365..5f715171751 100644
--- a/lib/helpers/document/handleSpreadDoc.js
+++ b/lib/helpers/document/handleSpreadDoc.js
@@ -2,16 +2,34 @@
const utils = require('../../utils');
+const keysToSkip = new Set(['__index', '__parentArray', '_doc']);
+
/**
* Using spread operator on a Mongoose document gives you a
* POJO that has a tendency to cause infinite recursion. So
* we use this function on `set()` to prevent that.
*/
-module.exports = function handleSpreadDoc(v) {
+module.exports = function handleSpreadDoc(v, includeExtraKeys) {
if (utils.isPOJO(v) && v.$__ != null && v._doc != null) {
+ if (includeExtraKeys) {
+ const extraKeys = {};
+ for (const key of Object.keys(v)) {
+ if (typeof key === 'symbol') {
+ continue;
+ }
+ if (key[0] === '$') {
+ continue;
+ }
+ if (keysToSkip.has(key)) {
+ continue;
+ }
+ extraKeys[key] = v[key];
+ }
+ return { ...v._doc, ...extraKeys };
+ }
return v._doc;
}
return v;
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/each.js b/lib/helpers/each.js
index fe7006931fd..9ce5cc6211f 100644
--- a/lib/helpers/each.js
+++ b/lib/helpers/each.js
@@ -22,4 +22,4 @@ module.exports = function each(arr, cb, done) {
}
});
}
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/error/combinePathErrors.js b/lib/helpers/error/combinePathErrors.js
new file mode 100644
index 00000000000..841dbc0aa6e
--- /dev/null
+++ b/lib/helpers/error/combinePathErrors.js
@@ -0,0 +1,22 @@
+'use strict';
+
+/*!
+ * ignore
+ */
+
+module.exports = function combinePathErrors(err) {
+ const keys = Object.keys(err.errors || {});
+ const len = keys.length;
+ const msgs = [];
+ let key;
+
+ for (let i = 0; i < len; ++i) {
+ key = keys[i];
+ if (err === err.errors[key]) {
+ continue;
+ }
+ msgs.push(key + ': ' + err.errors[key].message);
+ }
+
+ return msgs.join(', ');
+};
diff --git a/lib/helpers/firstKey.js b/lib/helpers/firstKey.js
new file mode 100644
index 00000000000..4495d2a8860
--- /dev/null
+++ b/lib/helpers/firstKey.js
@@ -0,0 +1,8 @@
+'use strict';
+
+module.exports = function firstKey(obj) {
+ if (obj == null) {
+ return null;
+ }
+ return Object.keys(obj)[0];
+};
diff --git a/lib/helpers/get.js b/lib/helpers/get.js
index dbab30601dc..08c2b849fcd 100644
--- a/lib/helpers/get.js
+++ b/lib/helpers/get.js
@@ -1,8 +1,9 @@
'use strict';
-/*!
+/**
* Simplified lodash.get to work around the annoying null quirk. See:
* https://github.com/lodash/lodash/issues/3659
+ * @api private
*/
module.exports = function get(obj, path, def) {
@@ -61,4 +62,4 @@ function getProperty(obj, prop) {
return obj.get(prop);
}
return obj[prop];
-}
\ No newline at end of file
+}
diff --git a/lib/helpers/getConstructorName.js b/lib/helpers/getConstructorName.js
index a4e19249318..5ebe7bb029b 100644
--- a/lib/helpers/getConstructorName.js
+++ b/lib/helpers/getConstructorName.js
@@ -1,7 +1,8 @@
'use strict';
-/*!
+/**
* If `val` is an object, returns constructor name, if possible. Otherwise returns undefined.
+ * @api private
*/
module.exports = function getConstructorName(val) {
@@ -12,4 +13,4 @@ module.exports = function getConstructorName(val) {
return void 0;
}
return val.constructor.name;
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/getDefaultBulkwriteResult.js b/lib/helpers/getDefaultBulkwriteResult.js
index 7d10f174864..855cf7a1bd8 100644
--- a/lib/helpers/getDefaultBulkwriteResult.js
+++ b/lib/helpers/getDefaultBulkwriteResult.js
@@ -24,4 +24,4 @@ function getDefaultBulkwriteResult() {
};
}
-module.exports = getDefaultBulkwriteResult;
\ No newline at end of file
+module.exports = getDefaultBulkwriteResult;
diff --git a/lib/helpers/getFunctionName.js b/lib/helpers/getFunctionName.js
index 87a2c690ab1..d1f3a5a6afe 100644
--- a/lib/helpers/getFunctionName.js
+++ b/lib/helpers/getFunctionName.js
@@ -1,8 +1,10 @@
'use strict';
+const functionNameRE = /^function\s*([^\s(]+)/;
+
module.exports = function(fn) {
- if (fn.name) {
- return fn.name;
- }
- return (fn.toString().trim().match(/^function\s*([^\s(]+)/) || [])[1];
+ return (
+ fn.name ||
+ (fn.toString().trim().match(functionNameRE) || [])[1]
+ );
};
diff --git a/lib/helpers/immediate.js b/lib/helpers/immediate.js
index 0a1c26c32f6..73085f7d420 100644
--- a/lib/helpers/immediate.js
+++ b/lib/helpers/immediate.js
@@ -7,7 +7,9 @@
'use strict';
-const nextTick = process.nextTick.bind(process);
+const nextTick = typeof process !== 'undefined' && typeof process.nextTick === 'function' ?
+ process.nextTick.bind(process) :
+ cb => setTimeout(cb, 0); // Fallback for browser build
module.exports = function immediate(cb) {
return nextTick(cb);
diff --git a/lib/helpers/indexes/applySchemaCollation.js b/lib/helpers/indexes/applySchemaCollation.js
new file mode 100644
index 00000000000..93a97a48bda
--- /dev/null
+++ b/lib/helpers/indexes/applySchemaCollation.js
@@ -0,0 +1,13 @@
+'use strict';
+
+const isTextIndex = require('./isTextIndex');
+
+module.exports = function applySchemaCollation(indexKeys, indexOptions, schemaOptions) {
+ if (isTextIndex(indexKeys)) {
+ return;
+ }
+
+ if (schemaOptions.hasOwnProperty('collation') && !indexOptions.hasOwnProperty('collation')) {
+ indexOptions.collation = schemaOptions.collation;
+ }
+};
diff --git a/lib/helpers/indexes/decorateDiscriminatorIndexOptions.js b/lib/helpers/indexes/decorateDiscriminatorIndexOptions.js
new file mode 100644
index 00000000000..7b7f51a1858
--- /dev/null
+++ b/lib/helpers/indexes/decorateDiscriminatorIndexOptions.js
@@ -0,0 +1,14 @@
+'use strict';
+
+module.exports = function decorateDiscriminatorIndexOptions(schema, indexOptions) {
+ // If the model is a discriminator and has an index, add a
+ // partialFilterExpression by default so the index will only apply
+ // to that discriminator.
+ const discriminatorName = schema.discriminatorMapping && schema.discriminatorMapping.value;
+ if (discriminatorName && !('sparse' in indexOptions)) {
+ const discriminatorKey = schema.options.discriminatorKey;
+ indexOptions.partialFilterExpression = indexOptions.partialFilterExpression || {};
+ indexOptions.partialFilterExpression[discriminatorKey] = discriminatorName;
+ }
+ return indexOptions;
+};
diff --git a/lib/helpers/indexes/getRelatedIndexes.js b/lib/helpers/indexes/getRelatedIndexes.js
new file mode 100644
index 00000000000..42d5798c623
--- /dev/null
+++ b/lib/helpers/indexes/getRelatedIndexes.js
@@ -0,0 +1,59 @@
+'use strict';
+
+function getRelatedSchemaIndexes(model, schemaIndexes) {
+ return getRelatedIndexes({
+ baseModelName: model.baseModelName,
+ discriminatorMapping: model.schema.discriminatorMapping,
+ indexes: schemaIndexes,
+ indexesType: 'schema'
+ });
+}
+
+function getRelatedDBIndexes(model, dbIndexes) {
+ return getRelatedIndexes({
+ baseModelName: model.baseModelName,
+ discriminatorMapping: model.schema.discriminatorMapping,
+ indexes: dbIndexes,
+ indexesType: 'db'
+ });
+}
+
+module.exports = {
+ getRelatedSchemaIndexes,
+ getRelatedDBIndexes
+};
+
+function getRelatedIndexes({
+ baseModelName,
+ discriminatorMapping,
+ indexes,
+ indexesType
+}) {
+ const discriminatorKey = discriminatorMapping && discriminatorMapping.key;
+ const discriminatorValue = discriminatorMapping && discriminatorMapping.value;
+
+ if (!discriminatorKey) {
+ return indexes;
+ }
+
+ const isChildDiscriminatorModel = Boolean(baseModelName);
+ if (isChildDiscriminatorModel) {
+ return indexes.filter(index => {
+ const partialFilterExpression = getPartialFilterExpression(index, indexesType);
+ return partialFilterExpression && partialFilterExpression[discriminatorKey] === discriminatorValue;
+ });
+ }
+
+ return indexes.filter(index => {
+ const partialFilterExpression = getPartialFilterExpression(index, indexesType);
+ return !partialFilterExpression || !partialFilterExpression[discriminatorKey];
+ });
+}
+
+function getPartialFilterExpression(index, indexesType) {
+ if (indexesType === 'schema') {
+ const options = index[1];
+ return options && options.partialFilterExpression;
+ }
+ return index.partialFilterExpression;
+}
diff --git a/lib/helpers/indexes/isDefaultIdIndex.js b/lib/helpers/indexes/isDefaultIdIndex.js
index c975dcfc389..56d74346c6b 100644
--- a/lib/helpers/indexes/isDefaultIdIndex.js
+++ b/lib/helpers/indexes/isDefaultIdIndex.js
@@ -15,4 +15,4 @@ module.exports = function isDefaultIdIndex(index) {
const key = get(index, 'key', {});
return Object.keys(key).length === 1 && key.hasOwnProperty('_id');
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/indexes/isIndexEqual.js b/lib/helpers/indexes/isIndexEqual.js
index 12e5c3d08f9..73504123600 100644
--- a/lib/helpers/indexes/isIndexEqual.js
+++ b/lib/helpers/indexes/isIndexEqual.js
@@ -6,13 +6,13 @@ const utils = require('../../utils');
* Given a Mongoose index definition (key + options objects) and a MongoDB server
* index definition, determine if the two indexes are equal.
*
- * @param {Object} key the Mongoose index spec
+ * @param {Object} schemaIndexKeysObject the Mongoose index spec
* @param {Object} options the Mongoose index definition's options
* @param {Object} dbIndex the index in MongoDB as returned by `listIndexes()`
* @api private
*/
-module.exports = function isIndexEqual(key, options, dbIndex) {
+module.exports = function isIndexEqual(schemaIndexKeysObject, options, dbIndex) {
// Special case: text indexes have a special format in the db. For example,
// `{ name: 'text' }` becomes:
// {
@@ -30,11 +30,11 @@ module.exports = function isIndexEqual(key, options, dbIndex) {
delete dbIndex.key._fts;
delete dbIndex.key._ftsx;
const weights = { ...dbIndex.weights, ...dbIndex.key };
- if (Object.keys(weights).length !== Object.keys(key).length) {
+ if (Object.keys(weights).length !== Object.keys(schemaIndexKeysObject).length) {
return false;
}
for (const prop of Object.keys(weights)) {
- if (!(prop in key)) {
+ if (!(prop in schemaIndexKeysObject)) {
return false;
}
const weight = weights[prop];
@@ -78,7 +78,7 @@ module.exports = function isIndexEqual(key, options, dbIndex) {
}
}
- const schemaIndexKeys = Object.keys(key);
+ const schemaIndexKeys = Object.keys(schemaIndexKeysObject);
const dbIndexKeys = Object.keys(dbIndex.key);
if (schemaIndexKeys.length !== dbIndexKeys.length) {
return false;
@@ -87,7 +87,7 @@ module.exports = function isIndexEqual(key, options, dbIndex) {
if (schemaIndexKeys[i] !== dbIndexKeys[i]) {
return false;
}
- if (!utils.deepEqual(key[schemaIndexKeys[i]], dbIndex.key[dbIndexKeys[i]])) {
+ if (!utils.deepEqual(schemaIndexKeysObject[schemaIndexKeys[i]], dbIndex.key[dbIndexKeys[i]])) {
return false;
}
}
diff --git a/lib/helpers/indexes/isTextIndex.js b/lib/helpers/indexes/isTextIndex.js
new file mode 100644
index 00000000000..fdd98d450ef
--- /dev/null
+++ b/lib/helpers/indexes/isTextIndex.js
@@ -0,0 +1,16 @@
+'use strict';
+
+/**
+ * Returns `true` if the given index options have a `text` option.
+ */
+
+module.exports = function isTextIndex(indexKeys) {
+ let isTextIndex = false;
+ for (const key of Object.keys(indexKeys)) {
+ if (indexKeys[key] === 'text') {
+ isTextIndex = true;
+ }
+ }
+
+ return isTextIndex;
+};
diff --git a/lib/helpers/isAsyncFunction.js b/lib/helpers/isAsyncFunction.js
index ecd2872af0e..679590e5866 100644
--- a/lib/helpers/isAsyncFunction.js
+++ b/lib/helpers/isAsyncFunction.js
@@ -1,11 +1,9 @@
'use strict';
-const { inspect } = require('util');
-
module.exports = function isAsyncFunction(v) {
- if (typeof v !== 'function') {
- return;
- }
-
- return inspect(v).startsWith('[AsyncFunction:');
-};
\ No newline at end of file
+ return (
+ typeof v === 'function' &&
+ v.constructor &&
+ v.constructor.name === 'AsyncFunction'
+ );
+};
diff --git a/lib/helpers/isBsonType.js b/lib/helpers/isBsonType.js
index 01435d3f285..f75fd40169d 100644
--- a/lib/helpers/isBsonType.js
+++ b/lib/helpers/isBsonType.js
@@ -1,13 +1,16 @@
'use strict';
-const get = require('./get');
-
-/*!
+/**
* Get the bson type, if it exists
+ * @api private
*/
function isBsonType(obj, typename) {
- return get(obj, '_bsontype', void 0) === typename;
+ return (
+ typeof obj === 'object' &&
+ obj !== null &&
+ obj._bsontype === typename
+ );
}
module.exports = isBsonType;
diff --git a/lib/helpers/isMongooseObject.js b/lib/helpers/isMongooseObject.js
index 016f9e65a86..736d0ecd7f4 100644
--- a/lib/helpers/isMongooseObject.js
+++ b/lib/helpers/isMongooseObject.js
@@ -1,21 +1,22 @@
'use strict';
-/*!
+const isMongooseArray = require('../types/array/isMongooseArray').isMongooseArray;
+/**
* Returns if `v` is a mongoose object that has a `toObject()` method we can use.
*
* This is for compatibility with libs like Date.js which do foolish things to Natives.
*
- * @param {any} v
+ * @param {Any} v
* @api private
*/
module.exports = function(v) {
- if (v == null) {
- return false;
- }
-
- return v.$__ != null || // Document
- v.isMongooseArray || // Array or Document Array
- v.isMongooseBuffer || // Buffer
- v.$isMongooseMap; // Map
-};
\ No newline at end of file
+ return (
+ v != null && (
+ isMongooseArray(v) || // Array or Document Array
+ v.$__ != null || // Document
+ v.isMongooseBuffer || // Buffer
+ v.$isMongooseMap // Map
+ )
+ );
+};
diff --git a/lib/helpers/isObject.js b/lib/helpers/isObject.js
index f8ac31326cb..21900deded4 100644
--- a/lib/helpers/isObject.js
+++ b/lib/helpers/isObject.js
@@ -1,6 +1,6 @@
'use strict';
-/*!
+/**
* Determines if `arg` is an object.
*
* @param {Object|Array|String|Function|RegExp|any} arg
@@ -9,8 +9,8 @@
*/
module.exports = function(arg) {
- if (Buffer.isBuffer(arg)) {
- return true;
- }
- return Object.prototype.toString.call(arg) === '[object Object]';
-};
\ No newline at end of file
+ return (
+ Buffer.isBuffer(arg) ||
+ Object.prototype.toString.call(arg) === '[object Object]'
+ );
+};
diff --git a/lib/helpers/isPromise.js b/lib/helpers/isPromise.js
index d6db2608cc9..0da827e2846 100644
--- a/lib/helpers/isPromise.js
+++ b/lib/helpers/isPromise.js
@@ -3,4 +3,4 @@ function isPromise(val) {
return !!val && (typeof val === 'object' || typeof val === 'function') && typeof val.then === 'function';
}
-module.exports = isPromise;
\ No newline at end of file
+module.exports = isPromise;
diff --git a/lib/helpers/isSimpleValidator.js b/lib/helpers/isSimpleValidator.js
new file mode 100644
index 00000000000..92c9346822a
--- /dev/null
+++ b/lib/helpers/isSimpleValidator.js
@@ -0,0 +1,22 @@
+'use strict';
+
+/**
+ * Determines if `arg` is a flat object.
+ *
+ * @param {Object|Array|String|Function|RegExp|any} arg
+ * @api private
+ * @return {Boolean}
+ */
+
+module.exports = function isSimpleValidator(obj) {
+ const keys = Object.keys(obj);
+ let result = true;
+ for (let i = 0, len = keys.length; i < len; ++i) {
+ if (typeof obj[keys[i]] === 'object' && obj[keys[i]] !== null) {
+ result = false;
+ break;
+ }
+ }
+
+ return result;
+};
diff --git a/lib/helpers/model/applyDefaultsToPOJO.js b/lib/helpers/model/applyDefaultsToPOJO.js
new file mode 100644
index 00000000000..4aca295cd29
--- /dev/null
+++ b/lib/helpers/model/applyDefaultsToPOJO.js
@@ -0,0 +1,52 @@
+'use strict';
+
+module.exports = function applyDefaultsToPOJO(doc, schema) {
+ const paths = Object.keys(schema.paths);
+ const plen = paths.length;
+
+ for (let i = 0; i < plen; ++i) {
+ let curPath = '';
+ const p = paths[i];
+
+ const type = schema.paths[p];
+ const path = type.splitPath();
+ const len = path.length;
+ let doc_ = doc;
+ for (let j = 0; j < len; ++j) {
+ if (doc_ == null) {
+ break;
+ }
+
+ const piece = path[j];
+ curPath += (!curPath.length ? '' : '.') + piece;
+
+ if (j === len - 1) {
+ if (typeof doc_[piece] !== 'undefined') {
+ if (type.$isSingleNested) {
+ applyDefaultsToPOJO(doc_[piece], type.caster.schema);
+ } else if (type.$isMongooseDocumentArray && Array.isArray(doc_[piece])) {
+ doc_[piece].forEach(el => applyDefaultsToPOJO(el, type.schema));
+ }
+
+ break;
+ }
+
+ const def = type.getDefault(doc, false, { skipCast: true });
+ if (typeof def !== 'undefined') {
+ doc_[piece] = def;
+
+ if (type.$isSingleNested) {
+ applyDefaultsToPOJO(def, type.caster.schema);
+ } else if (type.$isMongooseDocumentArray && Array.isArray(def)) {
+ def.forEach(el => applyDefaultsToPOJO(el, type.schema));
+ }
+ }
+ } else {
+ if (doc_[piece] == null) {
+ doc_[piece] = {};
+ }
+ doc_ = doc_[piece];
+ }
+ }
+ }
+};
diff --git a/lib/helpers/model/applyHooks.js b/lib/helpers/model/applyHooks.js
index 0498c75e665..7ed7895d4b3 100644
--- a/lib/helpers/model/applyHooks.js
+++ b/lib/helpers/model/applyHooks.js
@@ -23,10 +23,18 @@ applyHooks.middlewareFunctions = [
];
/*!
+ * ignore
+ */
+
+const alreadyHookedFunctions = new Set(applyHooks.middlewareFunctions.flatMap(fn => ([fn, `$__${fn}`])));
+
+/**
* Register hooks for this model
*
* @param {Model} model
* @param {Schema} schema
+ * @param {Object} options
+ * @api private
*/
function applyHooks(model, schema, options) {
@@ -115,6 +123,9 @@ function applyHooks(model, schema, options) {
checkForPromise: true
});
for (const method of customMethods) {
+ if (alreadyHookedFunctions.has(method)) {
+ continue;
+ }
if (!middleware.hasHooks(method)) {
// Don't wrap if there are no hooks for the custom method to avoid
// surprises. Also, `createWrapper()` enforces consistent async,
@@ -135,4 +146,4 @@ function applyHooks(model, schema, options) {
objToDecorate[`$__${method}`] = middleware.
createWrapper(method, originalMethod, null, customMethodOptions);
}
-}
\ No newline at end of file
+}
diff --git a/lib/helpers/model/applyMethods.js b/lib/helpers/model/applyMethods.js
index bd3718da3c8..e864bb1f12a 100644
--- a/lib/helpers/model/applyMethods.js
+++ b/lib/helpers/model/applyMethods.js
@@ -3,14 +3,17 @@
const get = require('../get');
const utils = require('../../utils');
-/*!
+/**
* Register methods for this model
*
* @param {Model} model
* @param {Schema} schema
+ * @api private
*/
module.exports = function applyMethods(model, schema) {
+ const Model = require('../../model');
+
function apply(method, schema) {
Object.defineProperty(model.prototype, method, {
get: function() {
@@ -29,6 +32,16 @@ module.exports = function applyMethods(model, schema) {
throw new Error('You have a method and a property in your schema both ' +
'named "' + method + '"');
}
+
+ // Avoid making custom methods if user sets a method to itself, e.g.
+ // `schema.method(save, Document.prototype.save)`. Can happen when
+ // calling `loadClass()` with a class that `extends Document`. See gh-12254
+ if (typeof fn === 'function' &&
+ Model.prototype[method] === fn) {
+ delete schema.methods[method];
+ continue;
+ }
+
if (schema.reserved[method] &&
!get(schema, `methodOptions.${method}.suppressWarning`, false)) {
utils.warn(`mongoose: the method name "${method}" is used by mongoose ` +
diff --git a/lib/helpers/model/applyStaticHooks.js b/lib/helpers/model/applyStaticHooks.js
index 219e2890318..934f9452ade 100644
--- a/lib/helpers/model/applyStaticHooks.js
+++ b/lib/helpers/model/applyStaticHooks.js
@@ -68,4 +68,4 @@ module.exports = function applyStaticHooks(model, hooks, statics) {
};
}
}
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/model/applyStatics.js b/lib/helpers/model/applyStatics.js
index 3b9501e0428..d94d91c7cfe 100644
--- a/lib/helpers/model/applyStatics.js
+++ b/lib/helpers/model/applyStatics.js
@@ -1,9 +1,10 @@
'use strict';
-/*!
+/**
* Register statics for this model
* @param {Model} model
* @param {Schema} schema
+ * @api private
*/
module.exports = function applyStatics(model, schema) {
for (const i in schema.statics) {
diff --git a/lib/helpers/model/castBulkWrite.js b/lib/helpers/model/castBulkWrite.js
index 84bcb79f8c8..b58f166d0bb 100644
--- a/lib/helpers/model/castBulkWrite.js
+++ b/lib/helpers/model/castBulkWrite.js
@@ -7,9 +7,13 @@ const cast = require('../../cast');
const castUpdate = require('../query/castUpdate');
const setDefaultsOnInsert = require('../setDefaultsOnInsert');
-/*!
+/**
* Given a model and a bulkWrite op, return a thunk that handles casting and
* validating the individual op.
+ * @param {Model} originalModel
+ * @param {Object} op
+ * @param {Object} [options]
+ * @api private
*/
module.exports = function castBulkWrite(originalModel, op, options) {
@@ -20,13 +24,19 @@ module.exports = function castBulkWrite(originalModel, op, options) {
const model = decideModelByObject(originalModel, op['insertOne']['document']);
const doc = new model(op['insertOne']['document']);
- if (model.schema.options.timestamps) {
+ if (model.schema.options.timestamps && options.timestamps !== false) {
doc.initializeTimestamps();
}
if (options.session != null) {
doc.$session(options.session);
}
op['insertOne']['document'] = doc;
+
+ if (options.skipValidation || op['insertOne'].skipValidation) {
+ callback(null);
+ return;
+ }
+
op['insertOne']['document'].$validate({ __noPromise: true }, function(error) {
if (error) {
return callback(error, null);
@@ -154,6 +164,12 @@ module.exports = function castBulkWrite(originalModel, op, options) {
}
op['replaceOne']['replacement'] = doc;
+ if (options.skipValidation || op['replaceOne'].skipValidation) {
+ op['replaceOne']['replacement'] = op['replaceOne']['replacement'].toBSON();
+ callback(null);
+ return;
+ }
+
op['replaceOne']['replacement'].$validate({ __noPromise: true }, function(error) {
if (error) {
return callback(error, null);
@@ -210,8 +226,9 @@ function _addDiscriminatorToObject(schema, obj) {
}
}
-/*!
+/**
* gets discriminator model if discriminator key is present in object
+ * @api private
*/
function decideModelByObject(model, object) {
diff --git a/lib/helpers/model/discriminator.js b/lib/helpers/model/discriminator.js
index 25485e90f26..0c843df10da 100644
--- a/lib/helpers/model/discriminator.js
+++ b/lib/helpers/model/discriminator.js
@@ -1,26 +1,32 @@
'use strict';
const Mixed = require('../../schema/mixed');
+const applyBuiltinPlugins = require('../schema/applyBuiltinPlugins');
const defineKey = require('../document/compile').defineKey;
const get = require('../get');
const utils = require('../../utils');
+const mergeDiscriminatorSchema = require('../../helpers/discriminator/mergeDiscriminatorSchema');
const CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
toJSON: true,
toObject: true,
_id: true,
- id: true
+ id: true,
+ virtuals: true,
+ methods: true
};
/*!
* ignore
*/
-module.exports = function discriminator(model, name, schema, tiedValue, applyPlugins) {
+module.exports = function discriminator(model, name, schema, tiedValue, applyPlugins, mergeHooks) {
if (!(schema && schema.instanceOfSchema)) {
throw new Error('You must pass a valid discriminator Schema');
}
+ mergeHooks = mergeHooks == null ? true : mergeHooks;
+
if (model.schema.discriminatorMapping &&
!model.schema.discriminatorMapping.isRoot) {
throw new Error('Discriminator "' + name +
@@ -29,12 +35,14 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu
if (applyPlugins) {
const applyPluginsToDiscriminators = get(model.base,
- 'options.applyPluginsToDiscriminators', false);
+ 'options.applyPluginsToDiscriminators', false) || !mergeHooks;
// Even if `applyPluginsToDiscriminators` isn't set, we should still apply
// global plugins to schemas embedded in the discriminator schema (gh-7370)
model.base._applyPlugins(schema, {
skipTopLevel: !applyPluginsToDiscriminators
});
+ } else if (!mergeHooks) {
+ applyBuiltinPlugins(schema);
}
const key = model.schema.options.discriminatorKey;
@@ -106,9 +114,8 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu
}
}
- utils.merge(schema, baseSchema, {
- isDiscriminatorSchemaMerge: true,
- omit: { discriminators: true, base: true },
+ mergeDiscriminatorSchema(schema, baseSchema, {
+ omit: { discriminators: true, base: true, _applyDiscriminators: true },
omitNested: conflictingPaths.reduce((cur, path) => {
cur['tree.' + path] = true;
return cur;
@@ -140,7 +147,6 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu
obj[key][schema.options.typeKey] = existingPath ? existingPath.options[schema.options.typeKey] : String;
schema.add(obj);
-
schema.discriminatorMapping = { key: key, value: value, isRoot: false };
if (baseSchema.options.collection) {
@@ -178,9 +184,12 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu
schema.options._id = _id;
}
schema.options.id = id;
- schema.s.hooks = model.schema.s.hooks.merge(schema.s.hooks);
-
- schema.plugins = Array.prototype.slice.call(baseSchema.plugins);
+ if (mergeHooks) {
+ schema.s.hooks = model.schema.s.hooks.merge(schema.s.hooks);
+ }
+ if (applyPlugins) {
+ schema.plugins = Array.prototype.slice.call(baseSchema.plugins);
+ }
schema.callQueue = baseSchema.callQueue.concat(schema.callQueue);
delete schema._requiredpaths; // reset just in case Schema#requiredPaths() was called on either schema
}
@@ -201,7 +210,7 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu
model.schema.discriminators[name] = schema;
- if (model.discriminators[name]) {
+ if (model.discriminators[name] && !schema.options.overwriteModels) {
throw new Error('Discriminator with name "' + name + '" already exists');
}
diff --git a/lib/helpers/model/pushNestedArrayPaths.js b/lib/helpers/model/pushNestedArrayPaths.js
new file mode 100644
index 00000000000..7f234faa213
--- /dev/null
+++ b/lib/helpers/model/pushNestedArrayPaths.js
@@ -0,0 +1,15 @@
+'use strict';
+
+module.exports = function pushNestedArrayPaths(paths, nestedArray, path) {
+ if (nestedArray == null) {
+ return;
+ }
+
+ for (let i = 0; i < nestedArray.length; ++i) {
+ if (Array.isArray(nestedArray[i])) {
+ pushNestedArrayPaths(paths, nestedArray[i], path + '.' + i);
+ } else {
+ paths.push(path + '.' + i);
+ }
+ }
+};
diff --git a/lib/helpers/once.js b/lib/helpers/once.js
index 02675799c38..dfa5ee71081 100644
--- a/lib/helpers/once.js
+++ b/lib/helpers/once.js
@@ -9,4 +9,4 @@ module.exports = function once(fn) {
called = true;
return fn.apply(null, arguments);
};
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/path/flattenObjectWithDottedPaths.js b/lib/helpers/path/flattenObjectWithDottedPaths.js
deleted file mode 100644
index 72343bfad52..00000000000
--- a/lib/helpers/path/flattenObjectWithDottedPaths.js
+++ /dev/null
@@ -1,38 +0,0 @@
-'use strict';
-
-const MongooseError = require('../../error/mongooseError');
-const setDottedPath = require('../path/setDottedPath');
-const util = require('util');
-
-/**
- * Given an object that may contain dotted paths, flatten the paths out.
- * For example: `flattenObjectWithDottedPaths({ a: { 'b.c': 42 } })` => `{ a: { b: { c: 42 } } }`
- */
-
-module.exports = function flattenObjectWithDottedPaths(obj) {
- if (obj == null || typeof obj !== 'object' || Array.isArray(obj)) {
- return;
- }
- // Avoid Mongoose docs
- if (obj.$__) {
- return;
- }
- const keys = Object.keys(obj);
- for (const key of keys) {
- const val = obj[key];
- if (key.indexOf('.') !== -1) {
- try {
- delete obj[key];
- setDottedPath(obj, key, val);
- } catch (err) {
- if (!(err instanceof TypeError)) {
- throw err;
- }
- throw new MongooseError(`Conflicting dotted paths when setting document path, key: "${key}", value: ${util.inspect(val)}`);
- }
- continue;
- }
-
- flattenObjectWithDottedPaths(obj[key]);
- }
-};
\ No newline at end of file
diff --git a/lib/helpers/path/parentPaths.js b/lib/helpers/path/parentPaths.js
index 77d00ae89bb..6822c8cefd3 100644
--- a/lib/helpers/path/parentPaths.js
+++ b/lib/helpers/path/parentPaths.js
@@ -1,13 +1,18 @@
'use strict';
+const dotRE = /\./g;
module.exports = function parentPaths(path) {
- const pieces = path.split('.');
+ if (path.indexOf('.') === -1) {
+ return [path];
+ }
+ const pieces = path.split(dotRE);
+ const len = pieces.length;
+ const ret = new Array(len);
let cur = '';
- const ret = [];
- for (let i = 0; i < pieces.length; ++i) {
- cur += (cur.length > 0 ? '.' : '') + pieces[i];
- ret.push(cur);
+ for (let i = 0; i < len; ++i) {
+ cur += (cur.length !== 0) ? '.' + pieces[i] : pieces[i];
+ ret[i] = cur;
}
return ret;
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/path/setDottedPath.js b/lib/helpers/path/setDottedPath.js
index 853769c686d..b17d549097c 100644
--- a/lib/helpers/path/setDottedPath.js
+++ b/lib/helpers/path/setDottedPath.js
@@ -1,9 +1,25 @@
'use strict';
+const specialProperties = require('../specialProperties');
+
+
module.exports = function setDottedPath(obj, path, val) {
- const parts = path.indexOf('.') === -1 ? [path] : path.split('.');
+ if (path.indexOf('.') === -1) {
+ if (specialProperties.has(path)) {
+ return;
+ }
+
+ obj[path] = val;
+ return;
+ }
+ const parts = path.split('.');
+
+ const last = parts.pop();
let cur = obj;
- for (const part of parts.slice(0, -1)) {
+ for (const part of parts) {
+ if (specialProperties.has(part)) {
+ continue;
+ }
if (cur[part] == null) {
cur[part] = {};
}
@@ -11,6 +27,7 @@ module.exports = function setDottedPath(obj, path, val) {
cur = cur[part];
}
- const last = parts[parts.length - 1];
- cur[last] = val;
-};
\ No newline at end of file
+ if (!specialProperties.has(last)) {
+ cur[last] = val;
+ }
+};
diff --git a/lib/helpers/pluralize.js b/lib/helpers/pluralize.js
index c567950173b..657c87f03a8 100644
--- a/lib/helpers/pluralize.js
+++ b/lib/helpers/pluralize.js
@@ -71,7 +71,7 @@ exports.uncountables = [
];
const uncountables = exports.uncountables;
-/*!
+/**
* Pluralize function.
*
* @author TJ Holowaychuk (extracted from _ext.js_)
@@ -91,4 +91,4 @@ function pluralize(str) {
}
}
return str;
-}
\ No newline at end of file
+}
diff --git a/lib/helpers/populate/SkipPopulateValue.js b/lib/helpers/populate/SkipPopulateValue.js
index 5d46cfdd8ea..38f3f3906a1 100644
--- a/lib/helpers/populate/SkipPopulateValue.js
+++ b/lib/helpers/populate/SkipPopulateValue.js
@@ -7,4 +7,4 @@ module.exports = function SkipPopulateValue(val) {
this.val = val;
return this;
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/populate/assignRawDocsToIdStructure.js b/lib/helpers/populate/assignRawDocsToIdStructure.js
index 562b15cf535..e04b601ba97 100644
--- a/lib/helpers/populate/assignRawDocsToIdStructure.js
+++ b/lib/helpers/populate/assignRawDocsToIdStructure.js
@@ -6,7 +6,9 @@ const utils = require('../../utils');
module.exports = assignRawDocsToIdStructure;
-/*!
+const kHasArray = Symbol('assignRawDocsToIdStructure.hasArray');
+
+/**
* Assign `vals` returned by mongo query to the `rawIds`
* structure returned from utils.getVals() honoring
* query sort order if specified by user.
@@ -21,8 +23,10 @@ module.exports = assignRawDocsToIdStructure;
* else documents are put back in original order of array if found in results
*
* @param {Array} rawIds
- * @param {Array} vals
- * @param {Boolean} sort
+ * @param {Array} resultDocs
+ * @param {Array} resultOrder
+ * @param {Object} options
+ * @param {Boolean} recursed
* @api private
*/
@@ -35,11 +39,24 @@ function assignRawDocsToIdStructure(rawIds, resultDocs, resultOrder, options, re
let sid;
let id;
- if (rawIds.isMongooseArrayProxy) {
+ if (utils.isMongooseArray(rawIds)) {
rawIds = rawIds.__array;
}
- for (let i = 0; i < rawIds.length; ++i) {
+ let i = 0;
+ const len = rawIds.length;
+
+ if (sorting && recursed && options[kHasArray] === undefined) {
+ options[kHasArray] = false;
+ for (const key in resultOrder) {
+ if (Array.isArray(resultOrder[key])) {
+ options[kHasArray] = true;
+ break;
+ }
+ }
+ }
+
+ for (i = 0; i < len; ++i) {
id = rawIds[i];
if (Array.isArray(id)) {
@@ -49,7 +66,7 @@ function assignRawDocsToIdStructure(rawIds, resultDocs, resultOrder, options, re
continue;
}
- if (id === null && !sorting) {
+ if (id === null && sorting === false) {
// keep nulls for findOne unless sorting, which always
// removes them (backward compat)
newOrder.push(id);
@@ -57,7 +74,6 @@ function assignRawDocsToIdStructure(rawIds, resultDocs, resultOrder, options, re
}
sid = String(id);
-
doc = resultDocs[sid];
// If user wants separate copies of same doc, use this option
if (options.clone && doc != null) {
@@ -74,7 +90,8 @@ function assignRawDocsToIdStructure(rawIds, resultDocs, resultOrder, options, re
if (doc) {
if (sorting) {
const _resultOrder = resultOrder[sid];
- if (Array.isArray(_resultOrder) && Array.isArray(doc) && _resultOrder.length === doc.length) {
+ if (options[kHasArray]) {
+ // If result arrays, rely on the MongoDB server response for ordering
newOrder.push(doc);
} else {
newOrder[_resultOrder] = doc;
@@ -104,4 +121,4 @@ function assignRawDocsToIdStructure(rawIds, resultDocs, resultOrder, options, re
rawIds[i] = doc;
});
}
-}
\ No newline at end of file
+}
diff --git a/lib/helpers/populate/assignVals.js b/lib/helpers/populate/assignVals.js
index e94bc3a2772..92f0ebecd05 100644
--- a/lib/helpers/populate/assignVals.js
+++ b/lib/helpers/populate/assignVals.js
@@ -52,6 +52,11 @@ module.exports = function assignVals(o) {
const _allIds = o.allIds[i];
+ if (o.path.endsWith('.$*')) {
+ // Skip maps re: gh-12494
+ return valueFilter(val, options, populateOptions, _allIds);
+ }
+
if (o.justOne === true && Array.isArray(val)) {
// Might be an embedded discriminator (re: gh-9244) with multiple models, so make sure to pick the right
// model before assigning.
@@ -170,7 +175,11 @@ module.exports = function assignVals(o) {
}
if (docs[i].$__) {
o.allOptions.options[populateModelSymbol] = o.allOptions.model;
- docs[i].$populated(_path, o.allIds[i], o.allOptions.options);
+ docs[i].$populated(_path, o.unpopulatedValues[i], o.allOptions.options);
+
+ if (valueToSet != null && valueToSet.$__ != null) {
+ valueToSet.$__.wasPopulated = { value: o.unpopulatedValues[i] };
+ }
if (valueToSet instanceof Map && !valueToSet.$isMongooseMap) {
valueToSet = new MongooseMap(valueToSet, _path, docs[i], docs[i].schema.path(_path).$__schemaType);
@@ -192,15 +201,23 @@ function numDocs(v) {
if (Array.isArray(v)) {
// If setting underneath an array of populated subdocs, we may have an
// array of arrays. See gh-7573
- if (v.some(el => Array.isArray(el))) {
- return v.map(el => numDocs(el));
+ if (v.some(el => Array.isArray(el) || el === null)) {
+ return v.map(el => {
+ if (el == null) {
+ return 0;
+ }
+ if (Array.isArray(el)) {
+ return el.filter(el => el != null).length;
+ }
+ return 1;
+ });
}
- return v.length;
+ return v.filter(el => el != null).length;
}
return v == null ? 0 : 1;
}
-/*!
+/**
* 1) Apply backwards compatible find/findOne behavior to sub documents
*
* find logic:
@@ -216,6 +233,12 @@ function numDocs(v) {
* background:
* _ids are left in the query even when user excludes them so
* that population mapping can occur.
+ * @param {Any} val
+ * @param {Object} assignmentOpts
+ * @param {Object} populateOptions
+ * @param {Function} [populateOptions.transform]
+ * @param {Boolean} allIds
+ * @api private
*/
function valueFilter(val, assignmentOpts, populateOptions, allIds) {
@@ -230,6 +253,8 @@ function valueFilter(val, assignmentOpts, populateOptions, allIds) {
const _allIds = Array.isArray(allIds) ? allIds[i] : allIds;
if (!isPopulatedObject(subdoc) && (!populateOptions.retainNullValues || subdoc != null) && !userSpecifiedTransform) {
continue;
+ } else if (!populateOptions.retainNullValues && subdoc == null) {
+ continue;
} else if (userSpecifiedTransform) {
subdoc = transform(isPopulatedObject(subdoc) ? subdoc : null, _allIds);
}
@@ -241,15 +266,19 @@ function valueFilter(val, assignmentOpts, populateOptions, allIds) {
}
}
+ const rLen = ret.length;
// Since we don't want to have to create a new mongoosearray, make sure to
// modify the array in place
- while (val.length > ret.length) {
+ while (val.length > rLen) {
Array.prototype.pop.apply(val, []);
}
- for (let i = 0; i < ret.length; ++i) {
- if (val.isMongooseArrayProxy) {
+ let i = 0;
+ if (utils.isMongooseArray(val)) {
+ for (i = 0; i < rLen; ++i) {
val.set(i, ret[i], true);
- } else {
+ }
+ } else {
+ for (i = 0; i < rLen; ++i) {
val[i] = ret[i];
}
}
@@ -272,8 +301,11 @@ function valueFilter(val, assignmentOpts, populateOptions, allIds) {
return val == null ? transform(val, allIds) : transform(null, allIds);
}
-/*!
+/**
* Remove _id from `subdoc` if user specified "lean" query option
+ * @param {Document} subdoc
+ * @param {Object} assignmentOpts
+ * @api private
*/
function maybeRemoveId(subdoc, assignmentOpts) {
@@ -286,9 +318,11 @@ function maybeRemoveId(subdoc, assignmentOpts) {
}
}
-/*!
+/**
* Determine if `obj` is something we can set a populated path to. Can be a
* document, a lean document, or an array/map that contains docs.
+ * @param {Any} obj
+ * @api private
*/
function isPopulatedObject(obj) {
@@ -304,4 +338,4 @@ function isPopulatedObject(obj) {
function noop(v) {
return v;
-}
\ No newline at end of file
+}
diff --git a/lib/helpers/populate/createPopulateQueryFilter.js b/lib/helpers/populate/createPopulateQueryFilter.js
index 1f133b7d15a..acfeee62ae0 100644
--- a/lib/helpers/populate/createPopulateQueryFilter.js
+++ b/lib/helpers/populate/createPopulateQueryFilter.js
@@ -3,6 +3,7 @@
const SkipPopulateValue = require('./SkipPopulateValue');
const parentPaths = require('../path/parentPaths');
const { trusted } = require('../query/trusted');
+const hasDollarKeys = require('../query/hasDollarKeys');
module.exports = function createPopulateQueryFilter(ids, _match, _foreignField, model, skipInvalidIds) {
const match = _formatMatch(_match);
@@ -13,6 +14,11 @@ module.exports = function createPopulateQueryFilter(ids, _match, _foreignField,
if (foreignField !== '_id' || !match['_id']) {
ids = _filterInvalidIds(ids, foreignSchemaType, skipInvalidIds);
match[foreignField] = trusted({ $in: ids });
+ } else if (foreignField === '_id' && match['_id']) {
+ const userSpecifiedMatch = hasDollarKeys(match[foreignField]) ?
+ match[foreignField] :
+ { $eq: match[foreignField] };
+ match[foreignField] = { ...trusted({ $in: ids }), ...userSpecifiedMatch };
}
const _parentPaths = parentPaths(foreignField);
@@ -37,6 +43,11 @@ module.exports = function createPopulateQueryFilter(ids, _match, _foreignField,
const foreignSchemaType = model.schema.path(foreignField);
ids = _filterInvalidIds(ids, foreignSchemaType, skipInvalidIds);
$or.push({ [foreignField]: { $in: ids } });
+ } else if (foreignField === '_id' && match['_id']) {
+ const userSpecifiedMatch = hasDollarKeys(match[foreignField]) ?
+ match[foreignField] :
+ { $eq: match[foreignField] };
+ match[foreignField] = { ...trusted({ $in: ids }), ...userSpecifiedMatch };
}
}
}
@@ -44,9 +55,13 @@ module.exports = function createPopulateQueryFilter(ids, _match, _foreignField,
return match;
};
-/*!
+/**
* Optionally filter out invalid ids that don't conform to foreign field's schema
* to avoid cast errors (gh-7706)
+ * @param {Array} ids
+ * @param {SchemaType} foreignSchemaType
+ * @param {Boolean} [skipInvalidIds]
+ * @api private
*/
function _filterInvalidIds(ids, foreignSchemaType, skipInvalidIds) {
@@ -64,9 +79,11 @@ function _filterInvalidIds(ids, foreignSchemaType, skipInvalidIds) {
});
}
-/*!
+/**
* Format `mod.match` given that it may be an array that we need to $or if
* the client has multiple docs with match functions
+ * @param {Array|Any} match
+ * @api private
*/
function _formatMatch(match) {
@@ -77,4 +94,4 @@ function _formatMatch(match) {
return Object.assign({}, match[0]);
}
return Object.assign({}, match);
-}
\ No newline at end of file
+}
diff --git a/lib/helpers/populate/getModelsMapForPopulate.js b/lib/helpers/populate/getModelsMapForPopulate.js
index cce511e5b2b..8ae89fcbbd3 100644
--- a/lib/helpers/populate/getModelsMapForPopulate.js
+++ b/lib/helpers/populate/getModelsMapForPopulate.js
@@ -15,6 +15,7 @@ const utils = require('../../utils');
const modelSymbol = require('../symbols').modelSymbol;
const populateModelSymbol = require('../symbols').populateModelSymbol;
const schemaMixedSymbol = require('../../schema/symbols').schemaMixedSymbol;
+const StrictPopulate = require('../../error/strictPopulate');
module.exports = function getModelsMapForPopulate(model, docs, options) {
let doc;
@@ -43,10 +44,8 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
let allSchemaTypes = getSchemaTypes(model, modelSchema, null, options.path);
allSchemaTypes = Array.isArray(allSchemaTypes) ? allSchemaTypes : [allSchemaTypes].filter(v => v != null);
- if (allSchemaTypes.length <= 0 && options.strictPopulate !== false && options._localModel != null) {
- return new MongooseError('Cannot populate path `' + options.path +
- '` because it is not in your schema. Set the `strictPopulate` option ' +
- 'to false to override.');
+ if (allSchemaTypes.length === 0 && options.strictPopulate !== false && options._localModel != null) {
+ return new StrictPopulate(options._fullPath || options.path);
}
for (let i = 0; i < len; i++) {
@@ -74,6 +73,24 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
let isRefPath = false;
let normalizedRefPath = null;
let schemaOptions = null;
+ let modelNamesInOrder = null;
+
+ if (schema != null && schema.instance === 'Embedded') {
+ if (schema.options.ref) {
+ const data = {
+ localField: options.path + '._id',
+ foreignField: '_id',
+ justOne: true
+ };
+ const res = _getModelNames(doc, schema, modelNameFromQuery, model);
+
+ const unpopulatedValue = mpath.get(options.path, doc);
+ const id = mpath.get('_id', unpopulatedValue);
+ addModelNamesToMap(model, map, available, res.modelNames, options, data, id, doc, schemaOptions, unpopulatedValue);
+ }
+ // No-op if no `ref` set. See gh-11538
+ continue;
+ }
if (Array.isArray(schema)) {
const schemasArray = schema;
@@ -111,6 +128,12 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
normalizedRefPath = normalizedRefPath || res.refPath;
justOne = res.justOne;
schemaOptions = get(schema, 'options.populate', null);
+ // Dedupe, because `refPath` can return duplicates of the same model name,
+ // and that causes perf issues.
+ if (isRefPath) {
+ modelNamesInOrder = modelNames;
+ modelNames = Array.from(new Set(modelNames));
+ }
} catch (error) {
return error;
}
@@ -162,6 +185,7 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
data.match = match;
data.hasMatchFunction = hasMatchFunction;
data.isRefPath = isRefPath;
+ data.modelNamesInOrder = modelNamesInOrder;
if (isRefPath) {
const embeddedDiscriminatorModelNames = _findRefPathForDiscriminators(doc,
@@ -183,7 +207,8 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
let isRefPath = false;
let justOne = null;
- if (schema && schema.caster) {
+ const originalSchema = schema;
+ if (schema && schema.instance === 'Array') {
schema = schema.caster;
}
if (schema && schema.$isSchemaMap) {
@@ -238,7 +263,7 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
modelForCurrentDoc = discriminatorModel;
} else {
try {
- modelForCurrentDoc = model.db.model(discriminatorValue);
+ modelForCurrentDoc = _getModelFromConn(model.db, discriminatorValue);
} catch (error) {
return error;
}
@@ -253,7 +278,9 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
schemaForCurrentDoc = schema;
}
- if (schemaForCurrentDoc != null) {
+ if (originalSchema && originalSchema.path.endsWith('.$*')) {
+ justOne = !originalSchema.$isMongooseArray && !originalSchema._arrayPath;
+ } else if (schemaForCurrentDoc != null) {
justOne = !schemaForCurrentDoc.$isMongooseArray && !schemaForCurrentDoc._arrayPath;
}
@@ -338,7 +365,9 @@ function _virtualPopulate(model, docs, options, _virtualRes) {
let localField;
const virtualPrefix = _virtualRes.nestedSchemaPath ?
_virtualRes.nestedSchemaPath + '.' : '';
- if (typeof virtual.options.localField === 'function') {
+ if (typeof options.localField === 'string') {
+ localField = options.localField;
+ } else if (typeof virtual.options.localField === 'function') {
localField = virtualPrefix + virtual.options.localField.call(doc, doc);
} else if (Array.isArray(virtual.options.localField)) {
localField = virtual.options.localField.map(field => virtualPrefix + field);
@@ -367,7 +396,7 @@ function _virtualPopulate(model, docs, options, _virtualRes) {
localField = localField.call(doc, doc);
}
if (typeof foreignField === 'function') {
- foreignField = foreignField.call(doc);
+ foreignField = foreignField.call(doc, doc);
}
data.isRefPath = false;
@@ -426,7 +455,6 @@ function _virtualPopulate(model, docs, options, _virtualRes) {
localField = localField[0];
foreignField = foreignField[0];
}
-
data.localField = localField;
data.foreignField = foreignField;
data.match = match;
@@ -449,11 +477,16 @@ function _virtualPopulate(model, docs, options, _virtualRes) {
* ignore
*/
-function addModelNamesToMap(model, map, available, modelNames, options, data, ret, doc, schemaOptions) {
+function addModelNamesToMap(model, map, available, modelNames, options, data, ret, doc, schemaOptions, unpopulatedValue) {
// `PopulateOptions#connection`: if the model is passed as a string, the
// connection matters because different connections have different models.
const connection = options.connection != null ? options.connection : model.db;
+ unpopulatedValue = unpopulatedValue === void 0 ? ret : unpopulatedValue;
+ if (Array.isArray(unpopulatedValue)) {
+ unpopulatedValue = utils.cloneArrays(unpopulatedValue);
+ }
+
if (modelNames == null) {
return;
}
@@ -472,7 +505,7 @@ function addModelNamesToMap(model, map, available, modelNames, options, data, re
Model = modelName;
} else {
try {
- Model = connection.model(modelName);
+ Model = _getModelFromConn(connection, modelName);
} catch (err) {
if (ret !== void 0) {
throw err;
@@ -484,8 +517,9 @@ function addModelNamesToMap(model, map, available, modelNames, options, data, re
let ids = ret;
const flat = Array.isArray(ret) ? utils.array.flatten(ret) : [];
- if (data.isRefPath && Array.isArray(ret) && flat.length === modelNames.length) {
- ids = flat.filter((val, i) => modelNames[i] === modelName);
+ const modelNamesForRefPath = data.modelNamesInOrder ? data.modelNamesInOrder : modelNames;
+ if (data.isRefPath && Array.isArray(ret) && flat.length === modelNamesForRefPath.length) {
+ ids = flat.filter((val, i) => modelNamesForRefPath[i] === modelName);
}
const perDocumentLimit = options.perDocumentLimit == null ?
@@ -496,7 +530,6 @@ function addModelNamesToMap(model, map, available, modelNames, options, data, re
const currentOptions = {
model: Model
};
-
if (data.isVirtual && get(data.virtual, 'options.options')) {
currentOptions.options = utils.clone(data.virtual.options.options);
} else if (schemaOptions != null) {
@@ -507,7 +540,6 @@ function addModelNamesToMap(model, map, available, modelNames, options, data, re
// Used internally for checking what model was used to populate this
// path.
options[populateModelSymbol] = Model;
-
available[modelName] = {
model: Model,
options: currentOptions,
@@ -515,6 +547,7 @@ function addModelNamesToMap(model, map, available, modelNames, options, data, re
docs: [doc],
ids: [ids],
allIds: [ret],
+ unpopulatedValues: [unpopulatedValue],
localField: new Set([data.localField]),
foreignField: new Set([data.foreignField]),
justOne: data.justOne,
@@ -530,6 +563,7 @@ function addModelNamesToMap(model, map, available, modelNames, options, data, re
available[modelName].docs.push(doc);
available[modelName].ids.push(ids);
available[modelName].allIds.push(ret);
+ available[modelName].unpopulatedValues.push(unpopulatedValue);
if (data.hasMatchFunction) {
available[modelName].match.push(data.match);
}
@@ -537,6 +571,15 @@ function addModelNamesToMap(model, map, available, modelNames, options, data, re
}
}
+function _getModelFromConn(conn, modelName) {
+ /* If this connection has a parent from `useDb()`, bubble up to parent's models */
+ if (conn.models[modelName] == null && conn._parent != null) {
+ return _getModelFromConn(conn._parent, modelName);
+ }
+
+ return conn.model(modelName);
+}
+
/*!
* ignore
*/
@@ -561,12 +604,14 @@ function _getLocalFieldValues(doc, localField, model, options, virtual, schema)
const localFieldGetters = localFieldPath && localFieldPath.getters ?
localFieldPath.getters : [];
+ localField = localFieldPath != null && localFieldPath.instance === 'Embedded' ? localField + '._id' : localField;
+
const _populateOptions = get(options, 'options', {});
const getters = 'getters' in _populateOptions ?
_populateOptions.getters :
get(virtual, 'options.getters', false);
- if (localFieldGetters.length > 0 && getters) {
+ if (localFieldGetters.length !== 0 && getters) {
const hydratedDoc = (doc.$__ != null) ? doc : model.hydrate(doc);
const localFieldValue = utils.getValue(localField, doc);
if (Array.isArray(localFieldValue)) {
@@ -581,11 +626,13 @@ function _getLocalFieldValues(doc, localField, model, options, virtual, schema)
}
}
-/*!
+/**
* Retrieve the _id of `val` if a Document or Array of Documents.
*
* @param {Array|Document|Any} val
+ * @param {Schema} schema
* @return {Array|Document|Any}
+ * @api private
*/
function convertTo_id(val, schema) {
@@ -603,7 +650,7 @@ function convertTo_id(val, schema) {
rawVal[i] = rawVal[i]._id;
}
}
- if (val.isMongooseArray && val.$schema()) {
+ if (utils.isMongooseArray(val) && val.$schema()) {
return val.$schema()._castForPopulate(val, val.$parent());
}
@@ -653,9 +700,9 @@ function _findRefPathForDiscriminators(doc, modelSchema, data, options, normaliz
if (schematype != null &&
schematype.$isMongooseArray &&
schematype.caster.discriminators != null &&
- Object.keys(schematype.caster.discriminators).length > 0) {
+ Object.keys(schematype.caster.discriminators).length !== 0) {
const subdocs = utils.getValue(cur, doc);
- const remnant = options.path.substr(cur.length + 1);
+ const remnant = options.path.substring(cur.length + 1);
const discriminatorKey = schematype.caster.schema.options.discriminatorKey;
modelNames = [];
for (const subdoc of subdocs) {
@@ -667,7 +714,7 @@ function _findRefPathForDiscriminators(doc, modelSchema, data, options, normaliz
}
const _path = discriminatorSchema.path(remnant);
if (_path == null || _path.options.refPath == null) {
- const docValue = utils.getValue(data.localField.substr(cur.length + 1), subdoc);
+ const docValue = utils.getValue(data.localField.substring(cur.length + 1), subdoc);
ret.forEach((v, i) => {
if (v === docValue) {
ret[i] = SkipPopulateValue(v);
@@ -682,4 +729,4 @@ function _findRefPathForDiscriminators(doc, modelSchema, data, options, normaliz
}
return modelNames;
-}
\ No newline at end of file
+}
diff --git a/lib/helpers/populate/getSchemaTypes.js b/lib/helpers/populate/getSchemaTypes.js
index 76c428653be..0534f015286 100644
--- a/lib/helpers/populate/getSchemaTypes.js
+++ b/lib/helpers/populate/getSchemaTypes.js
@@ -12,15 +12,17 @@ const mpath = require('mpath');
const populateModelSymbol = require('../symbols').populateModelSymbol;
-/*!
+/**
* Given a model and its schema, find all possible schema types for `path`,
* including searching through discriminators. If `doc` is specified, will
* use the doc's values for discriminator keys when searching, otherwise
* will search all discriminators.
*
+ * @param {Model} model
* @param {Schema} schema
* @param {Object} doc POJO
* @param {string} path
+ * @api private
*/
module.exports = function getSchemaTypes(model, schema, doc, path) {
@@ -171,6 +173,8 @@ module.exports = function getSchemaTypes(model, schema, doc, path) {
}
}
}
+ } else if (foundschema.$isSchemaMap && foundschema.$__schemaType instanceof Mixed) {
+ return foundschema.$__schemaType;
}
const fullPath = nestedPath.concat([trypath]).join('.');
diff --git a/lib/helpers/populate/leanPopulateMap.js b/lib/helpers/populate/leanPopulateMap.js
index a333124fa76..9ff9b13570d 100644
--- a/lib/helpers/populate/leanPopulateMap.js
+++ b/lib/helpers/populate/leanPopulateMap.js
@@ -4,4 +4,4 @@
* ignore
*/
-module.exports = new WeakMap();
\ No newline at end of file
+module.exports = new WeakMap();
diff --git a/lib/helpers/populate/lookupLocalFields.js b/lib/helpers/populate/lookupLocalFields.js
index be553ebf9c1..b85d8d757c3 100644
--- a/lib/helpers/populate/lookupLocalFields.js
+++ b/lib/helpers/populate/lookupLocalFields.js
@@ -37,4 +37,4 @@ module.exports = function lookupLocalFields(cur, path, val) {
}
return cur[path];
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/populate/markArraySubdocsPopulated.js b/lib/helpers/populate/markArraySubdocsPopulated.js
index fd7a2dd8349..28b19a1989a 100644
--- a/lib/helpers/populate/markArraySubdocsPopulated.js
+++ b/lib/helpers/populate/markArraySubdocsPopulated.js
@@ -1,12 +1,19 @@
'use strict';
-/*!
+const utils = require('../../utils');
+
+/**
* If populating a path within a document array, make sure each
* subdoc within the array knows its subpaths are populated.
*
- * ####Example:
+ * #### Example:
+ *
* const doc = await Article.findOne().populate('comments.author');
* doc.comments[0].populated('author'); // Should be set
+ *
+ * @param {Document} doc
+ * @param {Object} [populated]
+ * @api private
*/
module.exports = function markArraySubdocsPopulated(doc, populated) {
@@ -29,7 +36,7 @@ module.exports = function markArraySubdocsPopulated(doc, populated) {
continue;
}
- if (val.isMongooseDocumentArray) {
+ if (utils.isMongooseDocumentArray(val)) {
for (let j = 0; j < val.length; ++j) {
val[j].populated(rest, item._docs[id] == null ? void 0 : item._docs[id][j], item);
}
@@ -37,4 +44,4 @@ module.exports = function markArraySubdocsPopulated(doc, populated) {
}
}
}
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/populate/modelNamesFromRefPath.js b/lib/helpers/populate/modelNamesFromRefPath.js
index c441f565e01..df643b234ae 100644
--- a/lib/helpers/populate/modelNamesFromRefPath.js
+++ b/lib/helpers/populate/modelNamesFromRefPath.js
@@ -2,9 +2,13 @@
const MongooseError = require('../../error/mongooseError');
const isPathExcluded = require('../projection/isPathExcluded');
+const lookupLocalFields = require('./lookupLocalFields');
+const mpath = require('mpath');
const util = require('util');
const utils = require('../../utils');
+const hasNumericPropRE = /(\.\d+$|\.\d+\.)/g;
+
module.exports = function modelNamesFromRefPath(refPath, doc, populatedPath, modelSchema, queryProjection) {
if (refPath == null) {
return [];
@@ -18,10 +22,9 @@ module.exports = function modelNamesFromRefPath(refPath, doc, populatedPath, mod
// If populated path has numerics, the end `refPath` should too. For example,
// if populating `a.0.b` instead of `a.b` and `b` has `refPath = a.c`, we
// should return `a.0.c` for the refPath.
- const hasNumericProp = /(\.\d+$|\.\d+\.)/g;
- if (hasNumericProp.test(populatedPath)) {
- const chunks = populatedPath.split(hasNumericProp);
+ if (hasNumericPropRE.test(populatedPath)) {
+ const chunks = populatedPath.split(hasNumericPropRE);
if (chunks[chunks.length - 1] === '') {
throw new Error('Can\'t populate individual element in an array');
@@ -33,8 +36,8 @@ module.exports = function modelNamesFromRefPath(refPath, doc, populatedPath, mod
for (let i = 0; i < chunks.length; i += 2) {
const chunk = chunks[i];
if (_remaining.startsWith(chunk + '.')) {
- _refPath += _remaining.substr(0, chunk.length) + chunks[i + 1];
- _remaining = _remaining.substr(chunk.length + 1);
+ _refPath += _remaining.substring(0, chunk.length) + chunks[i + 1];
+ _remaining = _remaining.substring(chunk.length + 1);
} else if (i === chunks.length - 1) {
_refPath += _remaining;
_remaining = '';
@@ -44,13 +47,13 @@ module.exports = function modelNamesFromRefPath(refPath, doc, populatedPath, mod
}
}
- const refValue = utils.getValue(_refPath, doc);
+ const refValue = mpath.get(_refPath, doc, lookupLocalFields);
let modelNames = Array.isArray(refValue) ? refValue : [refValue];
modelNames = utils.array.flatten(modelNames);
return modelNames;
}
- const refValue = utils.getValue(refPath, doc);
+ const refValue = mpath.get(refPath, doc, lookupLocalFields);
let modelNames;
if (modelSchema != null && modelSchema.virtuals.hasOwnProperty(refPath)) {
@@ -62,4 +65,4 @@ module.exports = function modelNamesFromRefPath(refPath, doc, populatedPath, mod
modelNames = utils.array.flatten(modelNames);
return modelNames;
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/populate/removeDeselectedForeignField.js b/lib/helpers/populate/removeDeselectedForeignField.js
index 39b893a9d14..a86e6e3e9f1 100644
--- a/lib/helpers/populate/removeDeselectedForeignField.js
+++ b/lib/helpers/populate/removeDeselectedForeignField.js
@@ -28,4 +28,4 @@ module.exports = function removeDeselectedForeignField(foreignFields, options, d
}
}
}
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/populate/validateRef.js b/lib/helpers/populate/validateRef.js
index 9dc2b6fc641..2b58e166cc2 100644
--- a/lib/helpers/populate/validateRef.js
+++ b/lib/helpers/populate/validateRef.js
@@ -16,4 +16,4 @@ function validateRef(ref, path) {
throw new MongooseError('Invalid ref at path "' + path + '". Got ' +
util.inspect(ref, { depth: 0 }));
-}
\ No newline at end of file
+}
diff --git a/lib/helpers/printJestWarning.js b/lib/helpers/printJestWarning.js
index cc112e2bc88..00ba2d50c44 100644
--- a/lib/helpers/printJestWarning.js
+++ b/lib/helpers/printJestWarning.js
@@ -2,16 +2,20 @@
const utils = require('../utils');
-if (typeof jest !== 'undefined' && typeof window !== 'undefined') {
- utils.warn('Mongoose: looks like you\'re trying to test a Mongoose app ' +
- 'with Jest\'s default jsdom test environment. Please make sure you read ' +
- 'Mongoose\'s docs on configuring Jest to test Node.js apps: ' +
- 'http://mongoosejs.com/docs/jest.html');
-}
+if (typeof jest !== 'undefined' && !process.env.SUPPRESS_JEST_WARNINGS) {
+ if (typeof window !== 'undefined') {
+ utils.warn('Mongoose: looks like you\'re trying to test a Mongoose app ' +
+ 'with Jest\'s default jsdom test environment. Please make sure you read ' +
+ 'Mongoose\'s docs on configuring Jest to test Node.js apps: ' +
+ 'https://mongoosejs.com/docs/jest.html. Set the SUPPRESS_JEST_WARNINGS to true ' +
+ 'to hide this warning.');
+ }
-if (typeof jest !== 'undefined' && process.nextTick.toString().indexOf('nextTick') === -1) {
- utils.warn('Mongoose: looks like you\'re trying to test a Mongoose app ' +
- 'with Jest\'s mock timers enabled. Please make sure you read ' +
- 'Mongoose\'s docs on configuring Jest to test Node.js apps: ' +
- 'http://mongoosejs.com/docs/jest.html');
-}
\ No newline at end of file
+ if (setTimeout.clock != null && typeof setTimeout.clock.Date === 'function') {
+ utils.warn('Mongoose: looks like you\'re trying to test a Mongoose app ' +
+ 'with Jest\'s mock timers enabled. Please make sure you read ' +
+ 'Mongoose\'s docs on configuring Jest to test Node.js apps: ' +
+ 'https://mongoosejs.com/docs/jest.html. Set the SUPPRESS_JEST_WARNINGS to true ' +
+ 'to hide this warning.');
+ }
+}
diff --git a/lib/helpers/printStrictQueryWarning.js b/lib/helpers/printStrictQueryWarning.js
new file mode 100644
index 00000000000..3cf4fdf4341
--- /dev/null
+++ b/lib/helpers/printStrictQueryWarning.js
@@ -0,0 +1,11 @@
+'use strict';
+
+const util = require('util');
+
+module.exports = util.deprecate(
+ function() { },
+ 'Mongoose: the `strictQuery` option will be switched back to `false` by default ' +
+ 'in Mongoose 7. Use `mongoose.set(\'strictQuery\', false);` if you want to prepare ' +
+ 'for this change. Or use `mongoose.set(\'strictQuery\', true);` to suppress this warning.',
+ 'MONGOOSE'
+);
diff --git a/lib/helpers/processConnectionOptions.js b/lib/helpers/processConnectionOptions.js
index 214a0096050..1dbb767ebee 100644
--- a/lib/helpers/processConnectionOptions.js
+++ b/lib/helpers/processConnectionOptions.js
@@ -9,11 +9,12 @@ function processConnectionOptions(uri, options) {
? opts.readPreference
: getUriReadPreference(uri);
- const resolvedOpts = (readPreference && readPreference !== 'primary')
- ? resolveOptsConflicts(readPreference, opts)
- : opts;
+ const clonedOpts = clone(opts);
+ const resolvedOpts = (readPreference && readPreference !== 'primary' && readPreference !== 'primaryPreferred')
+ ? resolveOptsConflicts(readPreference, clonedOpts)
+ : clonedOpts;
- return clone(resolvedOpts);
+ return resolvedOpts;
}
function resolveOptsConflicts(pref, opts) {
diff --git a/lib/helpers/projection/applyProjection.js b/lib/helpers/projection/applyProjection.js
new file mode 100644
index 00000000000..1552e07e686
--- /dev/null
+++ b/lib/helpers/projection/applyProjection.js
@@ -0,0 +1,77 @@
+'use strict';
+
+const hasIncludedChildren = require('./hasIncludedChildren');
+const isExclusive = require('./isExclusive');
+const isInclusive = require('./isInclusive');
+const isPOJO = require('../../utils').isPOJO;
+
+module.exports = function applyProjection(doc, projection, _hasIncludedChildren) {
+ if (projection == null) {
+ return doc;
+ }
+ if (doc == null) {
+ return doc;
+ }
+
+ let exclude = null;
+ if (isInclusive(projection)) {
+ exclude = false;
+ } else if (isExclusive(projection)) {
+ exclude = true;
+ }
+
+ if (exclude == null) {
+ return doc;
+ } else if (exclude) {
+ _hasIncludedChildren = _hasIncludedChildren || hasIncludedChildren(projection);
+ return applyExclusiveProjection(doc, projection, _hasIncludedChildren);
+ } else {
+ _hasIncludedChildren = _hasIncludedChildren || hasIncludedChildren(projection);
+ return applyInclusiveProjection(doc, projection, _hasIncludedChildren);
+ }
+};
+
+function applyExclusiveProjection(doc, projection, hasIncludedChildren, projectionLimb, prefix) {
+ if (doc == null || typeof doc !== 'object') {
+ return doc;
+ }
+ const ret = { ...doc };
+ projectionLimb = prefix ? (projectionLimb || {}) : projection;
+
+ for (const key of Object.keys(ret)) {
+ const fullPath = prefix ? prefix + '.' + key : key;
+ if (projection.hasOwnProperty(fullPath) || projectionLimb.hasOwnProperty(key)) {
+ if (isPOJO(projection[fullPath]) || isPOJO(projectionLimb[key])) {
+ ret[key] = applyExclusiveProjection(ret[key], projection, hasIncludedChildren, projectionLimb[key], fullPath);
+ } else {
+ delete ret[key];
+ }
+ } else if (hasIncludedChildren[fullPath]) {
+ ret[key] = applyExclusiveProjection(ret[key], projection, hasIncludedChildren, projectionLimb[key], fullPath);
+ }
+ }
+ return ret;
+}
+
+function applyInclusiveProjection(doc, projection, hasIncludedChildren, projectionLimb, prefix) {
+ if (doc == null || typeof doc !== 'object') {
+ return doc;
+ }
+ const ret = { ...doc };
+ projectionLimb = prefix ? (projectionLimb || {}) : projection;
+
+ for (const key of Object.keys(ret)) {
+ const fullPath = prefix ? prefix + '.' + key : key;
+ if (projection.hasOwnProperty(fullPath) || projectionLimb.hasOwnProperty(key)) {
+ if (isPOJO(projection[fullPath]) || isPOJO(projectionLimb[key])) {
+ ret[key] = applyInclusiveProjection(ret[key], projection, hasIncludedChildren, projectionLimb[key], fullPath);
+ }
+ continue;
+ } else if (hasIncludedChildren[fullPath]) {
+ ret[key] = applyInclusiveProjection(ret[key], projection, hasIncludedChildren, projectionLimb[key], fullPath);
+ } else {
+ delete ret[key];
+ }
+ }
+ return ret;
+}
diff --git a/lib/helpers/projection/hasIncludedChildren.js b/lib/helpers/projection/hasIncludedChildren.js
new file mode 100644
index 00000000000..50afc5adfb7
--- /dev/null
+++ b/lib/helpers/projection/hasIncludedChildren.js
@@ -0,0 +1,41 @@
+'use strict';
+
+/**
+ * Creates an object that precomputes whether a given path has child fields in
+ * the projection.
+ *
+ * #### Example:
+ *
+ * const res = hasIncludedChildren({ 'a.b.c': 0 });
+ * res.a; // 1
+ * res['a.b']; // 1
+ * res['a.b.c']; // 1
+ * res['a.c']; // undefined
+ *
+ * @param {Object} fields
+ * @api private
+ */
+
+module.exports = function hasIncludedChildren(fields) {
+ const hasIncludedChildren = {};
+ const keys = Object.keys(fields);
+
+ for (const key of keys) {
+
+ if (key.indexOf('.') === -1) {
+ hasIncludedChildren[key] = 1;
+ continue;
+ }
+ const parts = key.split('.');
+ let c = parts[0];
+
+ for (let i = 0; i < parts.length; ++i) {
+ hasIncludedChildren[c] = 1;
+ if (i + 1 < parts.length) {
+ c = c + '.' + parts[i + 1];
+ }
+ }
+ }
+
+ return hasIncludedChildren;
+};
diff --git a/lib/helpers/projection/isExclusive.js b/lib/helpers/projection/isExclusive.js
index abc1bce72fd..a232857d601 100644
--- a/lib/helpers/projection/isExclusive.js
+++ b/lib/helpers/projection/isExclusive.js
@@ -21,8 +21,11 @@ module.exports = function isExclusive(projection) {
while (ki--) {
// Does this projection explicitly define inclusion/exclusion?
// Explicitly avoid `$meta` and `$slice`
- if (keys[ki] !== '_id' && isDefiningProjection(projection[keys[ki]])) {
- exclude = !projection[keys[ki]];
+ const key = keys[ki];
+ if (key !== '_id' && isDefiningProjection(projection[key])) {
+ exclude = (projection[key] != null && typeof projection[key] === 'object') ?
+ isExclusive(projection[key]) :
+ !projection[key];
break;
}
}
diff --git a/lib/helpers/projection/isInclusive.js b/lib/helpers/projection/isInclusive.js
index 098309ff33f..eebb412c4a3 100644
--- a/lib/helpers/projection/isInclusive.js
+++ b/lib/helpers/projection/isInclusive.js
@@ -26,7 +26,11 @@ module.exports = function isInclusive(projection) {
// If field is truthy (1, true, etc.) and not an object, then this
// projection must be inclusive. If object, assume its $meta, $slice, etc.
if (isDefiningProjection(projection[prop]) && !!projection[prop]) {
- return true;
+ if (projection[prop] != null && typeof projection[prop] === 'object') {
+ return isInclusive(projection[prop]);
+ } else {
+ return !!projection[prop];
+ }
}
}
diff --git a/lib/helpers/projection/isNestedProjection.js b/lib/helpers/projection/isNestedProjection.js
new file mode 100644
index 00000000000..f53d9cddf3a
--- /dev/null
+++ b/lib/helpers/projection/isNestedProjection.js
@@ -0,0 +1,8 @@
+'use strict';
+
+module.exports = function isNestedProjection(val) {
+ if (val == null || typeof val !== 'object') {
+ return false;
+ }
+ return val.$slice == null && val.$elemMatch == null && val.$meta == null && val.$ == null;
+};
diff --git a/lib/helpers/projection/isPathExcluded.js b/lib/helpers/projection/isPathExcluded.js
index fc2592cda52..e8f126b22da 100644
--- a/lib/helpers/projection/isPathExcluded.js
+++ b/lib/helpers/projection/isPathExcluded.js
@@ -2,15 +2,20 @@
const isDefiningProjection = require('./isDefiningProjection');
-/*!
+/**
* Determines if `path` is excluded by `projection`
*
* @param {Object} projection
- * @param {string} path
+ * @param {String} path
* @return {Boolean}
+ * @api private
*/
module.exports = function isPathExcluded(projection, path) {
+ if (projection == null) {
+ return false;
+ }
+
if (path === '_id') {
return projection._id === 0;
}
diff --git a/lib/helpers/projection/isSubpath.js b/lib/helpers/projection/isSubpath.js
new file mode 100644
index 00000000000..bec82f83d9a
--- /dev/null
+++ b/lib/helpers/projection/isSubpath.js
@@ -0,0 +1,14 @@
+'use strict';
+
+/**
+ * Determines if `path2` is a subpath of or equal to `path1`
+ *
+ * @param {string} path1
+ * @param {string} path2
+ * @return {Boolean}
+ * @api private
+ */
+
+module.exports = function isSubpath(path1, path2) {
+ return path1 === path2 || path2.startsWith(path1 + '.');
+};
diff --git a/lib/helpers/projection/parseProjection.js b/lib/helpers/projection/parseProjection.js
index d2a44b10653..479b52353b6 100644
--- a/lib/helpers/projection/parseProjection.js
+++ b/lib/helpers/projection/parseProjection.js
@@ -30,4 +30,4 @@ module.exports = function parseProjection(v, retainMinusPaths) {
}
return ret;
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/promiseOrCallback.js b/lib/helpers/promiseOrCallback.js
index 69ba38530c0..4282a6ce498 100644
--- a/lib/helpers/promiseOrCallback.js
+++ b/lib/helpers/promiseOrCallback.js
@@ -7,23 +7,32 @@ const emittedSymbol = Symbol('mongoose:emitted');
module.exports = function promiseOrCallback(callback, fn, ee, Promise) {
if (typeof callback === 'function') {
- return fn(function(error) {
- if (error != null) {
- if (ee != null && ee.listeners != null && ee.listeners('error').length > 0 && !error[emittedSymbol]) {
- error[emittedSymbol] = true;
- ee.emit('error', error);
+ try {
+ return fn(function(error) {
+ if (error != null) {
+ if (ee != null && ee.listeners != null && ee.listeners('error').length > 0 && !error[emittedSymbol]) {
+ error[emittedSymbol] = true;
+ ee.emit('error', error);
+ }
+ try {
+ callback(error);
+ } catch (error) {
+ return immediate(() => {
+ throw error;
+ });
+ }
+ return;
}
- try {
- callback(error);
- } catch (error) {
- return immediate(() => {
- throw error;
- });
- }
- return;
+ callback.apply(this, arguments);
+ });
+ } catch (error) {
+ if (ee != null && ee.listeners != null && ee.listeners('error').length > 0 && !error[emittedSymbol]) {
+ error[emittedSymbol] = true;
+ ee.emit('error', error);
}
- callback.apply(this, arguments);
- });
+
+ return callback(error);
+ }
}
Promise = Promise || PromiseProvider.get();
diff --git a/lib/helpers/query/applyGlobalMaxTimeMS.js b/lib/helpers/query/applyGlobalMaxTimeMS.js
deleted file mode 100644
index cb4926055b1..00000000000
--- a/lib/helpers/query/applyGlobalMaxTimeMS.js
+++ /dev/null
@@ -1,15 +0,0 @@
-'use strict';
-
-const utils = require('../../utils');
-
-module.exports = function applyGlobalMaxTimeMS(options, model) {
- if (utils.hasUserDefinedProperty(options, 'maxTimeMS')) {
- return;
- }
-
- if (utils.hasUserDefinedProperty(model.db.options, 'maxTimeMS')) {
- options.maxTimeMS = model.db.options.maxTimeMS;
- } else if (utils.hasUserDefinedProperty(model.base.options, 'maxTimeMS')) {
- options.maxTimeMS = model.base.options.maxTimeMS;
- }
-};
\ No newline at end of file
diff --git a/lib/helpers/query/applyGlobalOption.js b/lib/helpers/query/applyGlobalOption.js
new file mode 100644
index 00000000000..8888e368b9e
--- /dev/null
+++ b/lib/helpers/query/applyGlobalOption.js
@@ -0,0 +1,29 @@
+'use strict';
+
+const utils = require('../../utils');
+
+function applyGlobalMaxTimeMS(options, model) {
+ applyGlobalOption(options, model, 'maxTimeMS');
+}
+
+function applyGlobalDiskUse(options, model) {
+ applyGlobalOption(options, model, 'allowDiskUse');
+}
+
+module.exports = {
+ applyGlobalMaxTimeMS,
+ applyGlobalDiskUse
+};
+
+
+function applyGlobalOption(options, model, optionName) {
+ if (utils.hasUserDefinedProperty(options, optionName)) {
+ return;
+ }
+
+ if (utils.hasUserDefinedProperty(model.db.options, optionName)) {
+ options[optionName] = model.db.options[optionName];
+ } else if (utils.hasUserDefinedProperty(model.base.options, optionName)) {
+ options[optionName] = model.base.options[optionName];
+ }
+}
diff --git a/lib/helpers/query/applyQueryMiddleware.js b/lib/helpers/query/applyQueryMiddleware.js
index 99e096b19e0..692d69cee3a 100644
--- a/lib/helpers/query/applyQueryMiddleware.js
+++ b/lib/helpers/query/applyQueryMiddleware.js
@@ -16,11 +16,12 @@ applyQueryMiddleware.middlewareFunctions = validOps.concat([
'validate'
]);
-/*!
+/**
* Apply query middleware
*
- * @param {Query} query constructor
+ * @param {Query} Query constructor
* @param {Model} model
+ * @api private
*/
function applyQueryMiddleware(Query, model) {
@@ -75,4 +76,4 @@ function _getContexts(hook) {
ret.document = hook.document;
}
return ret;
-}
\ No newline at end of file
+}
diff --git a/lib/helpers/query/cast$expr.js b/lib/helpers/query/cast$expr.js
new file mode 100644
index 00000000000..dd4102d80e0
--- /dev/null
+++ b/lib/helpers/query/cast$expr.js
@@ -0,0 +1,282 @@
+'use strict';
+
+const CastError = require('../../error/cast');
+const StrictModeError = require('../../error/strict');
+const castNumber = require('../../cast/number');
+
+const booleanComparison = new Set(['$and', '$or']);
+const comparisonOperator = new Set(['$cmp', '$eq', '$lt', '$lte', '$gt', '$gte']);
+const arithmeticOperatorArray = new Set([
+ // avoid casting '$add' or '$subtract', because expressions can be either number or date,
+ // and we don't have a good way of inferring which arguments should be numbers and which should
+ // be dates.
+ '$multiply',
+ '$divide',
+ '$log',
+ '$mod',
+ '$trunc',
+ '$avg',
+ '$max',
+ '$min',
+ '$stdDevPop',
+ '$stdDevSamp',
+ '$sum'
+]);
+const arithmeticOperatorNumber = new Set([
+ '$abs',
+ '$exp',
+ '$ceil',
+ '$floor',
+ '$ln',
+ '$log10',
+ '$round',
+ '$sqrt',
+ '$sin',
+ '$cos',
+ '$tan',
+ '$asin',
+ '$acos',
+ '$atan',
+ '$atan2',
+ '$asinh',
+ '$acosh',
+ '$atanh',
+ '$sinh',
+ '$cosh',
+ '$tanh',
+ '$degreesToRadians',
+ '$radiansToDegrees'
+]);
+const arrayElementOperators = new Set([
+ '$arrayElemAt',
+ '$first',
+ '$last'
+]);
+const dateOperators = new Set([
+ '$year',
+ '$month',
+ '$week',
+ '$dayOfMonth',
+ '$dayOfYear',
+ '$hour',
+ '$minute',
+ '$second',
+ '$isoDayOfWeek',
+ '$isoWeekYear',
+ '$isoWeek',
+ '$millisecond'
+]);
+const expressionOperator = new Set(['$not']);
+
+module.exports = function cast$expr(val, schema, strictQuery) {
+ if (typeof val !== 'object' || val === null) {
+ throw new Error('`$expr` must be an object');
+ }
+
+ return _castExpression(val, schema, strictQuery);
+};
+
+function _castExpression(val, schema, strictQuery) {
+ // Preserve the value if it represents a path or if it's null
+ if (isPath(val) || val === null) {
+ return val;
+ }
+
+ if (val.$cond != null) {
+ if (Array.isArray(val.$cond)) {
+ val.$cond = val.$cond.map(expr => _castExpression(expr, schema, strictQuery));
+ } else {
+ val.$cond.if = _castExpression(val.$cond.if, schema, strictQuery);
+ val.$cond.then = _castExpression(val.$cond.then, schema, strictQuery);
+ val.$cond.else = _castExpression(val.$cond.else, schema, strictQuery);
+ }
+ } else if (val.$ifNull != null) {
+ val.$ifNull.map(v => _castExpression(v, schema, strictQuery));
+ } else if (val.$switch != null) {
+ val.branches.map(v => _castExpression(v, schema, strictQuery));
+ val.default = _castExpression(val.default, schema, strictQuery);
+ }
+
+ const keys = Object.keys(val);
+ for (const key of keys) {
+ if (booleanComparison.has(key)) {
+ val[key] = val[key].map(v => _castExpression(v, schema, strictQuery));
+ } else if (comparisonOperator.has(key)) {
+ val[key] = castComparison(val[key], schema, strictQuery);
+ } else if (arithmeticOperatorArray.has(key)) {
+ val[key] = castArithmetic(val[key], schema, strictQuery);
+ } else if (arithmeticOperatorNumber.has(key)) {
+ val[key] = castNumberOperator(val[key], schema, strictQuery);
+ } else if (expressionOperator.has(key)) {
+ val[key] = _castExpression(val[key], schema, strictQuery);
+ }
+ }
+
+ if (val.$in) {
+ val.$in = castIn(val.$in, schema, strictQuery);
+ }
+ if (val.$size) {
+ val.$size = castNumberOperator(val.$size, schema, strictQuery);
+ }
+
+ _omitUndefined(val);
+
+ return val;
+}
+
+function _omitUndefined(val) {
+ const keys = Object.keys(val);
+ for (let i = 0, len = keys.length; i < len; ++i) {
+ (val[keys[i]] === void 0) && delete val[keys[i]];
+ }
+}
+
+// { $op: <number> }
+function castNumberOperator(val) {
+ if (!isLiteral(val)) {
+ return val;
+ }
+
+ try {
+ return castNumber(val);
+ } catch (err) {
+ throw new CastError('Number', val);
+ }
+}
+
+function castIn(val, schema, strictQuery) {
+ const path = val[1];
+ if (!isPath(path)) {
+ return val;
+ }
+ const search = val[0];
+
+ const schematype = schema.path(path.slice(1));
+ if (schematype === null) {
+ if (strictQuery === false) {
+ return val;
+ } else if (strictQuery === 'throw') {
+ throw new StrictModeError('$in');
+ }
+
+ return void 0;
+ }
+
+ if (!schematype.$isMongooseArray) {
+ throw new Error('Path must be an array for $in');
+ }
+
+ return [
+ schematype.$isMongooseDocumentArray ? schematype.$embeddedSchemaType.cast(search) : schematype.caster.cast(search),
+ path
+ ];
+}
+
+// { $op: [<number>, <number>] }
+function castArithmetic(val) {
+ if (!Array.isArray(val)) {
+ if (!isLiteral(val)) {
+ return val;
+ }
+ try {
+ return castNumber(val);
+ } catch (err) {
+ throw new CastError('Number', val);
+ }
+ }
+
+ return val.map(v => {
+ if (!isLiteral(v)) {
+ return v;
+ }
+ try {
+ return castNumber(v);
+ } catch (err) {
+ throw new CastError('Number', v);
+ }
+ });
+}
+
+// { $op: [expression, expression] }
+function castComparison(val, schema, strictQuery) {
+ if (!Array.isArray(val) || val.length !== 2) {
+ throw new Error('Comparison operator must be an array of length 2');
+ }
+
+ val[0] = _castExpression(val[0], schema, strictQuery);
+ const lhs = val[0];
+
+ if (isLiteral(val[1])) {
+ let path = null;
+ let schematype = null;
+ let caster = null;
+ if (isPath(lhs)) {
+ path = lhs.slice(1);
+ schematype = schema.path(path);
+ } else if (typeof lhs === 'object' && lhs != null) {
+ for (const key of Object.keys(lhs)) {
+ if (dateOperators.has(key) && isPath(lhs[key])) {
+ path = lhs[key].slice(1) + '.' + key;
+ caster = castNumber;
+ } else if (arrayElementOperators.has(key) && isPath(lhs[key])) {
+ path = lhs[key].slice(1) + '.' + key;
+ schematype = schema.path(lhs[key].slice(1));
+ if (schematype != null) {
+ if (schematype.$isMongooseDocumentArray) {
+ schematype = schematype.$embeddedSchemaType;
+ } else if (schematype.$isMongooseArray) {
+ schematype = schematype.caster;
+ }
+ }
+ }
+ }
+ }
+
+ const is$literal = typeof val[1] === 'object' && val[1] != null && val[1].$literal != null;
+ if (schematype != null) {
+ if (is$literal) {
+ val[1] = { $literal: schematype.cast(val[1].$literal) };
+ } else {
+ val[1] = schematype.cast(val[1]);
+ }
+ } else if (caster != null) {
+ if (is$literal) {
+ try {
+ val[1] = { $literal: caster(val[1].$literal) };
+ } catch (err) {
+ throw new CastError(caster.name.replace(/^cast/, ''), val[1], path + '.$literal');
+ }
+ } else {
+ try {
+ val[1] = caster(val[1]);
+ } catch (err) {
+ throw new CastError(caster.name.replace(/^cast/, ''), val[1], path);
+ }
+ }
+ } else if (path != null && strictQuery === true) {
+ return void 0;
+ } else if (path != null && strictQuery === 'throw') {
+ throw new StrictModeError(path);
+ }
+ } else {
+ val[1] = _castExpression(val[1]);
+ }
+
+ return val;
+}
+
+function isPath(val) {
+ return typeof val === 'string' && val[0] === '$';
+}
+
+function isLiteral(val) {
+ if (typeof val === 'string' && val[0] === '$') {
+ return false;
+ }
+ if (typeof val === 'object' && val !== null && Object.keys(val).find(key => key[0] === '$')) {
+ // The `$literal` expression can make an object a literal
+ // https://www.mongodb.com/docs/manual/reference/operator/aggregation/literal/#mongodb-expression-exp.-literal
+ return val.$literal != null;
+ }
+ return true;
+}
diff --git a/lib/helpers/query/castFilterPath.js b/lib/helpers/query/castFilterPath.js
index 42b8460ceda..8175145cb6d 100644
--- a/lib/helpers/query/castFilterPath.js
+++ b/lib/helpers/query/castFilterPath.js
@@ -41,7 +41,6 @@ module.exports = function castFilterPath(query, schematype, val) {
}
continue;
}
- // cast(schematype.caster ? schematype.caster.schema : schema, nested, options, context);
} else {
val[$cond] = schematype.castForQueryWrapper({
$conditional: $cond,
@@ -52,4 +51,4 @@ module.exports = function castFilterPath(query, schematype, val) {
}
return val;
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/query/castUpdate.js b/lib/helpers/query/castUpdate.js
index 21b7f947ca7..e9c12c3ebce 100644
--- a/lib/helpers/query/castUpdate.js
+++ b/lib/helpers/query/castUpdate.js
@@ -14,16 +14,17 @@ const schemaMixedSymbol = require('../../schema/symbols').schemaMixedSymbol;
const setDottedPath = require('../path/setDottedPath');
const utils = require('../../utils');
-/*!
+/**
* Casts an update op based on the given schema
*
* @param {Schema} schema
* @param {Object} obj
- * @param {Object} options
+ * @param {Object} [options]
* @param {Boolean} [options.overwrite] defaults to false
* @param {Boolean|String} [options.strict] defaults to true
* @param {Query} context passed to setters
* @return {Boolean} true iff the update is non-empty
+ * @api private
*/
module.exports = function castUpdate(schema, obj, options, context, filter) {
if (obj == null) {
@@ -41,12 +42,8 @@ module.exports = function castUpdate(schema, obj, options, context, filter) {
}
return obj;
}
- if (schema.options.strict === 'throw' && obj.hasOwnProperty(schema.options.discriminatorKey)) {
- throw new StrictModeError(schema.options.discriminatorKey);
- } else if (context._mongooseOptions != null && !context._mongooseOptions.overwriteDiscriminatorKey) {
- delete obj[schema.options.discriminatorKey];
- }
- if (options.upsert) {
+
+ if (options.upsert && !options.overwrite) {
moveImmutableProperties(schema, obj, context);
}
@@ -122,9 +119,9 @@ module.exports = function castUpdate(schema, obj, options, context, filter) {
function castPipelineOperator(op, val) {
if (op === '$unset') {
- if (!Array.isArray(val) || val.find(v => typeof v !== 'string')) {
+ if (typeof val !== 'string' && (!Array.isArray(val) || val.find(v => typeof v !== 'string'))) {
throw new MongooseError('Invalid $unset in pipeline, must be ' +
- 'an array of strings');
+ ' a string or an array of strings');
}
return val;
}
@@ -149,17 +146,18 @@ function castPipelineOperator(op, val) {
throw new MongooseError('Invalid update pipeline operator: "' + op + '"');
}
-/*!
+/**
* Walk each path of obj and cast its values
* according to its schema.
*
* @param {Schema} schema
- * @param {Object} obj - part of a query
- * @param {String} op - the atomic operator ($pull, $set, etc)
- * @param {Object} options
+ * @param {Object} obj part of a query
+ * @param {String} op the atomic operator ($pull, $set, etc)
+ * @param {Object} [options]
* @param {Boolean|String} [options.strict]
* @param {Query} context
- * @param {String} pref - path prefix (internal only)
+ * @param {Object} filter
+ * @param {String} pref path prefix (internal only)
* @return {Bool} true if this path has keys to update
* @api private
*/
@@ -176,6 +174,8 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
let aggregatedError = null;
+ const strictMode = strict != null ? strict : schema.options.strict;
+
while (i--) {
key = keys[i];
val = obj[key];
@@ -191,6 +191,23 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
}
}
+ const discriminatorKey = (prefix ? prefix + key : key);
+ if (
+ schema.discriminatorMapping != null &&
+ discriminatorKey === schema.options.discriminatorKey &&
+ schema.discriminatorMapping.value !== obj[key] &&
+ !options.overwriteDiscriminatorKey
+ ) {
+ if (strictMode === 'throw') {
+ const err = new Error('Can\'t modify discriminator key "' + discriminatorKey + '" on discriminator model');
+ aggregatedError = _appendError(err, context, discriminatorKey, aggregatedError);
+ continue;
+ } else if (strictMode) {
+ delete obj[key];
+ continue;
+ }
+ }
+
if (getConstructorName(val) === 'Object') {
// watch for embedded doc schemas
schematype = schema._getSchema(prefix + key);
@@ -203,6 +220,7 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
}
if (op !== '$setOnInsert' &&
+ !options.overwrite &&
handleImmutable(schematype, strict, obj, key, prefix + key, context)) {
continue;
}
@@ -216,7 +234,7 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
$each: castUpdateVal(schematype, val.$each, op, key, context, prefix + key)
};
} catch (error) {
- aggregatedError = _handleCastError(error, context, key, aggregatedError);
+ aggregatedError = _appendError(error, context, key, aggregatedError);
}
if (val.$slice != null) {
@@ -236,13 +254,13 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
try {
obj[key] = schematype.castForQuery(val, context, { strict: _strict });
} catch (error) {
- aggregatedError = _handleCastError(error, context, key, aggregatedError);
+ aggregatedError = _appendError(error, context, key, aggregatedError);
}
} else {
try {
obj[key] = castUpdateVal(schematype, val, op, key, context, prefix + key);
} catch (error) {
- aggregatedError = _handleCastError(error, context, key, aggregatedError);
+ aggregatedError = _appendError(error, context, key, aggregatedError);
}
}
@@ -258,7 +276,7 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
try {
obj[key] = castUpdateVal(schematype, val, op, key, context, prefix + key);
} catch (error) {
- aggregatedError = _handleCastError(error, context, key, aggregatedError);
+ aggregatedError = _appendError(error, context, key, aggregatedError);
}
if (obj[key] === void 0) {
@@ -297,6 +315,7 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
// You can use `$setOnInsert` with immutable keys
if (op !== '$setOnInsert' &&
+ !options.overwrite &&
handleImmutable(schematype, strict, obj, key, prefix + key, context)) {
continue;
}
@@ -349,11 +368,14 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
delete obj[key];
}
} catch (error) {
- aggregatedError = _handleCastError(error, context, key, aggregatedError);
+ aggregatedError = _appendError(error, context, key, aggregatedError);
}
if (Array.isArray(obj[key]) && (op === '$addToSet' || op === '$push') && key !== '$each') {
- if (schematype && schematype.caster && !schematype.caster.$isMongooseArray) {
+ if (schematype &&
+ schematype.caster &&
+ !schematype.caster.$isMongooseArray &&
+ !schematype.caster[schemaMixedSymbol]) {
obj[key] = { $each: obj[key] };
}
}
@@ -379,7 +401,7 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
* ignore
*/
-function _handleCastError(error, query, key, aggregatedError) {
+function _appendError(error, query, key, aggregatedError) {
if (typeof query !== 'object' || !query.options.multipleCastError) {
throw error;
}
@@ -388,9 +410,10 @@ function _handleCastError(error, query, key, aggregatedError) {
return aggregatedError;
}
-/*!
+/**
* These operators should be cast to numbers instead
* of their path schema type.
+ * @api private
*/
const numberOps = {
@@ -398,17 +421,19 @@ const numberOps = {
$inc: 1
};
-/*!
+/**
* These ops require no casting because the RHS doesn't do anything.
+ * @api private
*/
const noCastOps = {
$unset: 1
};
-/*!
+/**
* These operators require casting docs
* to real Documents for Update operations.
+ * @api private
*/
const castOps = {
@@ -427,14 +452,15 @@ const overwriteOps = {
$setOnInsert: 1
};
-/*!
+/**
* Casts `val` according to `schema` and atomic `op`.
*
* @param {SchemaType} schema
* @param {Object} val
- * @param {String} op - the atomic operator ($pull, $set, etc)
+ * @param {String} op the atomic operator ($pull, $set, etc)
* @param {String} $conditional
* @param {Query} context
+ * @param {String} path
* @api private
*/
@@ -451,6 +477,8 @@ function castUpdateVal(schema, val, op, $conditional, context, path) {
return val;
}
+ // console.log('CastUpdateVal', path, op, val, schema);
+
const cond = schema.caster && op in castOps &&
(utils.isObject(val) || Array.isArray(val));
if (cond && !overwriteOps[op]) {
diff --git a/lib/helpers/query/completeMany.js b/lib/helpers/query/completeMany.js
index b52da432a3e..4ba8c2b083e 100644
--- a/lib/helpers/query/completeMany.js
+++ b/lib/helpers/query/completeMany.js
@@ -5,7 +5,7 @@ const immediate = require('../immediate');
module.exports = completeMany;
-/*!
+/**
* Given a model and an array of docs, hydrates all the docs to be instances
* of the model. Used to initialize docs returned from the db from `find()`
*
@@ -13,10 +13,11 @@ module.exports = completeMany;
* @param {Array} docs
* @param {Object} fields the projection used, including `select` from schemas
* @param {Object} userProvidedFields the user-specified projection
- * @param {Object} opts
+ * @param {Object} [opts]
* @param {Array} [opts.populated]
* @param {ClientSession} [opts.session]
* @param {Function} callback
+ * @api private
*/
function completeMany(model, docs, fields, userProvidedFields, opts, callback) {
diff --git a/lib/helpers/query/getEmbeddedDiscriminatorPath.js b/lib/helpers/query/getEmbeddedDiscriminatorPath.js
index 2f3f39d97e0..f376916b590 100644
--- a/lib/helpers/query/getEmbeddedDiscriminatorPath.js
+++ b/lib/helpers/query/getEmbeddedDiscriminatorPath.js
@@ -5,9 +5,15 @@ const get = require('../get');
const getDiscriminatorByValue = require('../discriminator/getDiscriminatorByValue');
const updatedPathsByArrayFilter = require('../update/updatedPathsByArrayFilter');
-/*!
+/**
* Like `schema.path()`, except with a document, because impossible to
* determine path type without knowing the embedded discriminator key.
+ * @param {Schema} schema
+ * @param {Object} [update]
+ * @param {Object} [filter]
+ * @param {String} path
+ * @param {Object} [options]
+ * @api private
*/
module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, path, options) {
diff --git a/lib/helpers/query/hasDollarKeys.js b/lib/helpers/query/hasDollarKeys.js
index 09c40d775de..3e3b188c120 100644
--- a/lib/helpers/query/hasDollarKeys.js
+++ b/lib/helpers/query/hasDollarKeys.js
@@ -4,16 +4,20 @@
* ignore
*/
-module.exports = function(obj) {
- if (obj == null || typeof obj !== 'object') {
+module.exports = function hasDollarKeys(obj) {
+
+ if (typeof obj !== 'object' || obj === null) {
return false;
}
+
const keys = Object.keys(obj);
const len = keys.length;
+
for (let i = 0; i < len; ++i) {
- if (keys[i].startsWith('$')) {
+ if (keys[i][0] === '$') {
return true;
}
}
+
return false;
};
diff --git a/lib/helpers/query/isOperator.js b/lib/helpers/query/isOperator.js
index 3b9813920ff..04488591a6c 100644
--- a/lib/helpers/query/isOperator.js
+++ b/lib/helpers/query/isOperator.js
@@ -7,5 +7,8 @@ const specialKeys = new Set([
]);
module.exports = function isOperator(path) {
- return path.startsWith('$') && !specialKeys.has(path);
-};
\ No newline at end of file
+ return (
+ path[0] === '$' &&
+ !specialKeys.has(path)
+ );
+};
diff --git a/lib/helpers/query/sanitizeFilter.js b/lib/helpers/query/sanitizeFilter.js
index 4177a0c68e7..1147df369b9 100644
--- a/lib/helpers/query/sanitizeFilter.js
+++ b/lib/helpers/query/sanitizeFilter.js
@@ -35,4 +35,4 @@ module.exports = function sanitizeFilter(filter) {
}
return filter;
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/query/sanitizeProjection.js b/lib/helpers/query/sanitizeProjection.js
index dcb61fba910..0c034875946 100644
--- a/lib/helpers/query/sanitizeProjection.js
+++ b/lib/helpers/query/sanitizeProjection.js
@@ -11,4 +11,4 @@ module.exports = function sanitizeProjection(projection) {
projection[keys[i]] = 1;
}
}
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/query/trusted.js b/lib/helpers/query/trusted.js
index 98c6054ad48..1e460eb50e0 100644
--- a/lib/helpers/query/trusted.js
+++ b/lib/helpers/query/trusted.js
@@ -10,4 +10,4 @@ exports.trusted = function trusted(obj) {
}
obj[trustedSymbol] = true;
return obj;
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/query/validOps.js b/lib/helpers/query/validOps.js
index c0ac397f278..f554ff2d9b0 100644
--- a/lib/helpers/query/validOps.js
+++ b/lib/helpers/query/validOps.js
@@ -21,4 +21,4 @@ module.exports = Object.freeze([
'findOneAndDelete',
'findOneAndRemove',
'remove'
-]);
\ No newline at end of file
+]);
diff --git a/lib/helpers/query/wrapThunk.js b/lib/helpers/query/wrapThunk.js
index daa4681effe..1303a4708d6 100644
--- a/lib/helpers/query/wrapThunk.js
+++ b/lib/helpers/query/wrapThunk.js
@@ -2,13 +2,15 @@
const MongooseError = require('../../error/mongooseError');
-/*!
+/**
* A query thunk is the function responsible for sending the query to MongoDB,
* like `Query#_findOne()` or `Query#_execUpdate()`. The `Query#exec()` function
* calls a thunk. The term "thunk" here is the traditional Node.js definition:
* a function that takes exactly 1 parameter, a callback.
*
* This function defines common behavior for all query thunks.
+ * @param {Function} fn
+ * @api private
*/
module.exports = function wrapThunk(fn) {
@@ -26,4 +28,4 @@ module.exports = function wrapThunk(fn) {
fn.call(this, cb);
};
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/schema/addAutoId.js b/lib/helpers/schema/addAutoId.js
index 11a1f2302e4..82c0c603c34 100644
--- a/lib/helpers/schema/addAutoId.js
+++ b/lib/helpers/schema/addAutoId.js
@@ -4,4 +4,4 @@ module.exports = function addAutoId(schema) {
const _obj = { _id: { auto: true } };
_obj._id[schema.options.typeKey] = 'ObjectId';
schema.add(_obj);
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/schema/applyBuiltinPlugins.js b/lib/helpers/schema/applyBuiltinPlugins.js
new file mode 100644
index 00000000000..8bd7319cbb1
--- /dev/null
+++ b/lib/helpers/schema/applyBuiltinPlugins.js
@@ -0,0 +1,12 @@
+'use strict';
+
+const builtinPlugins = require('../../plugins');
+
+module.exports = function applyBuiltinPlugins(schema) {
+ for (const plugin of Object.values(builtinPlugins)) {
+ plugin(schema, { deduplicate: true });
+ }
+ schema.plugins = Object.values(builtinPlugins).
+ map(fn => ({ fn, opts: { deduplicate: true } })).
+ concat(schema.plugins);
+};
diff --git a/lib/helpers/schema/applyPlugins.js b/lib/helpers/schema/applyPlugins.js
index f1daf4012e8..fe976800771 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]);
}
}
@@ -41,4 +52,4 @@ module.exports = function applyPlugins(schema, plugins, options, cacheKey) {
applyPlugins(discriminatorSchema, plugins,
{ skipTopLevel: !applyPluginsToDiscriminators }, cacheKey);
}
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/schema/cleanPositionalOperators.js b/lib/helpers/schema/cleanPositionalOperators.js
index 905bb78820b..f104d0398a7 100644
--- a/lib/helpers/schema/cleanPositionalOperators.js
+++ b/lib/helpers/schema/cleanPositionalOperators.js
@@ -9,4 +9,4 @@ module.exports = function cleanPositionalOperators(path) {
return path.
replace(/\.\$(\[[^\]]*\])?(?=\.)/g, '.0').
replace(/\.\$(\[[^\]]*\])?$/g, '.0');
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/schema/getIndexes.js b/lib/helpers/schema/getIndexes.js
index be907dbd647..28e0a3e6f5b 100644
--- a/lib/helpers/schema/getIndexes.js
+++ b/lib/helpers/schema/getIndexes.js
@@ -2,10 +2,13 @@
const get = require('../get');
const helperIsObject = require('../isObject');
+const decorateDiscriminatorIndexOptions = require('../indexes/decorateDiscriminatorIndexOptions');
-/*!
+/**
* Gather all indexes defined in the schema, including single nested,
* document arrays, and embedded discriminators.
+ * @param {Schema} schema
+ * @api private
*/
module.exports = function getIndexes(schema) {
@@ -88,6 +91,7 @@ module.exports = function getIndexes(schema) {
}
const indexName = options && options.name;
+
if (typeof indexName === 'string') {
if (indexByName.has(indexName)) {
Object.assign(indexByName.get(indexName), field);
@@ -108,19 +112,24 @@ module.exports = function getIndexes(schema) {
fixSubIndexPaths(schema, prefix);
} else {
schema._indexes.forEach(function(index) {
- if (!('background' in index[1])) {
- index[1].background = true;
+ const options = index[1];
+ if (!('background' in options)) {
+ options.background = true;
}
+ decorateDiscriminatorIndexOptions(schema, options);
});
indexes = indexes.concat(schema._indexes);
}
}
- /*!
+ /**
* Checks for indexes added to subdocs using Schema.index().
* These indexes need their paths prefixed properly.
*
* schema._indexes = [ [indexObj, options], [indexObj, options] ..]
+ * @param {Schema} schema
+ * @param {String} prefix
+ * @api private
*/
function fixSubIndexPaths(schema, prefix) {
diff --git a/lib/helpers/schema/getKeysInSchemaOrder.js b/lib/helpers/schema/getKeysInSchemaOrder.js
index f84a88f0411..83b7fd88c06 100644
--- a/lib/helpers/schema/getKeysInSchemaOrder.js
+++ b/lib/helpers/schema/getKeysInSchemaOrder.js
@@ -25,4 +25,4 @@ module.exports = function getKeysInSchemaOrder(schema, val, path) {
}
return keys;
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/schema/getPath.js b/lib/helpers/schema/getPath.js
index ccbc67c81e2..b881069546c 100644
--- a/lib/helpers/schema/getPath.js
+++ b/lib/helpers/schema/getPath.js
@@ -1,8 +1,11 @@
'use strict';
-/*!
+const numberRE = /^\d+$/;
+
+/**
* Behaves like `Schema#path()`, except for it also digs into arrays without
* needing to put `.0.`, so `getPath(schema, 'docArr.elProp')` works.
+ * @api private
*/
module.exports = function getPath(schema, path) {
@@ -10,13 +13,12 @@ module.exports = function getPath(schema, path) {
if (schematype != null) {
return schematype;
}
-
const pieces = path.split('.');
let cur = '';
let isArray = false;
for (const piece of pieces) {
- if (/^\d+$/.test(piece) && isArray) {
+ if (isArray && numberRE.test(piece)) {
continue;
}
cur = cur.length === 0 ? piece : cur + '.' + piece;
@@ -25,11 +27,11 @@ module.exports = function getPath(schema, path) {
if (schematype != null && schematype.schema) {
schema = schematype.schema;
cur = '';
- if (schematype.$isMongooseDocumentArray) {
+ if (!isArray && schematype.$isMongooseDocumentArray) {
isArray = true;
}
}
}
return schematype;
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/schema/handleIdOption.js b/lib/helpers/schema/handleIdOption.js
index 569bf9f51b1..335ff2b754e 100644
--- a/lib/helpers/schema/handleIdOption.js
+++ b/lib/helpers/schema/handleIdOption.js
@@ -17,4 +17,4 @@ module.exports = function handleIdOption(schema, options) {
}
return schema;
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/schema/handleTimestampOption.js b/lib/helpers/schema/handleTimestampOption.js
index 1551b7c10f3..0f9c30c3619 100644
--- a/lib/helpers/schema/handleTimestampOption.js
+++ b/lib/helpers/schema/handleTimestampOption.js
@@ -21,4 +21,4 @@ function handleTimestampOption(arg, prop) {
return prop;
}
return arg[prop];
-}
\ No newline at end of file
+}
diff --git a/lib/helpers/schema/idGetter.js b/lib/helpers/schema/idGetter.js
index 5a0741773f8..31ea2ec8659 100644
--- a/lib/helpers/schema/idGetter.js
+++ b/lib/helpers/schema/idGetter.js
@@ -18,8 +18,9 @@ module.exports = function addIdGetter(schema) {
return schema;
};
-/*!
+/**
* Returns this documents _id cast to a string.
+ * @api private
*/
function idGetter() {
diff --git a/lib/helpers/schema/merge.js b/lib/helpers/schema/merge.js
index edc5175eedc..55ed82466f7 100644
--- a/lib/helpers/schema/merge.js
+++ b/lib/helpers/schema/merge.js
@@ -23,5 +23,6 @@ module.exports = function merge(s1, s2, skipConflictingPaths) {
s1.virtuals[virtual] = s2.virtuals[virtual].clone();
}
+ s1._indexes = s1._indexes.concat(s2._indexes || []);
s1.s.hooks.merge(s2.s.hooks, false);
};
diff --git a/lib/helpers/schematype/handleImmutable.js b/lib/helpers/schematype/handleImmutable.js
index a9a0b7f1087..cc22c914922 100644
--- a/lib/helpers/schematype/handleImmutable.js
+++ b/lib/helpers/schematype/handleImmutable.js
@@ -19,13 +19,16 @@ module.exports = function(schematype) {
};
function createImmutableSetter(path, immutable) {
- return function immutableSetter(v) {
+ return function immutableSetter(v, _priorVal, _doc, options) {
if (this == null || this.$__ == null) {
return v;
}
if (this.isNew) {
return v;
}
+ if (options && options.overwriteImmutable) {
+ return v;
+ }
const _immutable = typeof immutable === 'function' ?
immutable.call(this, this) :
diff --git a/lib/helpers/setDefaultsOnInsert.js b/lib/helpers/setDefaultsOnInsert.js
index 03cc4e87788..a94a5b35f1e 100644
--- a/lib/helpers/setDefaultsOnInsert.js
+++ b/lib/helpers/setDefaultsOnInsert.js
@@ -79,14 +79,23 @@ module.exports = function(filter, schema, castedDoc, options) {
return;
}
const def = schemaType.getDefault(null, true);
- if (!isModified(modified, path) && typeof def !== 'undefined') {
- castedDoc = castedDoc || {};
- castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
- if (get(castedDoc, path) == null) {
- castedDoc.$setOnInsert[path] = def;
- }
- updatedValues[path] = def;
+ if (isModified(modified, path)) {
+ return;
+ }
+ if (typeof def === 'undefined') {
+ return;
+ }
+ if (schemaType.splitPath().includes('$*')) {
+ // Skip defaults underneath maps. We should never do `$setOnInsert` on a path with `$*`
+ return;
+ }
+
+ castedDoc = castedDoc || {};
+ castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
+ if (get(castedDoc, path) == null) {
+ castedDoc.$setOnInsert[path] = def;
}
+ updatedValues[path] = def;
});
return castedDoc;
@@ -96,6 +105,8 @@ function isModified(modified, path) {
if (modified[path]) {
return true;
}
+
+ // Is any parent path of `path` modified?
const sp = path.split('.');
let cur = sp[0];
for (let i = 1; i < sp.length; ++i) {
@@ -104,5 +115,18 @@ function isModified(modified, path) {
}
cur += '.' + sp[i];
}
+
+ // Is any child of `path` modified?
+ const modifiedKeys = Object.keys(modified);
+ if (modifiedKeys.length) {
+ const parentPath = path + '.';
+
+ for (const modifiedPath of modifiedKeys) {
+ if (modifiedPath.slice(0, path.length + 1) === parentPath) {
+ return true;
+ }
+ }
+ }
+
return false;
}
diff --git a/lib/helpers/specialProperties.js b/lib/helpers/specialProperties.js
index 1e1aca50a23..8a961e4e833 100644
--- a/lib/helpers/specialProperties.js
+++ b/lib/helpers/specialProperties.js
@@ -1,3 +1,3 @@
'use strict';
-module.exports = new Set(['__proto__', 'constructor', 'prototype']);
\ No newline at end of file
+module.exports = new Set(['__proto__', 'constructor', 'prototype']);
diff --git a/lib/helpers/symbols.js b/lib/helpers/symbols.js
index 7e323e28510..f12db3d8272 100644
--- a/lib/helpers/symbols.js
+++ b/lib/helpers/symbols.js
@@ -17,4 +17,4 @@ exports.populateModelSymbol = Symbol('mongoose.PopulateOptions#Model');
exports.schemaTypeSymbol = Symbol('mongoose#schemaType');
exports.sessionNewDocuments = Symbol('mongoose:ClientSession#newDocuments');
exports.scopeSymbol = Symbol('mongoose#Document#scope');
-exports.validatorErrorSymbol = Symbol('mongoose:validatorError');
\ No newline at end of file
+exports.validatorErrorSymbol = Symbol('mongoose:validatorError');
diff --git a/lib/helpers/timers.js b/lib/helpers/timers.js
index 7bd09c7418c..eb7e6453f18 100644
--- a/lib/helpers/timers.js
+++ b/lib/helpers/timers.js
@@ -1,3 +1,3 @@
'use strict';
-exports.setTimeout = setTimeout;
\ No newline at end of file
+exports.setTimeout = setTimeout;
diff --git a/lib/helpers/timestamps/setDocumentTimestamps.js b/lib/helpers/timestamps/setDocumentTimestamps.js
new file mode 100644
index 00000000000..c1b6d5fc2c1
--- /dev/null
+++ b/lib/helpers/timestamps/setDocumentTimestamps.js
@@ -0,0 +1,26 @@
+'use strict';
+
+module.exports = function setDocumentTimestamps(doc, timestampOption, currentTime, createdAt, updatedAt) {
+ const skipUpdatedAt = timestampOption != null && timestampOption.updatedAt === false;
+ const skipCreatedAt = timestampOption != null && timestampOption.createdAt === false;
+
+ const defaultTimestamp = currentTime != null ?
+ currentTime() :
+ doc.ownerDocument().constructor.base.now();
+
+ if (!skipCreatedAt &&
+ (doc.isNew || doc.$isSubdocument) &&
+ createdAt &&
+ !doc.$__getValue(createdAt) &&
+ doc.$__isSelected(createdAt)) {
+ doc.$set(createdAt, defaultTimestamp, undefined, { overwriteImmutable: true });
+ }
+
+ if (!skipUpdatedAt && updatedAt && (doc.isNew || doc.$isModified())) {
+ let ts = defaultTimestamp;
+ if (doc.isNew && createdAt != null) {
+ ts = doc.$__getValue(createdAt);
+ }
+ doc.$set(updatedAt, ts);
+ }
+};
diff --git a/lib/helpers/timestamps/setupTimestamps.js b/lib/helpers/timestamps/setupTimestamps.js
index 8b398427253..ed44e7d938c 100644
--- a/lib/helpers/timestamps/setupTimestamps.js
+++ b/lib/helpers/timestamps/setupTimestamps.js
@@ -4,6 +4,7 @@ const applyTimestampsToChildren = require('../update/applyTimestampsToChildren')
const applyTimestampsToUpdate = require('../update/applyTimestampsToUpdate');
const get = require('../get');
const handleTimestampOption = require('../schema/handleTimestampOption');
+const setDocumentTimestamps = require('./setDocumentTimestamps');
const symbols = require('../../schema/symbols');
module.exports = function setupTimestamps(schema, timestamps) {
@@ -26,44 +27,25 @@ module.exports = function setupTimestamps(schema, timestamps) {
schema.$timestamps = { createdAt: createdAt, updatedAt: updatedAt };
+ if (createdAt && !schema.paths[createdAt]) {
+ const baseImmutableCreatedAt = schema.base != null ? schema.base.get('timestamps.createdAt.immutable') : null;
+ const immutable = baseImmutableCreatedAt != null ? baseImmutableCreatedAt : true;
+ schemaAdditions[createdAt] = { [schema.options.typeKey || 'type']: Date, immutable };
+ }
+
if (updatedAt && !schema.paths[updatedAt]) {
schemaAdditions[updatedAt] = Date;
}
- if (createdAt && !schema.paths[createdAt]) {
- schemaAdditions[createdAt] = { [schema.options.typeKey || 'type']: Date, immutable: true };
- }
schema.add(schemaAdditions);
- schema.pre('save', function(next) {
+ schema.pre('save', function timestampsPreSave(next) {
const timestampOption = get(this, '$__.saveOptions.timestamps');
if (timestampOption === false) {
return next();
}
- const skipUpdatedAt = timestampOption != null && timestampOption.updatedAt === false;
- const skipCreatedAt = timestampOption != null && timestampOption.createdAt === false;
-
- const defaultTimestamp = currentTime != null ?
- currentTime() :
- (this.ownerDocument ? this.ownerDocument() : this).constructor.base.now();
- const auto_id = this._id && this._id.auto;
-
- if (!skipCreatedAt && createdAt && !this.$__getValue(createdAt) && this.$__isSelected(createdAt)) {
- this.$set(createdAt, auto_id ? this._id.getTimestamp() : defaultTimestamp);
- }
-
- if (!skipUpdatedAt && updatedAt && (this.isNew || this.$isModified())) {
- let ts = defaultTimestamp;
- if (this.isNew) {
- if (createdAt != null) {
- ts = this.$__getValue(createdAt);
- } else if (auto_id) {
- ts = this._id.getTimestamp();
- }
- }
- this.$set(updatedAt, ts);
- }
+ setDocumentTimestamps(this, timestampOption, currentTime, createdAt, updatedAt);
next();
});
@@ -78,6 +60,18 @@ module.exports = function setupTimestamps(schema, timestamps) {
if (updatedAt && !this.get(updatedAt)) {
this.$set(updatedAt, ts);
}
+
+ if (this.$isSubdocument) {
+ return this;
+ }
+
+ const subdocs = this.$getAllSubdocs();
+ for (const subdoc of subdocs) {
+ if (subdoc.initializeTimestamps) {
+ subdoc.initializeTimestamps();
+ }
+ }
+
return this;
};
@@ -104,4 +98,4 @@ module.exports = function setupTimestamps(schema, timestamps) {
applyTimestampsToChildren(now, this.getUpdate(), this.model.schema);
next();
}
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/topology/allServersUnknown.js b/lib/helpers/topology/allServersUnknown.js
index 33d23ab3e7e..45c40ba4d22 100644
--- a/lib/helpers/topology/allServersUnknown.js
+++ b/lib/helpers/topology/allServersUnknown.js
@@ -9,4 +9,4 @@ module.exports = function allServersUnknown(topologyDescription) {
const servers = Array.from(topologyDescription.servers.values());
return servers.length > 0 && servers.every(server => server.type === 'Unknown');
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/topology/isAtlas.js b/lib/helpers/topology/isAtlas.js
index f0638d55136..445a8b49cd3 100644
--- a/lib/helpers/topology/isAtlas.js
+++ b/lib/helpers/topology/isAtlas.js
@@ -2,12 +2,30 @@
const getConstructorName = require('../getConstructorName');
+/**
+ * @typedef { import('mongodb').TopologyDescription } TopologyDescription
+ */
+
+/**
+ * Checks if topologyDescription contains servers connected to an atlas instance
+ *
+ * @param {TopologyDescription} topologyDescription
+ * @returns {boolean}
+ */
module.exports = function isAtlas(topologyDescription) {
if (getConstructorName(topologyDescription) !== 'TopologyDescription') {
return false;
}
- const hostnames = Array.from(topologyDescription.servers.keys());
- return hostnames.length > 0 &&
- hostnames.every(host => host.endsWith('.mongodb.net:27017'));
-};
\ No newline at end of file
+ if (topologyDescription.servers.size === 0) {
+ return false;
+ }
+
+ for (const server of topologyDescription.servers.values()) {
+ if (server.host.endsWith('.mongodb.net') === false || server.port !== 27017) {
+ return false;
+ }
+ }
+
+ return true;
+};
diff --git a/lib/helpers/topology/isSSLError.js b/lib/helpers/topology/isSSLError.js
index d910fc8344c..17c1c501085 100644
--- a/lib/helpers/topology/isSSLError.js
+++ b/lib/helpers/topology/isSSLError.js
@@ -13,4 +13,4 @@ module.exports = function isSSLError(topologyDescription) {
const descriptions = Array.from(topologyDescription.servers.values());
return descriptions.length > 0 &&
descriptions.every(descr => descr.error && descr.error.message.indexOf(nonSSLMessage) !== -1);
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/update/applyTimestampsToChildren.js b/lib/helpers/update/applyTimestampsToChildren.js
index 6186c3d4d92..5b8e7deb5b4 100644
--- a/lib/helpers/update/applyTimestampsToChildren.js
+++ b/lib/helpers/update/applyTimestampsToChildren.js
@@ -15,7 +15,7 @@ function applyTimestampsToChildren(now, update, schema) {
}
const keys = Object.keys(update);
- const hasDollarKey = keys.some(key => key.startsWith('$'));
+ const hasDollarKey = keys.some(key => key[0] === '$');
if (hasDollarKey) {
if (update.$push) {
@@ -38,7 +38,7 @@ function applyTimestampsToChildren(now, update, schema) {
}
}
- const updateKeys = Object.keys(update).filter(key => !key.startsWith('$'));
+ const updateKeys = Object.keys(update).filter(key => key[0] !== '$');
for (const key of updateKeys) {
applyTimestampsToUpdateKey(schema, key, update, now);
}
@@ -61,6 +61,8 @@ function applyTimestampsToChildren(now, update, schema) {
if (createdAt != null) {
subdoc[createdAt] = now;
}
+
+ applyTimestampsToChildren(now, subdoc, $path.schema);
});
} else {
if (updatedAt != null) {
@@ -69,6 +71,8 @@ function applyTimestampsToChildren(now, update, schema) {
if (createdAt != null) {
op[key][createdAt] = now;
}
+
+ applyTimestampsToChildren(now, op[key], $path.schema);
}
}
}
@@ -78,12 +82,15 @@ function applyTimestampsToChildren(now, update, schema) {
function applyTimestampsToDocumentArray(arr, schematype, now) {
const timestamps = schematype.schema.options.timestamps;
+ const len = arr.length;
+
if (!timestamps) {
+ for (let i = 0; i < len; ++i) {
+ applyTimestampsToChildren(now, arr[i], schematype.schema);
+ }
return;
}
- const len = arr.length;
-
const createdAt = handleTimestampOption(timestamps, 'createdAt');
const updatedAt = handleTimestampOption(timestamps, 'updatedAt');
for (let i = 0; i < len; ++i) {
@@ -101,6 +108,7 @@ function applyTimestampsToDocumentArray(arr, schematype, now) {
function applyTimestampsToSingleNested(subdoc, schematype, now) {
const timestamps = schematype.schema.options.timestamps;
if (!timestamps) {
+ applyTimestampsToChildren(now, subdoc, schematype.schema);
return;
}
@@ -153,7 +161,7 @@ function applyTimestampsToUpdateKey(schema, key, update, now) {
// Single nested is easy
update[parentPath + '.' + updatedAt] = now;
} else if (parentSchemaType.$isMongooseDocumentArray) {
- let childPath = key.substr(parentPath.length + 1);
+ let childPath = key.substring(parentPath.length + 1);
if (/^\d+$/.test(childPath)) {
update[parentPath + '.' + childPath][updatedAt] = now;
@@ -161,7 +169,7 @@ function applyTimestampsToUpdateKey(schema, key, update, now) {
}
const firstDot = childPath.indexOf('.');
- childPath = firstDot !== -1 ? childPath.substr(0, firstDot) : childPath;
+ childPath = firstDot !== -1 ? childPath.substring(0, firstDot) : childPath;
update[parentPath + '.' + childPath + '.' + updatedAt] = now;
}
@@ -182,4 +190,4 @@ function applyTimestampsToUpdateKey(schema, key, update, now) {
update[key][createdAt] = now;
}
}
-}
\ No newline at end of file
+}
diff --git a/lib/helpers/update/applyTimestampsToUpdate.js b/lib/helpers/update/applyTimestampsToUpdate.js
index 6fab29b5d9c..3c48f965be5 100644
--- a/lib/helpers/update/applyTimestampsToUpdate.js
+++ b/lib/helpers/update/applyTimestampsToUpdate.js
@@ -44,7 +44,7 @@ function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, optio
if (Array.isArray(updates)) {
// Update with aggregation pipeline
- updates.push({ $set: { updatedAt: now } });
+ updates.push({ $set: { [updatedAt]: now } });
return updates;
}
@@ -80,7 +80,6 @@ function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, optio
}
if (!skipCreatedAt && createdAt) {
-
if (currentUpdate[createdAt]) {
delete currentUpdate[createdAt];
}
diff --git a/lib/helpers/update/castArrayFilters.js b/lib/helpers/update/castArrayFilters.js
index c60ebdcde9c..163e33be14c 100644
--- a/lib/helpers/update/castArrayFilters.js
+++ b/lib/helpers/update/castArrayFilters.js
@@ -10,7 +10,20 @@ module.exports = function castArrayFilters(query) {
const update = query.getUpdate();
const schema = query.schema;
const updatedPathsByFilter = updatedPathsByArrayFilter(update);
- const strictQuery = schema.options.strictQuery;
+
+ let strictQuery = schema.options.strict;
+ if (query._mongooseOptions.strict != null) {
+ strictQuery = query._mongooseOptions.strict;
+ }
+ if (query.model && query.model.base.options.strictQuery != null) {
+ strictQuery = query.model.base.options.strictQuery;
+ }
+ if (schema._userProvidedOptions.strictQuery != null) {
+ strictQuery = schema._userProvidedOptions.strictQuery;
+ }
+ if (query._mongooseOptions.strictQuery != null) {
+ strictQuery = query._mongooseOptions.strictQuery;
+ }
_castArrayFilters(arrayFilters, schema, strictQuery, updatedPathsByFilter, query);
};
@@ -24,14 +37,36 @@ function _castArrayFilters(arrayFilters, schema, strictQuery, updatedPathsByFilt
if (filter == null) {
throw new Error(`Got null array filter in ${arrayFilters}`);
}
- for (const key of Object.keys(filter)) {
- if (key === '$and' || key === '$or') {
+ const keys = Object.keys(filter).filter(key => filter[key] != null);
+ if (keys.length === 0) {
+ continue;
+ }
+
+ const firstKey = keys[0];
+ if (firstKey === '$and' || firstKey === '$or') {
+ for (const key of keys) {
_castArrayFilters(filter[key], schema, strictQuery, updatedPathsByFilter, query);
- continue;
- }
- if (filter[key] == null) {
- continue;
}
+ continue;
+ }
+ const dot = firstKey.indexOf('.');
+ const filterWildcardPath = dot === -1 ? firstKey : firstKey.substring(0, dot);
+ if (updatedPathsByFilter[filterWildcardPath] == null) {
+ continue;
+ }
+ const baseFilterPath = cleanPositionalOperators(
+ updatedPathsByFilter[filterWildcardPath]
+ );
+
+ const baseSchematype = getPath(schema, baseFilterPath);
+ let filterBaseSchema = baseSchematype != null ? baseSchematype.schema : null;
+ if (filterBaseSchema != null &&
+ filterBaseSchema.discriminators != null &&
+ filter[filterWildcardPath + '.' + filterBaseSchema.options.discriminatorKey]) {
+ filterBaseSchema = filterBaseSchema.discriminators[filter[filterWildcardPath + '.' + filterBaseSchema.options.discriminatorKey]] || filterBaseSchema;
+ }
+
+ for (const key of keys) {
if (updatedPathsByFilter[key] === null) {
continue;
}
@@ -39,21 +74,25 @@ function _castArrayFilters(arrayFilters, schema, strictQuery, updatedPathsByFilt
continue;
}
const dot = key.indexOf('.');
- let filterPath = dot === -1 ?
- updatedPathsByFilter[key] + '.0' :
- updatedPathsByFilter[key.substr(0, dot)] + '.0' + key.substr(dot);
- if (filterPath == null) {
- throw new Error(`Filter path not found for ${key}`);
+
+ let filterPathRelativeToBase = dot === -1 ? null : key.substring(dot);
+ let schematype;
+ if (filterPathRelativeToBase == null || filterBaseSchema == null) {
+ schematype = baseSchematype;
+ } else {
+ // If there are multiple array filters in the path being updated, make sure
+ // to replace them so we can get the schema path.
+ filterPathRelativeToBase = cleanPositionalOperators(filterPathRelativeToBase);
+ schematype = getPath(filterBaseSchema, filterPathRelativeToBase);
}
- // If there are multiple array filters in the path being updated, make sure
- // to replace them so we can get the schema path.
- filterPath = cleanPositionalOperators(filterPath);
- const schematype = getPath(schema, filterPath);
if (schematype == null) {
if (!strictQuery) {
return;
}
+ const filterPath = filterPathRelativeToBase == null ?
+ baseFilterPath + '.0' :
+ baseFilterPath + '.0' + filterPathRelativeToBase;
// For now, treat `strictQuery = true` and `strictQuery = 'throw'` as
// equivalent for casting array filters. `strictQuery = true` doesn't
// quite work in this context because we never want to silently strip out
@@ -67,4 +106,4 @@ function _castArrayFilters(arrayFilters, schema, strictQuery, updatedPathsByFilt
}
}
}
-}
\ No newline at end of file
+}
diff --git a/lib/helpers/update/moveImmutableProperties.js b/lib/helpers/update/moveImmutableProperties.js
index 8541c5bd284..81a62a82cb4 100644
--- a/lib/helpers/update/moveImmutableProperties.js
+++ b/lib/helpers/update/moveImmutableProperties.js
@@ -50,4 +50,4 @@ function _walkUpdatePath(schema, op, path, update, ctx) {
update.$setOnInsert = update.$setOnInsert || {};
update.$setOnInsert[path] = op[path];
delete op[path];
-}
\ No newline at end of file
+}
diff --git a/lib/helpers/update/removeUnusedArrayFilters.js b/lib/helpers/update/removeUnusedArrayFilters.js
index 6a52f916612..22fa2483b6e 100644
--- a/lib/helpers/update/removeUnusedArrayFilters.js
+++ b/lib/helpers/update/removeUnusedArrayFilters.js
@@ -29,4 +29,4 @@ function _checkSingleFilterKey(arrayFilter, updateKeys) {
const arrayFilterKey = firstDot === -1 ? firstKey : firstKey.slice(0, firstDot);
return updateKeys.find(key => key.includes('$[' + arrayFilterKey + ']')) != null;
-}
\ No newline at end of file
+}
diff --git a/lib/helpers/update/updatedPathsByArrayFilter.js b/lib/helpers/update/updatedPathsByArrayFilter.js
index 0958808db8c..fe7d3b6895d 100644
--- a/lib/helpers/update/updatedPathsByArrayFilter.js
+++ b/lib/helpers/update/updatedPathsByArrayFilter.js
@@ -19,9 +19,9 @@ module.exports = function updatedPathsByArrayFilter(update) {
throw new Error(`Path '${path}' contains the same array filter multiple times`);
}
cur[match.substring(2, match.length - 1)] = path.
- substr(0, firstMatch - 1).
+ substring(0, firstMatch - 1).
replace(/\$\[[^\]]+\]/g, '0');
}
return cur;
}, {});
-};
\ No newline at end of file
+};
diff --git a/lib/helpers/updateValidators.js b/lib/helpers/updateValidators.js
index eddb8574a00..176eff26e16 100644
--- a/lib/helpers/updateValidators.js
+++ b/lib/helpers/updateValidators.js
@@ -125,26 +125,19 @@ module.exports = function(query, schema, castedDoc, options, callback) {
validatorsToExecute.push(function(callback) {
schemaPath.doValidate(v, function(err) {
if (err) {
- err.path = updates[i];
- validationErrors.push(err);
- return callback(null);
- }
-
- v.validate(function(err) {
- if (err) {
- if (err.errors) {
- for (const key of Object.keys(err.errors)) {
- const _err = err.errors[key];
- _err.path = updates[i] + '.' + key;
- validationErrors.push(_err);
- }
- } else {
- err.path = updates[i];
- validationErrors.push(err);
+ if (err.errors) {
+ for (const key of Object.keys(err.errors)) {
+ const _err = err.errors[key];
+ _err.path = updates[i] + '.' + key;
+ validationErrors.push(_err);
}
+ } else {
+ err.path = updates[i];
+ validationErrors.push(err);
}
- callback(null);
- });
+ }
+
+ return callback(null);
}, context, { updateValidator: true });
});
} else {
diff --git a/lib/index.js b/lib/index.js
index adad199ff3b..94b8082ff38 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -8,6 +8,7 @@ require('./driver').set(require('./drivers/node-mongodb-native'));
const Document = require('./document');
const EventEmitter = require('events').EventEmitter;
+const Kareem = require('kareem');
const Schema = require('./schema');
const SchemaType = require('./schematype');
const SchemaTypes = require('./schema/index');
@@ -18,36 +19,37 @@ const Types = require('./types');
const Query = require('./query');
const Model = require('./model');
const applyPlugins = require('./helpers/schema/applyPlugins');
+const builtinPlugins = require('./plugins');
const driver = require('./driver');
-const get = require('./helpers/get');
const promiseOrCallback = require('./helpers/promiseOrCallback');
const legacyPluralize = require('./helpers/pluralize');
const utils = require('./utils');
const pkg = require('../package.json');
const cast = require('./cast');
-const clearValidating = require('./plugins/clearValidating');
-const removeSubdocs = require('./plugins/removeSubdocs');
-const saveSubdocs = require('./plugins/saveSubdocs');
-const trackTransaction = require('./plugins/trackTransaction');
-const validateBeforeSave = require('./plugins/validateBeforeSave');
const Aggregate = require('./aggregate');
const PromiseProvider = require('./promise_provider');
-const shardingPlugin = require('./plugins/sharding');
+const printStrictQueryWarning = require('./helpers/printStrictQueryWarning');
const trusted = require('./helpers/query/trusted').trusted;
const sanitizeFilter = require('./helpers/query/sanitizeFilter');
+const isBsonType = require('./helpers/isBsonType');
+const MongooseError = require('./error/mongooseError');
+const SetOptionError = require('./error/setOptionError');
const defaultMongooseSymbol = Symbol.for('mongoose:default');
require('./helpers/printJestWarning');
+const objectIdHexRegexp = /^[0-9A-Fa-f]{24}$/;
+
/**
* Mongoose constructor.
*
* The exports object of the `mongoose` module is an instance of this class.
* Most apps will only use this one instance.
*
- * ####Example:
+ * #### Example:
+ *
* const mongoose = require('mongoose');
* mongoose instanceof mongoose.Mongoose; // true
*
@@ -59,16 +61,21 @@ require('./helpers/printJestWarning');
*/
function Mongoose(options) {
this.connections = [];
+ this.nextConnectionId = 0;
this.models = {};
this.events = new EventEmitter();
+ this.__driver = driver.get();
// default global options
this.options = Object.assign({
pluralization: true,
autoIndex: true,
autoCreate: true
}, options);
- const conn = this.createConnection(); // default connection
- conn.models = this.models;
+ const createInitialConnection = utils.getOption('createInitialConnection', this.options);
+ if (createInitialConnection == null || createInitialConnection) {
+ const conn = this.createConnection(); // default connection
+ conn.models = this.models;
+ }
if (this.options.pluralization) {
this._pluralize = legacyPluralize;
@@ -101,16 +108,10 @@ function Mongoose(options) {
configurable: false,
enumerable: true,
writable: false,
- value: [
- [saveSubdocs, { deduplicate: true }],
- [validateBeforeSave, { deduplicate: true }],
- [shardingPlugin, { deduplicate: true }],
- [removeSubdocs, { deduplicate: true }],
- [trackTransaction, { deduplicate: true }],
- [clearValidating, { deduplicate: true }]
- ]
+ value: Object.values(builtinPlugins).map(plugin => ([plugin, { deduplicate: true }]))
});
}
+
Mongoose.prototype.cast = cast;
/**
* Expose connection states for user-land
@@ -121,11 +122,21 @@ Mongoose.prototype.cast = cast;
*/
Mongoose.prototype.STATES = STATES;
+/**
+ * Expose connection states for user-land
+ *
+ * @memberOf Mongoose
+ * @property ConnectionStates
+ * @api public
+ */
+Mongoose.prototype.ConnectionStates = STATES;
+
/**
* Object with `get()` and `set()` containing the underlying driver this Mongoose instance
* uses to communicate with the database. A driver is a Mongoose-specific interface that defines functions
* like `find()`.
*
+ * @deprecated
* @memberOf Mongoose
* @property driver
* @api public
@@ -133,10 +144,44 @@ Mongoose.prototype.STATES = STATES;
Mongoose.prototype.driver = driver;
+/**
+ * Overwrites the current driver used by this Mongoose instance. A driver is a
+ * Mongoose-specific interface that defines functions like `find()`.
+ *
+ * @memberOf Mongoose
+ * @method setDriver
+ * @api public
+ */
+
+Mongoose.prototype.setDriver = function setDriver(driver) {
+ const _mongoose = this instanceof Mongoose ? this : mongoose;
+
+ if (_mongoose.__driver === driver) {
+ return _mongoose;
+ }
+
+ const openConnection = _mongoose.connections && _mongoose.connections.find(conn => conn.readyState !== STATES.disconnected);
+ if (openConnection) {
+ const msg = 'Cannot modify Mongoose driver if a connection is already open. ' +
+ 'Call `mongoose.disconnect()` before modifying the driver';
+ throw new MongooseError(msg);
+ }
+ _mongoose.__driver = driver;
+
+ const Connection = driver.getConnection();
+ _mongoose.connections = [new Connection(_mongoose)];
+ _mongoose.connections[0].models = _mongoose.models;
+
+ return _mongoose;
+};
+
/**
* Sets mongoose options
*
- * ####Example:
+ * `key` can be used a object to set multiple options at once.
+ * If a error gets thrown for one option, other options will still be evaluated.
+ *
+ * #### Example:
*
* mongoose.set('test', value) // sets the 'test' option to `value`
*
@@ -144,53 +189,92 @@ Mongoose.prototype.driver = driver;
*
* mongoose.set('debug', function(collectionName, methodName, ...methodArgs) {}); // use custom function to log collection methods + arguments
*
- * Currently supported options are:
- * - 'debug': If `true`, prints the operations mongoose sends to MongoDB to the console. If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arugments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`.
- * - 'returnOriginal': If `false`, changes the default `returnOriginal` option to `findOneAndUpdate()`, `findByIdAndUpdate`, and `findOneAndReplace()` to false. This is equivalent to setting the `new` option to `true` for `findOneAndX()` calls by default. Read our [`findOneAndUpdate()` tutorial](/docs/tutorials/findoneandupdate.html) for more information.
- * - 'bufferCommands': enable/disable mongoose's buffering mechanism for all connections and models
- * - 'cloneSchemas': false by default. Set to `true` to `clone()` all schemas before compiling into a model.
- * - 'applyPluginsToDiscriminators': false by default. Set to true to apply global plugins to discriminator schemas. This typically isn't necessary because plugins are applied to the base schema and discriminators copy all middleware, methods, statics, and properties from the base schema.
- * - 'applyPluginsToChildSchemas': true by default. Set to false to skip applying global plugins to child schemas
- * - 'objectIdGetter': true by default. Mongoose adds a getter to MongoDB ObjectId's called `_id` that returns `this` for convenience with populate. Set this to false to remove the getter.
- * - 'runValidators': false by default. Set to true to enable [update validators](/docs/validation.html#update-validators) for all validators by default.
- * - 'toObject': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](/docs/api.html#document_Document-toObject)
- * - 'toJSON': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](/docs/api.html#document_Document-toJSON), for determining how Mongoose documents get serialized by `JSON.stringify()`
- * - 'strict': true by default, may be `false`, `true`, or `'throw'`. Sets the default strict mode for schemas.
- * - 'strictQuery': same value as 'strict' by default (`true`), may be `false`, `true`, or `'throw'`. Sets the default [strictQuery](/docs/guide.html#strictQuery) mode for schemas.
- * - 'selectPopulatedPaths': true by default. Set to false to opt out of Mongoose adding all fields that you `populate()` to your `select()`. The schema-level option `selectPopulatedPaths` overwrites this one.
- * - 'maxTimeMS': If set, attaches [maxTimeMS](https://docs.mongodb.com/manual/reference/operator/meta/maxTimeMS/) to every query
- * - 'autoIndex': true by default. Set to false to disable automatic index creation for all models associated with this Mongoose instance.
- * - 'autoCreate': Set to `true` to make Mongoose call [`Model.createCollection()`](/docs/api/model.html#model_Model.createCollection) automatically when you create a model with `mongoose.model()` or `conn.model()`. This is useful for testing transactions, change streams, and other features that require the collection to exist.
- * - 'overwriteModels': Set to `true` to default to overwriting models with the same name when calling `mongoose.model()`, as opposed to throwing an `OverwriteModelError`.
+ * mongoose.set({ debug: true, autoIndex: false }); // set multiple options at once
*
- * @param {String} key
- * @param {String|Function|Boolean} value
+ * Currently supported options are:
+ * - `allowDiskUse`: Set to `true` to set `allowDiskUse` to true to all aggregation operations by default.
+ * - `applyPluginsToChildSchemas`: `true` by default. Set to false to skip applying global plugins to child schemas
+ * - `applyPluginsToDiscriminators`: `false` by default. Set to true to apply global plugins to discriminator schemas. This typically isn't necessary because plugins are applied to the base schema and discriminators copy all middleware, methods, statics, and properties from the base schema.
+ * - `autoCreate`: Set to `true` to make Mongoose call [`Model.createCollection()`](/docs/api/model.html#model_Model-createCollection) automatically when you create a model with `mongoose.model()` or `conn.model()`. This is useful for testing transactions, change streams, and other features that require the collection to exist.
+ * - `autoIndex`: `true` by default. Set to false to disable automatic index creation for all models associated with this Mongoose instance.
+ * - `bufferCommands`: enable/disable mongoose's buffering mechanism for all connections and models
+ * - `bufferTimeoutMS`: If bufferCommands is on, this option sets the maximum amount of time Mongoose buffering will wait before throwing an error. If not specified, Mongoose will use 10000 (10 seconds).
+ * - `cloneSchemas`: `false` by default. Set to `true` to `clone()` all schemas before compiling into a model.
+ * - `debug`: If `true`, prints the operations mongoose sends to MongoDB to the console. If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arguments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`.
+ * - `id`: If `true`, adds a `id` virtual to all schemas unless overwritten on a per-schema basis.
+ * - `timestamps.createdAt.immutable`: `true` by default. If `false`, it will change the `createdAt` field to be [`immutable: false`](https://mongoosejs.com/docs/api/schematype.html#schematype_SchemaType-immutable) which means you can update the `createdAt`
+ * - `maxTimeMS`: If set, attaches [maxTimeMS](https://www.mongodb.com/docs/manual/reference/operator/meta/maxTimeMS/) to every query
+ * - `objectIdGetter`: `true` by default. Mongoose adds a getter to MongoDB ObjectId's called `_id` that returns `this` for convenience with populate. Set this to false to remove the getter.
+ * - `overwriteModels`: Set to `true` to default to overwriting models with the same name when calling `mongoose.model()`, as opposed to throwing an `OverwriteModelError`.
+ * - `returnOriginal`: If `false`, changes the default `returnOriginal` option to `findOneAndUpdate()`, `findByIdAndUpdate`, and `findOneAndReplace()` to false. This is equivalent to setting the `new` option to `true` for `findOneAndX()` calls by default. Read our [`findOneAndUpdate()` tutorial](/docs/tutorials/findoneandupdate.html) for more information.
+ * - `runValidators`: `false` by default. Set to true to enable [update validators](/docs/validation.html#update-validators) for all validators by default.
+ * - `sanitizeFilter`: `false` by default. Set to true to enable the [sanitization of the query filters](/docs/api/mongoose.html#mongoose_Mongoose-sanitizeFilter) against query selector injection attacks by wrapping any nested objects that have a property whose name starts with `$` in a `$eq`.
+ * - `selectPopulatedPaths`: `true` by default. Set to false to opt out of Mongoose adding all fields that you `populate()` to your `select()`. The schema-level option `selectPopulatedPaths` overwrites this one.
+ * - `strict`: `true` by default, may be `false`, `true`, or `'throw'`. Sets the default strict mode for schemas.
+ * - `strictQuery`: same value as 'strict' by default (`true`), may be `false`, `true`, or `'throw'`. Sets the default [strictQuery](/docs/guide.html#strictQuery) mode for schemas. The default value will be switched back to `false` in Mongoose 7, use `mongoose.set('strictQuery', false);` if you want to prepare for the change.
+ * - `toJSON`: `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](/docs/api/document.html#document_Document-toJSON), for determining how Mongoose documents get serialized by `JSON.stringify()`
+ * - `toObject`: `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](/docs/api/document.html#document_Document-toObject)
+ *
+ * @param {String|Object} key The name of the option or a object of multiple key-value pairs
+ * @param {String|Function|Boolean} value The value of the option, unused if "key" is a object
+ * @returns {Mongoose} The used Mongoose instnace
* @api public
*/
Mongoose.prototype.set = function(key, value) {
const _mongoose = this instanceof Mongoose ? this : mongoose;
- if (VALID_OPTIONS.indexOf(key) === -1) throw new Error(`\`${key}\` is an invalid option.`);
+ if (arguments.length === 1 && typeof key !== 'object') {
+ if (VALID_OPTIONS.indexOf(key) === -1) {
+ const error = new SetOptionError();
+ error.addError(key, new SetOptionError.SetOptionInnerError(key));
+ throw error;
+ }
- if (arguments.length === 1) {
return _mongoose.options[key];
}
- _mongoose.options[key] = value;
+ let options = {};
- if (key === 'objectIdGetter') {
- if (value) {
- Object.defineProperty(mongoose.Types.ObjectId.prototype, '_id', {
- enumerable: false,
- configurable: true,
- get: function() {
- return this;
- }
- });
- } else {
- delete mongoose.Types.ObjectId.prototype._id;
+ if (arguments.length === 2) {
+ options = { [key]: value };
+ }
+
+ if (arguments.length === 1 && typeof key === 'object') {
+ options = key;
+ }
+
+ // array for errors to collect all errors for all key-value pairs, like ".validate"
+ let error = undefined;
+
+ for (const [optionKey, optionValue] of Object.entries(options)) {
+ if (VALID_OPTIONS.indexOf(optionKey) === -1) {
+ if (!error) {
+ error = new SetOptionError();
+ }
+ error.addError(optionKey, new SetOptionError.SetOptionInnerError(optionKey));
+ continue;
}
+
+ _mongoose.options[optionKey] = optionValue;
+
+ if (optionKey === 'objectIdGetter') {
+ if (optionValue) {
+ Object.defineProperty(mongoose.Types.ObjectId.prototype, '_id', {
+ enumerable: false,
+ configurable: true,
+ get: function() {
+ return this;
+ }
+ });
+ } else {
+ delete mongoose.Types.ObjectId.prototype._id;
+ }
+ }
+ }
+
+ if (error) {
+ throw error;
}
return _mongoose;
@@ -199,7 +283,7 @@ Mongoose.prototype.set = function(key, value) {
/**
* Gets mongoose options
*
- * ####Example:
+ * #### Example:
*
* mongoose.get('test') // returns the 'test' value
*
@@ -218,58 +302,54 @@ Mongoose.prototype.get = Mongoose.prototype.set;
*
* _Options passed take precedence over options included in connection strings._
*
- * ####Example:
+ * #### Example:
*
* // with mongodb:// URI
- * db = mongoose.createConnection('mongodb://user:pass@localhost:port/database');
+ * db = mongoose.createConnection('mongodb://user:pass@127.0.0.1:port/database');
*
* // and options
* const opts = { db: { native_parser: true }}
- * db = mongoose.createConnection('mongodb://user:pass@localhost:port/database', opts);
+ * db = mongoose.createConnection('mongodb://user:pass@127.0.0.1:port/database', opts);
*
* // replica sets
- * db = mongoose.createConnection('mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/database');
+ * db = mongoose.createConnection('mongodb://user:pass@127.0.0.1:port,anotherhost:port,yetanother:port/database');
*
* // and options
* const opts = { replset: { strategy: 'ping', rs_name: 'testSet' }}
- * db = mongoose.createConnection('mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/database', opts);
- *
- * // and options
- * const opts = { server: { auto_reconnect: false }, user: 'username', pass: 'mypassword' }
- * db = mongoose.createConnection('localhost', 'database', port, opts)
+ * db = mongoose.createConnection('mongodb://user:pass@127.0.0.1:port,anotherhost:port,yetanother:port/database', opts);
*
* // initialize now, connect later
* db = mongoose.createConnection();
- * db.openUri('localhost', 'database', port, [opts]);
+ * db.openUri('127.0.0.1', 'database', port, [opts]);
*
- * @param {String} [uri] a mongodb:// URI
- * @param {Object} [options] passed down to the [MongoDB driver's `connect()` function](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html), except for 4 mongoose-specific options explained below.
- * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](http://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection.
+ * @param {String} uri mongodb URI to connect to
+ * @param {Object} [options] passed down to the [MongoDB driver's `connect()` function](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/MongoClientOptions.html), except for 4 mongoose-specific options explained below.
+ * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](https://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection.
* @param {String} [options.dbName] The name of the database you want to use. If not provided, Mongoose uses the database name from connection string.
* @param {String} [options.user] username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility.
* @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility.
* @param {Boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection.
- * @param {Number} [options.reconnectTries=30] If you're connected to a single server or mongos proxy (as opposed to a replica set), the MongoDB driver will try to reconnect every `reconnectInterval` milliseconds for `reconnectTries` times, and give up afterward. When the driver gives up, the mongoose connection emits a `reconnectFailed` event. This option does nothing for replica set connections.
- * @param {Number} [options.reconnectInterval=1000] See `reconnectTries` option above.
- * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](http://mongodb.github.io/node-mongodb-native/3.1/api/MongoClient.html).
- * @param {Number} [options.maxPoolSize=5] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
- * @param {Number} [options.minPoolSize=1] The minimum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
+ * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/MongoClientOptions.html#promiseLibrary).
+ * @param {Number} [options.maxPoolSize=5] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
+ * @param {Number} [options.minPoolSize=1] The minimum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
* @param {Number} [options.connectTimeoutMS=30000] How long the MongoDB driver will wait before killing a socket due to inactivity _during initial connection_. Defaults to 30000. This option is passed transparently to [Node.js' `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback).
* @param {Number} [options.socketTimeoutMS=30000] How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. A socket may be inactive because of either no activity or a long-running operation. This is set to `30000` by default, you should set this to 2-3x your longest running operation if you expect some of your database operations to run longer than 20 seconds. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes.
* @param {Number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0`, `4`, or `6`. `4` means use IPv4 only, `6` means use IPv6 only, `0` means try both.
- * @return {Connection} the created Connection object. Connections are thenable, so you can do `await mongoose.createConnection()`
+ * @return {Connection} the created Connection object. Connections are not thenable, so you can't do `await mongoose.createConnection()`. To await use `mongoose.createConnection(uri).asPromise()` instead.
* @api public
*/
Mongoose.prototype.createConnection = function(uri, options, callback) {
const _mongoose = this instanceof Mongoose ? this : mongoose;
+ const Connection = _mongoose.__driver.getConnection();
const conn = new Connection(_mongoose);
if (typeof options === 'function') {
callback = options;
options = null;
}
_mongoose.connections.push(conn);
+ _mongoose.nextConnectionId++;
_mongoose.events.emit('createConnection', conn);
if (arguments.length > 0) {
@@ -282,12 +362,12 @@ Mongoose.prototype.createConnection = function(uri, options, callback) {
/**
* Opens the default mongoose connection.
*
- * ####Example:
+ * #### Example:
*
- * mongoose.connect('mongodb://user:pass@localhost:port/database');
+ * mongoose.connect('mongodb://user:pass@127.0.0.1:port/database');
*
* // replica sets
- * const uri = 'mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/mydatabase';
+ * const uri = 'mongodb://user:pass@127.0.0.1:port,anotherhost:port,yetanother:port/mydatabase';
* mongoose.connect(uri);
*
* // with options
@@ -299,27 +379,25 @@ Mongoose.prototype.createConnection = function(uri, options, callback) {
* // if error is truthy, the initial connection failed.
* })
*
- * @param {String} uri(s)
- * @param {Object} [options] passed down to the [MongoDB driver's `connect()` function](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html), except for 4 mongoose-specific options explained below.
- * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](http://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection.
+ * @param {String} uri mongodb URI to connect to
+ * @param {Object} [options] passed down to the [MongoDB driver's `connect()` function](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/MongoClientOptions.html), except for 4 mongoose-specific options explained below.
+ * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](https://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection.
* @param {Number} [options.bufferTimeoutMS=10000] Mongoose specific option. If `bufferCommands` is true, Mongoose will throw an error after `bufferTimeoutMS` if the operation is still buffered.
* @param {String} [options.dbName] The name of the database we want to use. If not provided, use database name from connection string.
* @param {String} [options.user] username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility.
* @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility.
- * @param {Number} [options.maxPoolSize=100] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
+ * @param {Number} [options.maxPoolSize=100] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
* @param {Number} [options.minPoolSize=0] The minimum number of sockets the MongoDB driver will keep open for this connection.
* @param {Number} [options.serverSelectionTimeoutMS] If `useUnifiedTopology = true`, the MongoDB driver will try to find a server to send any given operation to, and keep retrying for `serverSelectionTimeoutMS` milliseconds before erroring out. If not set, the MongoDB driver defaults to using `30000` (30 seconds).
* @param {Number} [options.heartbeatFrequencyMS] If `useUnifiedTopology = true`, the MongoDB driver sends a heartbeat every `heartbeatFrequencyMS` to check on the status of the connection. A heartbeat is subject to `serverSelectionTimeoutMS`, so the MongoDB driver will retry failed heartbeats for up to 30 seconds by default. Mongoose only emits a `'disconnected'` event after a heartbeat has failed, so you may want to decrease this setting to reduce the time between when your server goes down and when Mongoose emits `'disconnected'`. We recommend you do **not** set this setting below 1000, too many heartbeats can lead to performance degradation.
* @param {Boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection.
- * @param {Number} [options.reconnectTries=30] If you're connected to a single server or mongos proxy (as opposed to a replica set), the MongoDB driver will try to reconnect every `reconnectInterval` milliseconds for `reconnectTries` times, and give up afterward. When the driver gives up, the mongoose connection emits a `reconnectFailed` event. This option does nothing for replica set connections.
- * @param {Number} [options.reconnectInterval=1000] See `reconnectTries` option above.
- * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](http://mongodb.github.io/node-mongodb-native/3.1/api/MongoClient.html).
+ * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/MongoClientOptions.html#promiseLibrary).
* @param {Number} [options.connectTimeoutMS=30000] How long the MongoDB driver will wait before killing a socket due to inactivity _during initial connection_. Defaults to 30000. This option is passed transparently to [Node.js' `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback).
* @param {Number} [options.socketTimeoutMS=30000] How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. A socket may be inactive because of either no activity or a long-running operation. This is set to `30000` by default, you should set this to 2-3x your longest running operation if you expect some of your database operations to run longer than 20 seconds. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes.
* @param {Number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0`, `4`, or `6`. `4` means use IPv4 only, `6` means use IPv6 only, `0` means try both.
* @param {Boolean} [options.autoCreate=false] Set to `true` to make Mongoose automatically call `createCollection()` on every model created on this connection.
* @param {Function} [callback]
- * @see Mongoose#createConnection #index_Mongoose-createConnection
+ * @see Mongoose#createConnection /docs/api/mongoose.html#mongoose_Mongoose-createConnection
* @api public
* @return {Promise} resolves to `this` if connection succeeded
*/
@@ -328,6 +406,10 @@ Mongoose.prototype.connect = function(uri, options, callback) {
const _mongoose = this instanceof Mongoose ? this : mongoose;
const conn = _mongoose.connection;
+ if (_mongoose.options.strictQuery === undefined) {
+ printStrictQueryWarning();
+ }
+
return _mongoose._promiseOrCallback(callback, cb => {
conn.openUri(uri, options, err => {
if (err != null) {
@@ -368,15 +450,15 @@ Mongoose.prototype.disconnect = function(callback) {
};
/**
- * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions)
- * for benefits like causal consistency, [retryable writes](https://docs.mongodb.com/manual/core/retryable-writes/),
- * and [transactions](http://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html).
+ * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://www.mongodb.com/docs/manual/release-notes/3.6/#client-sessions)
+ * for benefits like causal consistency, [retryable writes](https://www.mongodb.com/docs/manual/core/retryable-writes/),
+ * and [transactions](https://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html).
*
* Calling `mongoose.startSession()` is equivalent to calling `mongoose.connection.startSession()`.
* Sessions are scoped to a connection, so calling `mongoose.startSession()`
- * starts a session on the [default mongoose connection](/docs/api.html#mongoose_Mongoose-connection).
+ * starts a session on the [default mongoose connection](/docs/api/mongoose.html#mongoose_Mongoose-connection).
*
- * @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html#startSession)
+ * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/MongoClient.html#startSession)
* @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency
* @param {Function} [callback]
* @return {Promise<ClientSession>} promise that resolves to a MongoDB driver `ClientSession`
@@ -416,7 +498,7 @@ Mongoose.prototype.pluralize = function(fn) {
* you will get an `OverwriteModelError`. If you call `mongoose.model()` with
* the same name and same schema, you'll get the same schema back.
*
- * ####Example:
+ * #### Example:
*
* const mongoose = require('mongoose');
*
@@ -438,7 +520,7 @@ Mongoose.prototype.pluralize = function(fn) {
*
* _When no `collection` argument is passed, Mongoose uses the model name. If you don't like this behavior, either pass a collection name, use `mongoose.pluralize()`, or set your schemas collection name option._
*
- * ####Example:
+ * #### Example:
*
* const schema = new Schema({ name: String }, { collection: 'actor' });
*
@@ -466,6 +548,14 @@ Mongoose.prototype.model = function(name, schema, collection, options) {
schema = false;
}
+ if (arguments.length === 1) {
+ const model = _mongoose.models[name];
+ if (!model) {
+ throw new MongooseError.MissingSchemaError(name);
+ }
+ return model;
+ }
+
if (utils.isObject(schema) && !(schema instanceof Schema)) {
schema = new Schema(schema);
}
@@ -511,7 +601,6 @@ Mongoose.prototype.model = function(name, schema, collection, options) {
}
const model = _mongoose._model(name, schema, collection, options);
-
_mongoose.connection.models[name] = model;
_mongoose.models[name] = model;
@@ -553,12 +642,17 @@ Mongoose.prototype._model = function(name, schema, collection, options) {
const connection = options.connection || _mongoose.connection;
model = _mongoose.Model.compile(model || name, schema, collection, connection, _mongoose);
-
// Errors handled internally, so safe to ignore error
model.init(function $modelInitNoop() {});
connection.emit('model', model);
+ if (schema._applyDiscriminators != null) {
+ for (const disc of Object.keys(schema._applyDiscriminators)) {
+ model.discriminator(disc, schema._applyDiscriminators[disc]);
+ }
+ }
+
return model;
};
@@ -569,7 +663,7 @@ Mongoose.prototype._model = function(name, schema, collection, options) {
*
* Equivalent to `mongoose.connection.deleteModel(name)`.
*
- * ####Example:
+ * #### Example:
*
* mongoose.model('User', new Schema({ name: String }));
* console.log(mongoose.model('User')); // Model object
@@ -597,7 +691,7 @@ Mongoose.prototype.deleteModel = function(name) {
/**
* Returns an array of model names created on this instance of Mongoose.
*
- * ####Note:
+ * #### Note:
*
* _Does not include names of models created using `connection.model()`._
*
@@ -623,10 +717,10 @@ Mongoose.prototype._applyPlugins = function(schema, options) {
const _mongoose = this instanceof Mongoose ? this : mongoose;
options = options || {};
- options.applyPluginsToDiscriminators = get(_mongoose,
- 'options.applyPluginsToDiscriminators', false);
- options.applyPluginsToChildSchemas = get(_mongoose,
- 'options.applyPluginsToChildSchemas', true);
+ options.applyPluginsToDiscriminators = _mongoose.options && _mongoose.options.applyPluginsToDiscriminators || false;
+ options.applyPluginsToChildSchemas = typeof (_mongoose.options && _mongoose.options.applyPluginsToChildSchemas) === 'boolean' ?
+ _mongoose.options.applyPluginsToChildSchemas :
+ true;
applyPlugins(schema, _mongoose.plugins, options, '$globalPluginsApplied');
};
@@ -638,7 +732,7 @@ Mongoose.prototype._applyPlugins = function(schema, options) {
* @param {Function} fn plugin callback
* @param {Object} [opts] optional options
* @return {Mongoose} this
- * @see plugins ./plugins.html
+ * @see plugins /docs/plugins.html
* @api public
*/
@@ -652,7 +746,7 @@ Mongoose.prototype.plugin = function(fn, opts) {
/**
* The Mongoose module's default connection. Equivalent to `mongoose.connections[0]`, see [`connections`](#mongoose_Mongoose-connections).
*
- * ####Example:
+ * #### Example:
*
* const mongoose = require('mongoose');
* mongoose.connect(...);
@@ -673,25 +767,25 @@ Mongoose.prototype.__defineGetter__('connection', function() {
});
Mongoose.prototype.__defineSetter__('connection', function(v) {
- if (v instanceof Connection) {
+ if (v instanceof this.__driver.getConnection()) {
this.connections[0] = v;
this.models = v.models;
}
});
/**
- * An array containing all [connections](connections.html) associated with this
+ * An array containing all [connections](connection.html) associated with this
* Mongoose instance. By default, there is 1 connection. Calling
* [`createConnection()`](#mongoose_Mongoose-createConnection) adds a connection
* to this array.
*
- * ####Example:
+ * #### Example:
*
* const mongoose = require('mongoose');
* mongoose.connections.length; // 1, just the default connection
* mongoose.connections[0] === mongoose.connection; // true
*
- * mongoose.createConnection('mongodb://localhost:27017/test');
+ * mongoose.createConnection('mongodb://127.0.0.1:27017/test');
* mongoose.connections.length; // 2
*
* @memberOf Mongoose
@@ -702,17 +796,26 @@ Mongoose.prototype.__defineSetter__('connection', function(v) {
Mongoose.prototype.connections;
-/*!
- * Connection
- */
-
-const Connection = driver.get().getConnection();
-
-/*!
- * Collection
+/**
+ * An integer containing the value of the next connection id. Calling
+ * [`createConnection()`](#mongoose_Mongoose-createConnection) increments
+ * this value.
+ *
+ * #### Example:
+ *
+ * const mongoose = require('mongoose');
+ * mongoose.createConnection(); // id `0`, `nextConnectionId` becomes `1`
+ * mongoose.createConnection(); // id `1`, `nextConnectionId` becomes `2`
+ * mongoose.connections[0].destroy() // Removes connection with id `0`
+ * mongoose.createConnection(); // id `2`, `nextConnectionId` becomes `3`
+ *
+ * @memberOf Mongoose
+ * @instance
+ * @property {Number} nextConnectionId
+ * @api private
*/
-const Collection = driver.get().Collection;
+Mongoose.prototype.nextConnectionId;
/**
* The Mongoose Aggregate constructor
@@ -726,11 +829,20 @@ Mongoose.prototype.Aggregate = Aggregate;
/**
* The Mongoose Collection constructor
*
+ * @memberOf Mongoose
+ * @instance
* @method Collection
* @api public
*/
-Mongoose.prototype.Collection = Collection;
+Object.defineProperty(Mongoose.prototype, 'Collection', {
+ get: function() {
+ return this.__driver.Collection;
+ },
+ set: function(Collection) {
+ this.__driver.Collection = Collection;
+ }
+});
/**
* The Mongoose [Connection](#connection_Connection) constructor
@@ -741,12 +853,23 @@ Mongoose.prototype.Collection = Collection;
* @api public
*/
-Mongoose.prototype.Connection = Connection;
+Object.defineProperty(Mongoose.prototype, 'Connection', {
+ get: function() {
+ return this.__driver.getConnection();
+ },
+ set: function(Connection) {
+ if (Connection === this.__driver.getConnection()) {
+ return;
+ }
+
+ this.__driver.getConnection = () => Connection;
+ }
+});
/**
* The Mongoose version
*
- * #### Example
+ * #### Example:
*
* console.log(mongoose.version); // '5.x.x'
*
@@ -761,7 +884,7 @@ Mongoose.prototype.version = pkg.version;
*
* The exports of the mongoose module is an instance of this class.
*
- * ####Example:
+ * #### Example:
*
* const mongoose = require('mongoose');
* const mongoose2 = new mongoose.Mongoose();
@@ -775,7 +898,7 @@ Mongoose.prototype.Mongoose = Mongoose;
/**
* The Mongoose [Schema](#schema_Schema) constructor
*
- * ####Example:
+ * #### Example:
*
* const mongoose = require('mongoose');
* const Schema = mongoose.Schema;
@@ -799,12 +922,12 @@ Mongoose.prototype.SchemaType = SchemaType;
/**
* The various Mongoose SchemaTypes.
*
- * ####Note:
+ * #### Note:
*
* _Alias of mongoose.Schema.Types for backwards compatibility._
*
* @property SchemaTypes
- * @see Schema.SchemaTypes #schema_Schema.Types
+ * @see Schema.SchemaTypes /docs/schematypes.html
* @api public
*/
@@ -822,18 +945,18 @@ Mongoose.prototype.VirtualType = VirtualType;
/**
* The various Mongoose Types.
*
- * ####Example:
+ * #### Example:
*
* const mongoose = require('mongoose');
* const array = mongoose.Types.Array;
*
- * ####Types:
+ * #### Types:
*
* - [Array](/docs/schematypes.html#arrays)
* - [Buffer](/docs/schematypes.html#buffers)
* - [Embedded](/docs/schematypes.html#schemas)
* - [DocumentArray](/docs/api/documentarraypath.html)
- * - [Decimal128](/docs/api.html#mongoose_Mongoose-Decimal128)
+ * - [Decimal128](/docs/api/mongoose.html#mongoose_Mongoose-Decimal128)
* - [ObjectId](/docs/schematypes.html#objectids)
* - [Map](/docs/schematypes.html#maps)
* - [Subdocument](/docs/schematypes.html#schemas)
@@ -895,7 +1018,7 @@ Mongoose.prototype.PromiseProvider = PromiseProvider;
Mongoose.prototype.Model = Model;
/**
- * The Mongoose [Document](/api/document.html) constructor.
+ * The Mongoose [Document](/docs/api/document.html#Document) constructor.
*
* @method Document
* @api public
@@ -916,11 +1039,11 @@ Mongoose.prototype.DocumentProvider = require('./document_provider');
/**
* The Mongoose ObjectId [SchemaType](/docs/schematypes.html). Used for
* declaring paths in your schema that should be
- * [MongoDB ObjectIds](https://docs.mongodb.com/manual/reference/method/ObjectId/).
+ * [MongoDB ObjectIds](https://www.mongodb.com/docs/manual/reference/method/ObjectId/).
* Do not use this to create a new ObjectId instance, use `mongoose.Types.ObjectId`
* instead.
*
- * ####Example:
+ * #### Example:
*
* const childSchema = new Schema({ parentId: mongoose.ObjectId });
*
@@ -934,64 +1057,78 @@ Mongoose.prototype.ObjectId = SchemaTypes.ObjectId;
* Returns true if Mongoose can cast the given value to an ObjectId, or
* false otherwise.
*
- * ####Example:
+ * #### Example:
*
* mongoose.isValidObjectId(new mongoose.Types.ObjectId()); // true
* mongoose.isValidObjectId('0123456789ab'); // true
- * mongoose.isValidObjectId(6); // false
+ * mongoose.isValidObjectId(6); // true
+ * mongoose.isValidObjectId(new User({ name: 'test' })); // true
+ *
+ * mongoose.isValidObjectId({ test: 42 }); // false
*
* @method isValidObjectId
+ * @param {Any} v
+ * @returns {boolean} true if `v` is something Mongoose can coerce to an ObjectId
* @api public
*/
Mongoose.prototype.isValidObjectId = function(v) {
- if (v == null) {
- return true;
- }
- const base = this || mongoose;
- const ObjectId = base.driver.get().ObjectId;
- if (v instanceof ObjectId) {
- return true;
- }
-
- if (v._id != null) {
- if (v._id instanceof ObjectId) {
- return true;
- }
- if (v._id.toString instanceof Function) {
- v = v._id.toString();
- if (typeof v === 'string' && v.length === 12) {
- return true;
- }
- if (typeof v === 'string' && /^[0-9A-Fa-f]{24}$/.test(v)) {
- return true;
- }
- return false;
- }
- }
+ const _mongoose = this instanceof Mongoose ? this : mongoose;
+ return _mongoose.Types.ObjectId.isValid(v);
+};
- if (v.toString instanceof Function) {
- v = v.toString();
- }
+/**
+ * Returns true if the given value is a Mongoose ObjectId (using `instanceof`) or if the
+ * given value is a 24 character hex string, which is the most commonly used string representation
+ * of an ObjectId.
+ *
+ * This function is similar to `isValidObjectId()`, but considerably more strict, because
+ * `isValidObjectId()` will return `true` for _any_ value that Mongoose can convert to an
+ * ObjectId. That includes Mongoose documents, any string of length 12, and any number.
+ * `isObjectIdOrHexString()` returns true only for `ObjectId` instances or 24 character hex
+ * strings, and will return false for numbers, documents, and strings of length 12.
+ *
+ * #### Example:
+ *
+ * mongoose.isObjectIdOrHexString(new mongoose.Types.ObjectId()); // true
+ * mongoose.isObjectIdOrHexString('62261a65d66c6be0a63c051f'); // true
+ *
+ * mongoose.isObjectIdOrHexString('0123456789ab'); // false
+ * mongoose.isObjectIdOrHexString(6); // false
+ * mongoose.isObjectIdOrHexString(new User({ name: 'test' })); // false
+ * mongoose.isObjectIdOrHexString({ test: 42 }); // false
+ *
+ * @method isObjectIdOrHexString
+ * @param {Any} v
+ * @returns {boolean} true if `v` is an ObjectId instance _or_ a 24 char hex string
+ * @api public
+ */
- if (typeof v === 'string' && v.length === 12) {
- return true;
- }
- if (typeof v === 'string' && /^[0-9A-Fa-f]{24}$/.test(v)) {
- return true;
- }
+Mongoose.prototype.isObjectIdOrHexString = function(v) {
+ return isBsonType(v, 'ObjectID') || (typeof v === 'string' && objectIdHexRegexp.test(v));
+};
- return false;
+/**
+ *
+ * Syncs all the indexes for the models registered with this connection.
+ *
+ * @param {Object} options
+ * @param {Boolean} options.continueOnError `false` by default. If set to `true`, mongoose will not throw an error if one model syncing failed, and will return an object where the keys are the names of the models, and the values are the results/errors for each model.
+ * @return {Promise} Returns a Promise, when the Promise resolves the value is a list of the dropped indexes.
+ */
+Mongoose.prototype.syncIndexes = function(options) {
+ const _mongoose = this instanceof Mongoose ? this : mongoose;
+ return _mongoose.connection.syncIndexes(options);
};
/**
* The Mongoose Decimal128 [SchemaType](/docs/schematypes.html). Used for
* declaring paths in your schema that should be
- * [128-bit decimal floating points](http://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-decimal.html).
+ * [128-bit decimal floating points](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-decimal.html).
* Do not use this to create a new Decimal128 instance, use `mongoose.Types.Decimal128`
* instead.
*
- * ####Example:
+ * #### Example:
*
* const vehicleSchema = new Schema({ fuelLevel: mongoose.Decimal128 });
*
@@ -1006,7 +1143,7 @@ Mongoose.prototype.Decimal128 = SchemaTypes.Decimal128;
* declaring paths in your schema that Mongoose's change tracking, casting,
* and validation should ignore.
*
- * ####Example:
+ * #### Example:
*
* const schema = new Schema({ arbitrary: mongoose.Mixed });
*
@@ -1019,7 +1156,7 @@ Mongoose.prototype.Mixed = SchemaTypes.Mixed;
/**
* The Mongoose Date [SchemaType](/docs/schematypes.html).
*
- * ####Example:
+ * #### Example:
*
* const schema = new Schema({ test: Date });
* schema.path('test') instanceof mongoose.Date; // true
@@ -1034,7 +1171,7 @@ Mongoose.prototype.Date = SchemaTypes.Date;
* The Mongoose Number [SchemaType](/docs/schematypes.html). Used for
* declaring paths in your schema that Mongoose should cast to numbers.
*
- * ####Example:
+ * #### Example:
*
* const schema = new Schema({ num: mongoose.Number });
* // Equivalent to:
@@ -1153,12 +1290,49 @@ Mongoose.prototype._promiseOrCallback = function(callback, fn, ee) {
return promiseOrCallback(callback, fn, ee, this.Promise);
};
-/*!
- * The exports object is an instance of Mongoose.
+/**
+ * Use this function in `pre()` middleware to skip calling the wrapped function.
+ *
+ * #### Example:
+ *
+ * schema.pre('save', function() {
+ * // Will skip executing `save()`, but will execute post hooks as if
+ * // `save()` had executed with the result `{ matchedCount: 0 }`
+ * return mongoose.skipMiddlewareFunction({ matchedCount: 0 });
+ * });
*
+ * @method skipMiddlewareFunction
+ * @param {any} result
* @api public
*/
+Mongoose.prototype.skipMiddlewareFunction = Kareem.skipWrappedFunction;
+
+/**
+ * Use this function in `post()` middleware to replace the result
+ *
+ * #### Example:
+ *
+ * schema.post('find', function(res) {
+ * // Normally you have to modify `res` in place. But with
+ * // `overwriteMiddlewarResult()`, you can make `find()` return a
+ * // completely different value.
+ * return mongoose.overwriteMiddlewareResult(res.filter(doc => !doc.isDeleted));
+ * });
+ *
+ * @method overwriteMiddlewareResult
+ * @param {any} result
+ * @api public
+ */
+
+Mongoose.prototype.overwriteMiddlewareResult = Kareem.overwriteResult;
+
+/**
+ * The exports object is an instance of Mongoose.
+ *
+ * @api private
+ */
+
const mongoose = module.exports = exports = new Mongoose({
[defaultMongooseSymbol]: true
});
diff --git a/lib/internal.js b/lib/internal.js
index 7e534dc994f..c4445c254d6 100644
--- a/lib/internal.js
+++ b/lib/internal.js
@@ -10,14 +10,12 @@ const ActiveRoster = StateMachine.ctor('require', 'modify', 'init', 'default', '
module.exports = exports = InternalCache;
function InternalCache() {
- this.activePaths = new ActiveRoster;
-
- // embedded docs
- this.ownerDocument = undefined;
- this.fullPath = undefined;
+ this.activePaths = new ActiveRoster();
}
-InternalCache.prototype.strictMode = undefined;
+InternalCache.prototype.strictMode = true;
+
+InternalCache.prototype.fullPath = undefined;
InternalCache.prototype.selected = undefined;
InternalCache.prototype.shardval = undefined;
InternalCache.prototype.saveError = undefined;
@@ -28,9 +26,19 @@ InternalCache.prototype.inserting = undefined;
InternalCache.prototype.saving = undefined;
InternalCache.prototype.version = undefined;
InternalCache.prototype._id = undefined;
+InternalCache.prototype.ownerDocument = undefined;
InternalCache.prototype.populate = undefined; // what we want to populate in this doc
InternalCache.prototype.populated = undefined;// the _ids that have been populated
-InternalCache.prototype.wasPopulated = false; // if this doc was the result of a population
+InternalCache.prototype.primitiveAtomics = undefined;
+
+/**
+ * If `false`, this document was not the result of population.
+ * If `true`, this document is a populated doc underneath another doc
+ * If an object, this document is a populated doc and the `value` property of the
+ * object contains the original depopulated value.
+ */
+InternalCache.prototype.wasPopulated = false;
+
InternalCache.prototype.scope = undefined;
InternalCache.prototype.session = null;
diff --git a/lib/model.js b/lib/model.js
index 054bdcf2f88..313d1f4747f 100644
--- a/lib/model.js
+++ b/lib/model.js
@@ -22,9 +22,13 @@ const ServerSelectionError = require('./error/serverSelection');
const ValidationError = require('./error/validation');
const VersionError = require('./error/version');
const ParallelSaveError = require('./error/parallelSave');
+const applyDefaultsHelper = require('./helpers/document/applyDefaults');
+const applyDefaultsToPOJO = require('./helpers/model/applyDefaultsToPOJO');
const applyQueryMiddleware = require('./helpers/query/applyQueryMiddleware');
const applyHooks = require('./helpers/model/applyHooks');
const applyMethods = require('./helpers/model/applyMethods');
+const applyProjection = require('./helpers/projection/applyProjection');
+const applySchemaCollation = require('./helpers/indexes/applySchemaCollation');
const applyStaticHooks = require('./helpers/model/applyStaticHooks');
const applyStatics = require('./helpers/model/applyStatics');
const applyWriteConcern = require('./helpers/schema/applyWriteConcern');
@@ -32,7 +36,9 @@ const assignVals = require('./helpers/populate/assignVals');
const castBulkWrite = require('./helpers/model/castBulkWrite');
const createPopulateQueryFilter = require('./helpers/populate/createPopulateQueryFilter');
const getDefaultBulkwriteResult = require('./helpers/getDefaultBulkwriteResult');
+const getSchemaDiscriminatorByValue = require('./helpers/discriminator/getSchemaDiscriminatorByValue');
const discriminator = require('./helpers/model/discriminator');
+const firstKey = require('./helpers/firstKey');
const each = require('./helpers/each');
const get = require('./helpers/get');
const getConstructorName = require('./helpers/getConstructorName');
@@ -42,11 +48,22 @@ const immediate = require('./helpers/immediate');
const internalToObjectOptions = require('./options').internalToObjectOptions;
const isDefaultIdIndex = require('./helpers/indexes/isDefaultIdIndex');
const isIndexEqual = require('./helpers/indexes/isIndexEqual');
+const {
+ getRelatedDBIndexes,
+ getRelatedSchemaIndexes
+} = require('./helpers/indexes/getRelatedIndexes');
+const isPathExcluded = require('./helpers/projection/isPathExcluded');
+const decorateDiscriminatorIndexOptions = require('./helpers/indexes/decorateDiscriminatorIndexOptions');
const isPathSelectedInclusive = require('./helpers/projection/isPathSelectedInclusive');
const leanPopulateMap = require('./helpers/populate/leanPopulateMap');
const modifiedPaths = require('./helpers/update/modifiedPaths');
const parallelLimit = require('./helpers/parallelLimit');
+const parentPaths = require('./helpers/path/parentPaths');
+const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscriminatorPipeline');
+const pushNestedArrayPaths = require('./helpers/model/pushNestedArrayPaths');
const removeDeselectedForeignField = require('./helpers/populate/removeDeselectedForeignField');
+const setDottedPath = require('./helpers/path/setDottedPath');
+const STATES = require('./connectionstate');
const util = require('util');
const utils = require('./utils');
@@ -66,15 +83,15 @@ const saveToObjectOptions = Object.assign({}, internalToObjectOptions, {
/**
* A Model is a class that's your primary tool for interacting with MongoDB.
- * An instance of a Model is called a [Document](./api.html#Document).
+ * An instance of a Model is called a [Document](/docs/api/document.html#Document).
*
* In Mongoose, the term "Model" refers to subclasses of the `mongoose.Model`
* class. You should not use the `mongoose.Model` class directly. The
- * [`mongoose.model()`](./api.html#mongoose_Mongoose-model) and
- * [`connection.model()`](./api.html#connection_Connection-model) functions
+ * [`mongoose.model()`](/docs/api/mongoose.html#mongoose_Mongoose-model) and
+ * [`connection.model()`](/docs/api/connection.html#connection_Connection-model) functions
* create subclasses of `mongoose.Model` as shown below.
*
- * ####Example:
+ * #### Example:
*
* // `UserModel` is a "Model", a subclass of `mongoose.Model`.
* const UserModel = mongoose.model('User', new Schema({ name: String }));
@@ -87,9 +104,9 @@ const saveToObjectOptions = Object.assign({}, internalToObjectOptions, {
* const userFromDb = await UserModel.findOne({ name: 'Foo' });
*
* @param {Object} doc values for initial set
- * @param [fields] optional object containing the fields that were selected in the query which returned this document. You do **not** need to set this parameter to ensure Mongoose handles your [query projection](./api.html#query_Query-select).
+ * @param {Object} [fields] optional object containing the fields that were selected in the query which returned this document. You do **not** need to set this parameter to ensure Mongoose handles your [query projection](/docs/api/query.html#query_Query-select).
* @param {Boolean} [skipId=false] optional boolean. If true, mongoose doesn't add an `_id` field to the document.
- * @inherits Document http://mongoosejs.com/docs/api/document.html
+ * @inherits Document https://mongoosejs.com/docs/api/document.html
* @event `error`: If listening to this event, 'error' is emitted when a document was saved without passing a callback and an `error` occurred. If not listening, the event bubbles to the connection used to create this Model.
* @event `index`: Emitted after `Model#ensureIndexes` completes. If an error occurred it is passed with the event.
* @event `index-single-start`: Emitted when an individual index starts within `Model#ensureIndexes`. The fields and options being used to build the index are also passed with the event.
@@ -106,14 +123,15 @@ function Model(doc, fields, skipId) {
Document.call(this, doc, fields, skipId);
}
-/*!
+/**
* Inherits from Document.
*
* All Model.prototype features are available on
* top level (non-sub) documents.
+ * @api private
*/
-Model.prototype.__proto__ = Document.prototype;
+Object.setPrototypeOf(Model.prototype, Document.prototype);
Model.prototype.$isMongooseModelPrototype = true;
/**
@@ -193,22 +211,23 @@ Model.prototype.baseModelName;
* Event emitter that reports any errors that occurred. Useful for global error
* handling.
*
- * ####Example:
+ * #### Example:
*
* MyModel.events.on('error', err => console.log(err.message));
*
* // Prints a 'CastError' because of the above handler
- * await MyModel.findOne({ _id: 'notanid' }).catch(noop);
+ * await MyModel.findOne({ _id: 'Not a valid ObjectId' }).catch(noop);
*
* @api public
+ * @property events
* @fires error whenever any query or model function errors
* @memberOf Model
- * @static events
+ * @static
*/
Model.events;
-/*!
+/**
* Compiled middleware for this model. Set in `applyHooks()`.
*
* @api private
@@ -237,11 +256,10 @@ function _applyCustomWhere(doc, where) {
*/
Model.prototype.$__handleSave = function(options, callback) {
- const _this = this;
- let saveOptions = {};
+ const saveOptions = {};
applyWriteConcern(this.$__schema, options);
- if (typeof options.writeConcern != 'undefined') {
+ if (typeof options.writeConcern !== 'undefined') {
saveOptions.writeConcern = {};
if ('w' in options.writeConcern) {
saveOptions.writeConcern.w = options.writeConcern.w;
@@ -266,14 +284,12 @@ Model.prototype.$__handleSave = function(options, callback) {
if ('checkKeys' in options) {
saveOptions.checkKeys = options.checkKeys;
}
+
const session = this.$session();
- if (!saveOptions.hasOwnProperty('session')) {
+ if (!saveOptions.hasOwnProperty('session') && session != null) {
saveOptions.session = session;
}
- if (Object.keys(saveOptions).length === 0) {
- saveOptions = null;
- }
if (this.$isNew) {
// send entire doc
const obj = this.toObject(saveToObjectOptions);
@@ -290,9 +306,9 @@ Model.prototype.$__handleSave = function(options, callback) {
}
this.$__version(true, obj);
- this[modelCollectionSymbol].insertOne(obj, saveOptions, function(err, ret) {
+ this[modelCollectionSymbol].insertOne(obj, saveOptions, (err, ret) => {
if (err) {
- _setIsNew(_this, true);
+ _setIsNew(this, true);
callback(err, null);
return;
@@ -300,69 +316,68 @@ Model.prototype.$__handleSave = function(options, callback) {
callback(null, ret);
});
+
this.$__reset();
_setIsNew(this, false);
// Make it possible to retry the insert
this.$__.inserting = true;
- } else {
- // Make sure we don't treat it as a new object on error,
- // since it already exists
- this.$__.inserting = false;
-
- const delta = this.$__delta();
- if (delta) {
- if (delta instanceof MongooseError) {
- callback(delta);
- return;
- }
- const where = this.$__where(delta[0]);
- if (where instanceof MongooseError) {
- callback(where);
- return;
- }
+ return;
+ }
- _applyCustomWhere(this, where);
- this[modelCollectionSymbol].updateOne(where, delta[1], saveOptions, (err, ret) => {
- if (err) {
- this.$__undoReset();
+ // Make sure we don't treat it as a new object on error,
+ // since it already exists
+ this.$__.inserting = false;
- callback(err);
- return;
- }
- ret.$where = where;
- callback(null, ret);
- });
- } else {
- const optionsWithCustomValues = Object.assign({}, options, saveOptions);
- const where = this.$__where();
- if (this.$__schema.options.optimisticConcurrency) {
- const key = this.$__schema.options.versionKey;
- const val = this.$__getValue(key);
- if (val != null) {
- where[key] = val;
- }
- }
- this.constructor.exists(where, optionsWithCustomValues).
- then((documentExists) => {
- if (!documentExists) {
- const matchedCount = 0;
- return callback(null, { $where: where, matchedCount });
- }
+ const delta = this.$__delta();
+ if (delta) {
+ if (delta instanceof MongooseError) {
+ callback(delta);
+ return;
+ }
- const matchedCount = 1;
- callback(null, { $where: where, matchedCount });
- }).
- catch(callback);
+ const where = this.$__where(delta[0]);
+ if (where instanceof MongooseError) {
+ callback(where);
return;
}
- // store the modified paths before the document is reset
- this.$__.modifiedPaths = this.modifiedPaths();
- this.$__reset();
+ _applyCustomWhere(this, where);
+ this[modelCollectionSymbol].updateOne(where, delta[1], saveOptions, (err, ret) => {
+ if (err) {
+ this.$__undoReset();
- _setIsNew(this, false);
+ callback(err);
+ return;
+ }
+ ret.$where = where;
+ callback(null, ret);
+ });
+ } else {
+ const optionsWithCustomValues = Object.assign({}, options, saveOptions);
+ const where = this.$__where();
+ const optimisticConcurrency = this.$__schema.options.optimisticConcurrency;
+ if (optimisticConcurrency) {
+ const key = this.$__schema.options.versionKey;
+ const val = this.$__getValue(key);
+ if (val != null) {
+ where[key] = val;
+ }
+ }
+ this.constructor.exists(where, optionsWithCustomValues)
+ .then(documentExists => {
+ const matchedCount = !documentExists ? 0 : 1;
+ callback(null, { $where: where, matchedCount });
+ })
+ .catch(callback);
+ return;
}
+
+ // store the modified paths before the document is reset
+ this.$__.modifiedPaths = this.modifiedPaths();
+ this.$__reset();
+
+ _setIsNew(this, false);
};
/*!
@@ -371,14 +386,19 @@ Model.prototype.$__handleSave = function(options, callback) {
Model.prototype.$__save = function(options, callback) {
this.$__handleSave(options, (error, result) => {
- const hooks = this.$__schema.s.hooks;
if (error) {
+ const hooks = this.$__schema.s.hooks;
return hooks.execPost('save:error', this, [this], { error: error }, (error) => {
callback(error, this);
});
}
let numAffected = 0;
- if (get(options, 'safe.w') !== 0 && get(options, 'w') !== 0) {
+ const writeConcern = options != null ?
+ options.writeConcern != null ?
+ options.writeConcern.w :
+ options.w :
+ 0;
+ if (writeConcern !== 0) {
// Skip checking if write succeeded if writeConcern is set to
// unacknowledged writes, because otherwise `numAffected` will always be 0
if (result != null) {
@@ -390,8 +410,10 @@ Model.prototype.$__save = function(options, callback) {
numAffected = result;
}
}
+
+ const versionBump = this.$__.version;
// was this an update that required a version bump?
- if (this.$__.version && !this.$__.inserting) {
+ if (versionBump && !this.$__.inserting) {
const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version);
this.$__.version = undefined;
const key = this.$__schema.options.versionKey;
@@ -413,6 +435,7 @@ Model.prototype.$__save = function(options, callback) {
this.$__undoReset();
error = new DocumentNotFoundError(result.$where,
this.constructor.modelName, numAffected, result);
+ const hooks = this.$__schema.s.hooks;
return hooks.execPost('save:error', this, [this], { error: error }, (error) => {
callback(error, this);
});
@@ -440,10 +463,10 @@ function generateVersionError(doc, modifiedPaths) {
}
/**
- * Saves this document by inserting a new document into the database if [document.isNew](/docs/api.html#document_Document-isNew) is `true`,
- * or sends an [updateOne](/docs/api.html#document_Document-updateOne) operation with just the modified paths if `isNew` is `false`.
+ * Saves this document by inserting a new document into the database if [document.isNew](/docs/api/document.html#document_Document-isNew) is `true`,
+ * or sends an [updateOne](/docs/api/document.html#document_Document-updateOne) operation with just the modified paths if `isNew` is `false`.
*
- * ####Example:
+ * #### Example:
*
* product.sold = Date.now();
* product = await product.save();
@@ -451,26 +474,26 @@ function generateVersionError(doc, modifiedPaths) {
* If save is successful, the returned promise will fulfill with the document
* saved.
*
- * ####Example:
+ * #### Example:
*
* const newProduct = await product.save();
* newProduct === product; // true
*
* @param {Object} [options] options optional options
- * @param {Session} [options.session=null] the [session](https://docs.mongodb.com/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](api.html#document_Document-$session).
- * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](http://mongoosejs.com//docs/guide.html#safe). Use the `w` option instead.
+ * @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](/docs/api/document.html#document_Document-$session).
+ * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](https://mongoosejs.com//docs/guide.html#safe). Use the `w` option instead.
* @param {Boolean} [options.validateBeforeSave] set to false to save without validating.
* @param {Boolean} [options.validateModifiedOnly=false] if `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths.
- * @param {Number|String} [options.w] set the [write concern](https://docs.mongodb.com/manual/reference/write-concern/#w-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern)
- * @param {Boolean} [options.j] set to true for MongoDB to wait until this `save()` has been [journaled before resolving the returned promise](https://docs.mongodb.com/manual/reference/write-concern/#j-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern)
- * @param {Number} [options.wtimeout] sets a [timeout for the write concern](https://docs.mongodb.com/manual/reference/write-concern/#wtimeout). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern).
- * @param {Boolean} [options.checkKeys=true] the MongoDB driver prevents you from saving keys that start with '$' or contain '.' by default. Set this option to `false` to skip that check. See [restrictions on field names](https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names)
- * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](./guide.html#timestamps) are enabled, skip timestamps for this `save()`.
+ * @param {Number|String} [options.w] set the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#w-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern)
+ * @param {Boolean} [options.j] set to true for MongoDB to wait until this `save()` has been [journaled before resolving the returned promise](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern)
+ * @param {Number} [options.wtimeout] sets a [timeout for the write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern).
+ * @param {Boolean} [options.checkKeys=true] the MongoDB driver prevents you from saving keys that start with '$' or contain '.' by default. Set this option to `false` to skip that check. See [restrictions on field names](https://www.mongodb.com/docs/manual/reference/limits/#Restrictions-on-Field-Names)
+ * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this `save()`.
* @param {Function} [fn] optional callback
- * @throws {DocumentNotFoundError} if this [save updates an existing document](api.html#document_Document-isNew) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating).
+ * @throws {DocumentNotFoundError} if this [save updates an existing document](/docs/api/document.html#document_Document-isNew) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating).
* @return {Promise|undefined} Returns undefined if used with callback or a Promise otherwise.
* @api public
- * @see middleware http://mongoosejs.com/docs/middleware.html
+ * @see middleware https://mongoosejs.com/docs/middleware.html
*/
Model.prototype.save = function(options, fn) {
@@ -492,6 +515,9 @@ Model.prototype.save = function(options, fn) {
if (options.hasOwnProperty('session')) {
this.$session(options.session);
}
+ if (this.$__.timestamps != null) {
+ options.timestamps = this.$__.timestamps;
+ }
this.$__.$versionError = generateVersionError(this, this.modifiedPaths());
fn = this.constructor.$handleCallbackError(fn);
@@ -506,9 +532,9 @@ Model.prototype.save = function(options, fn) {
this.$__.saveOptions = options;
this.$__save(options, error => {
- this.$__.saving = undefined;
- delete this.$__.saveOptions;
- delete this.$__.$versionError;
+ this.$__.saving = null;
+ this.$__.saveOptions = null;
+ this.$__.$versionError = null;
this.$op = null;
if (error) {
@@ -522,12 +548,13 @@ Model.prototype.save = function(options, fn) {
Model.prototype.$save = Model.prototype.save;
-/*!
+/**
* Determines whether versioning should be skipped for the given path
*
* @param {Document} self
* @param {String} path
* @return {Boolean} true if versioning should be skipped for the given path
+ * @api private
*/
function shouldSkipVersioning(self, path) {
const skipVersioning = self.$__schema.options.skipVersioning;
@@ -539,16 +566,17 @@ function shouldSkipVersioning(self, path) {
return skipVersioning[path];
}
-/*!
+/**
* Apply the operation to the delta (update) clause as
* well as track versioning for our where clause.
*
* @param {Document} self
- * @param {Object} where
+ * @param {Object} where Unused
* @param {Object} delta
* @param {Object} data
* @param {Mixed} val
- * @param {String} [operation]
+ * @param {String} [op]
+ * @api private
*/
function operand(self, where, delta, data, val, op) {
@@ -566,7 +594,6 @@ function operand(self, where, delta, data, val, op) {
if (VERSION_ALL === (VERSION_ALL & self.$__.version)) return;
if (self.$__schema.options.optimisticConcurrency) {
- self.$__.version = VERSION_ALL;
return;
}
@@ -578,6 +605,7 @@ function operand(self, where, delta, data, val, op) {
case '$pullAll':
case '$push':
case '$addToSet':
+ case '$inc':
break;
default:
// nothing to do
@@ -589,7 +617,11 @@ function operand(self, where, delta, data, val, op) {
// only increment the version if an array position changes.
// modifying elements of an array is ok if position does not change.
if (op === '$push' || op === '$addToSet' || op === '$pullAll' || op === '$pull') {
- self.$__.version = VERSION_INC;
+ if (/\.\d+\.|\.\d+$/.test(data.path)) {
+ increment.call(self);
+ } else {
+ self.$__.version = VERSION_INC;
+ }
} else if (/^\$p/.test(op)) {
// potentially changing array positions
increment.call(self);
@@ -603,7 +635,7 @@ function operand(self, where, delta, data, val, op) {
}
}
-/*!
+/**
* Compiles an update and where clause for a `val` with _atomics.
*
* @param {Document} self
@@ -611,6 +643,7 @@ function operand(self, where, delta, data, val, op) {
* @param {Object} delta
* @param {Object} data
* @param {Array} value
+ * @api private
*/
function handleAtomics(self, where, delta, data, value) {
@@ -685,6 +718,12 @@ function handleAtomics(self, where, delta, data, value) {
Model.prototype.$__delta = function() {
const dirty = this.$__dirty();
+
+ const optimisticConcurrency = this.$__schema.options.optimisticConcurrency;
+ if (optimisticConcurrency) {
+ this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
+ }
+
if (!dirty.length && VERSION_ALL !== this.$__.version) {
return;
}
@@ -697,7 +736,7 @@ Model.prototype.$__delta = function() {
where._id = this._doc._id;
// If `_id` is an object, need to depopulate, but also need to be careful
// because `_id` can technically be null (see gh-6406)
- if (get(where, '_id.$__', null) != null) {
+ if ((where && where._id && where._id.$__ || null) != null) {
where._id = where._id.toObject({ transform: false, depopulate: true });
}
for (; d < len; ++d) {
@@ -730,12 +769,25 @@ Model.prototype.$__delta = function() {
}
}
+ // If this path is set to default, and either this path or one of
+ // its parents is excluded, don't treat this path as dirty.
+ if (this.$isDefault(data.path) && this.$__.selected) {
+ if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) {
+ continue;
+ }
+
+ const pathsToCheck = parentPaths(data.path);
+ if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) {
+ continue;
+ }
+ }
+
if (divergent.length) continue;
if (value === undefined) {
operand(this, where, delta, data, 1, '$unset');
} else if (value === null) {
operand(this, where, delta, data, null);
- } else if (value.isMongooseArray && value.$path() && value[arrayAtomicsSymbol]) {
+ } else if (utils.isMongooseArray(value) && value.$path() && value[arrayAtomicsSymbol]) {
// arrays and other custom types (support plugins etc)
handleAtomics(this, where, delta, data, value);
} else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) {
@@ -743,14 +795,21 @@ Model.prototype.$__delta = function() {
value = value.toObject();
operand(this, where, delta, data, value);
} else {
- value = utils.clone(value, {
- depopulate: true,
- transform: false,
- virtuals: false,
- getters: false,
- _isNested: true
- });
- operand(this, where, delta, data, value);
+ if (this.$__.primitiveAtomics && this.$__.primitiveAtomics[data.path] != null) {
+ const val = this.$__.primitiveAtomics[data.path];
+ const op = firstKey(val);
+ operand(this, where, delta, data, val[op], op);
+ } else {
+ value = utils.clone(value, {
+ depopulate: true,
+ transform: false,
+ virtuals: false,
+ getters: false,
+ omitUndefined: true,
+ _isNested: true
+ });
+ operand(this, where, delta, data, value);
+ }
}
}
@@ -761,17 +820,24 @@ Model.prototype.$__delta = function() {
if (this.$__.version) {
this.$__version(where, delta);
}
+
+ if (Object.keys(delta).length === 0) {
+ return [where, null];
+ }
+
return [where, delta];
};
-/*!
+/**
* Determine if array was populated with some form of filter and is now
* being updated in a manner which could overwrite data unintentionally.
*
* @see https://github.com/Automattic/mongoose/issues/1334
* @param {Document} doc
* @param {String} path
+ * @param {Any} array
* @return {String|undefined}
+ * @api private
*/
function checkDivergentArray(doc, path, array) {
@@ -787,7 +853,7 @@ function checkDivergentArray(doc, path, array) {
}
}
- if (!(pop && array && array.isMongooseArray)) return;
+ if (!(pop && utils.isMongooseArray(array))) return;
// If the array was populated using options that prevented all
// documents from being returned (match, skip, limit) or they
@@ -796,7 +862,7 @@ function checkDivergentArray(doc, path, array) {
// how to remove elements. $pop will pop off the _id from the end
// of the array in the db which is not guaranteed to be the
// same as the last element we have here. $set of the entire array
- // would be similarily destructive as we never received all
+ // would be similarly destructive as we never received all
// elements of the array and potentially would overwrite data.
const check = pop.options.match ||
pop.options.options && utils.object.hasOwnProperty(pop.options.options, 'limit') || // 0 is not permitted
@@ -824,21 +890,26 @@ function checkDivergentArray(doc, path, array) {
Model.prototype.$__version = function(where, delta) {
const key = this.$__schema.options.versionKey;
-
if (where === true) {
// this is an insert
if (key) {
- this.$__setValue(key, delta[key] = 0);
+ setDottedPath(delta, key, 0);
+ this.$__setValue(key, 0);
}
return;
}
+ if (key === false) {
+ return;
+ }
+
// updates
// only apply versioning if our versionKey was selected. else
// there is no way to select the correct version. we could fail
// fast here and force them to include the versionKey but
// thats a bit intrusive. can we do this automatically?
+
if (!this.$__isSelected(key)) {
return;
}
@@ -862,25 +933,30 @@ Model.prototype.$__version = function(where, delta) {
}
};
+/*!
+ * ignore
+ */
+
+function increment() {
+ this.$__.version = VERSION_ALL;
+ return this;
+}
+
/**
* Signal that we desire an increment of this documents version.
*
- * ####Example:
+ * #### Example:
*
- * Model.findById(id, function (err, doc) {
- * doc.increment();
- * doc.save(function (err) { .. })
- * })
+ * const doc = await Model.findById(id);
+ * doc.increment();
+ * await doc.save();
*
- * @see versionKeys http://mongoosejs.com/docs/guide.html#versionKey
+ * @see versionKeys https://mongoosejs.com/docs/guide.html#versionKey
+ * @memberOf Model
+ * @method increment
* @api public
*/
-function increment() {
- this.$__.version = VERSION_ALL;
- return this;
-}
-
Model.prototype.increment = increment;
/**
@@ -909,26 +985,16 @@ Model.prototype.$__where = function _where(where) {
/**
* Removes this document from the db.
*
- * ####Example:
- * product.remove(function (err, product) {
- * if (err) return handleError(err);
- * Product.findById(product._id, function (err, product) {
- * console.log(product) // null
- * })
- * })
- *
+ * #### Example:
*
- * As an extra measure of flow control, remove will return a Promise (bound to `fn` if passed) so it could be chained, or hooked to receive errors
- *
- * ####Example:
- * product.remove().then(function (product) {
- * ...
- * }).catch(function (err) {
- * assert.ok(err)
- * })
+ * const product = await product.remove().catch(function (err) {
+ * assert.ok(err);
+ * });
+ * const foundProduct = await Product.findById(product._id);
+ * console.log(foundProduct) // null
*
* @param {Object} [options]
- * @param {Session} [options.session=null] the [session](https://docs.mongodb.com/manual/reference/server-sessions/) associated with this operation. If not specified, defaults to the [document's associated session](api.html#document_Document-$session).
+ * @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this operation. If not specified, defaults to the [document's associated session](/docs/api/document.html#document_Document-$session).
* @param {function(err,product)} [fn] optional callback
* @return {Promise} Promise
* @api public
@@ -957,8 +1023,14 @@ Model.prototype.remove = function remove(options, fn) {
}, this.constructor.events);
};
-/*!
+/**
* Alias for remove
+ *
+ * @method $remove
+ * @memberOf Model
+ * @instance
+ * @api public
+ * @see Model.remove #model_Model-remove
*/
Model.prototype.$remove = Model.prototype.remove;
@@ -967,7 +1039,8 @@ Model.prototype.delete = Model.prototype.remove;
/**
* Removes this document from the db. Equivalent to `.remove()`.
*
- * ####Example:
+ * #### Example:
+ *
* product = await product.deleteOne();
* await Product.findById(product._id); // null
*
@@ -1036,13 +1109,15 @@ Model.prototype.$__deleteOne = Model.prototype.$__remove;
/**
* Returns another Model instance.
*
- * ####Example:
+ * #### Example:
*
* const doc = new Tank;
* doc.model('User').findById(id, callback);
*
* @param {String} name model name
+ * @method model
* @api public
+ * @return {Model}
*/
Model.prototype.model = function model(name) {
@@ -1050,13 +1125,32 @@ Model.prototype.model = function model(name) {
};
/**
- * Returns true if at least one document exists in the database that matches
- * the given `filter`, and false otherwise.
+ * Returns another Model instance.
+ *
+ * #### Example:
+ *
+ * const doc = new Tank;
+ * doc.model('User').findById(id, callback);
+ *
+ * @param {String} name model name
+ * @method $model
+ * @api public
+ * @return {Model}
+ */
+
+Model.prototype.$model = function $model(name) {
+ return this[modelDbSymbol].model(name);
+};
+
+/**
+ * Returns a document with `_id` only if at least one document exists in the database that matches
+ * the given `filter`, and `null` otherwise.
*
* Under the hood, `MyModel.exists({ answer: 42 })` is equivalent to
* `MyModel.findOne({ answer: 42 }).select({ _id: 1 }).lean()`
*
- * ####Example:
+ * #### Example:
+ *
* await Character.deleteMany({});
* await Character.create({ name: 'Jean-Luc Picard' });
*
@@ -1068,7 +1162,7 @@ Model.prototype.model = function model(name) {
* - `findOne()`
*
* @param {Object} filter
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
* @param {Function} [callback] callback
* @return {Query}
*/
@@ -1088,10 +1182,6 @@ Model.exists = function exists(filter, options, callback) {
if (typeof callback === 'function') {
return query.exec(callback);
}
- options = options || {};
- if (!options.explain) {
- return query.then(doc => !!doc);
- }
return query;
};
@@ -1099,7 +1189,7 @@ Model.exists = function exists(filter, options, callback) {
/**
* Adds a discriminator type.
*
- * ####Example:
+ * #### Example:
*
* function BaseSchema() {
* Schema.apply(this, arguments);
@@ -1127,6 +1217,9 @@ Model.exists = function exists(filter, options, callback) {
* @param {Object|String} [options] If string, same as `options.value`.
* @param {String} [options.value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter.
* @param {Boolean} [options.clone=true] By default, `discriminator()` clones the given `schema`. Set to `false` to skip cloning.
+ * @param {Boolean} [options.overwriteModels=false] by default, Mongoose does not allow you to define a discriminator with the same name as another discriminator. Set this to allow overwriting discriminators with the same name.
+ * @param {Boolean} [options.mergeHooks=true] By default, Mongoose merges the base schema's hooks with the discriminator schema's hooks. Set this option to `false` to make Mongoose use the discriminator schema's hooks instead.
+ * @param {Boolean} [options.mergePlugins=true] By default, Mongoose merges the base schema's plugins with the discriminator schema's plugins. Set this option to `false` to make Mongoose use the discriminator schema's plugins instead.
* @return {Model} The newly created discriminator model
* @api public
*/
@@ -1143,7 +1236,8 @@ Model.discriminator = function(name, schema, options) {
options = options || {};
const value = utils.isPOJO(options) ? options.value : options;
- const clone = get(options, 'clone', true);
+ const clone = typeof options.clone === 'boolean' ? options.clone : true;
+ const mergePlugins = typeof options.mergePlugins === 'boolean' ? options.mergePlugins : true;
_checkContext(this, 'discriminator');
@@ -1154,8 +1248,8 @@ Model.discriminator = function(name, schema, options) {
schema = schema.clone();
}
- schema = discriminator(this, name, schema, value, true);
- if (this.db.models[name]) {
+ schema = discriminator(this, name, schema, value, mergePlugins, options.mergeHooks);
+ if (this.db.models[name] && !schema.options.overwriteModels) {
throw new OverwriteModelError(name);
}
@@ -1165,7 +1259,7 @@ Model.discriminator = function(name, schema, options) {
model = this.db.model(model || name, schema, this.$__collection.name);
this.discriminators[name] = model;
const d = this.discriminators[name];
- d.prototype.__proto__ = this.prototype;
+ Object.setPrototypeOf(d.prototype, this.prototype);
Object.defineProperty(d, 'baseModelName', {
value: this.modelName,
configurable: true,
@@ -1187,8 +1281,9 @@ Model.discriminator = function(name, schema, options) {
return d;
};
-/*!
+/**
* Make sure `this` is a model
+ * @api private
*/
function _checkContext(ctx, fnName) {
@@ -1216,28 +1311,27 @@ for (const i in EventEmitter.prototype) {
}
/**
- * This function is responsible for building [indexes](https://docs.mongodb.com/manual/indexes/),
- * unless [`autoIndex`](http://mongoosejs.com/docs/guide.html#autoIndex) is turned off.
+ * This function is responsible for building [indexes](https://www.mongodb.com/docs/manual/indexes/),
+ * unless [`autoIndex`](https://mongoosejs.com/docs/guide.html#autoIndex) is turned off.
*
* Mongoose calls this function automatically when a model is created using
- * [`mongoose.model()`](/docs/api.html#mongoose_Mongoose-model) or
- * [`connection.model()`](/docs/api.html#connection_Connection-model), so you
- * don't need to call it. This function is also idempotent, so you may call it
- * to get back a promise that will resolve when your indexes are finished
- * building as an alternative to [`MyModel.on('index')`](/docs/guide.html#indexes)
+ * [`mongoose.model()`](/docs/api/mongoose.html#mongoose_Mongoose-model) or
+ * [`connection.model()`](/docs/api/connection.html#connection_Connection-model), so you
+ * don't need to call `init()` to trigger index builds.
+ *
+ * However, you _may_ need to call `init()` to get back a promise that will resolve when your indexes are finished.
+ * Calling `await Model.init()` is helpful if you need to wait for indexes to build before continuing.
+ * For example, if you want to wait for unique indexes to build before continuing with a test case.
*
- * ####Example:
+ * #### Example:
*
- * const eventSchema = new Schema({ thing: { type: 'string', unique: true }})
+ * const eventSchema = new Schema({ thing: { type: 'string', unique: true } })
* // This calls `Event.init()` implicitly, so you don't need to call
* // `Event.init()` on your own.
* const Event = mongoose.model('Event', eventSchema);
*
- * Event.init().then(function(Event) {
- * // You can also use `Event.on('index')` if you prefer event emitters
- * // over promises.
- * console.log('Indexes are done building!');
- * });
+ * await Event.init();
+ * console.log('Indexes are done building!');
*
* @api public
* @param {Function} [callback]
@@ -1258,17 +1352,30 @@ Model.init = function init(callback) {
}
const Promise = PromiseProvider.get();
- const autoIndex = utils.getOption('autoIndex',
- this.schema.options, this.db.config, this.db.base.options);
- const autoCreate = utils.getOption('autoCreate',
- this.schema.options, this.db.config, this.db.base.options);
-
- const _ensureIndexes = autoIndex ?
- cb => this.ensureIndexes({ _automatic: true }, cb) :
- cb => cb();
- const _createCollection = autoCreate ?
- cb => this.createCollection({}, cb) :
- cb => cb();
+
+ const model = this;
+ const _ensureIndexes = function(cb) {
+ const autoIndex = utils.getOption('autoIndex',
+ model.schema.options, model.db.config, model.db.base.options);
+ if (autoIndex) model.ensureIndexes({ _automatic: true }, cb);
+ else cb();
+ };
+ const _createCollection = function(cb) {
+ const conn = model.db;
+
+ if ((conn.readyState === STATES.connecting || conn.readyState === STATES.disconnected) && conn._shouldBufferCommands()) {
+ conn._queue.push({ fn: _createCollection, ctx: conn, args: [cb] });
+ } else {
+ try {
+ const autoCreate = utils.getOption('autoCreate',
+ model.schema.options, model.db.config, model.db.base.options);
+ if (autoCreate) model.createCollection({}, cb);
+ else cb();
+ } catch (err) {
+ return cb(err);
+ }
+ }
+ };
this.$init = new Promise((resolve, reject) => {
_createCollection(error => {
@@ -1307,12 +1414,12 @@ Model.init = function init(callback) {
* created. Use this method to create the collection explicitly.
*
* Note 1: You may need to call this before starting a transaction
- * See https://docs.mongodb.com/manual/core/transactions/#transactions-and-operations
+ * See https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations
*
* Note 2: You don't have to call this if your schema contains index or unique field.
* In that case, just use `Model.init()`
*
- * ####Example:
+ * #### Example:
*
* const userSchema = new Schema({ name: String })
* const User = mongoose.model('User', userSchema);
@@ -1322,7 +1429,7 @@ Model.init = function init(callback) {
* });
*
* @api public
- * @param {Object} [options] see [MongoDB driver docs](http://mongodb.github.io/node-mongodb-native/3.1/api/Db.html#createCollection)
+ * @param {Object} [options] see [MongoDB driver docs](https://mongodb.github.io/node-mongodb-native/4.9/classes/Db.html#createCollection)
* @param {Function} [callback]
* @returns {Promise}
*/
@@ -1338,13 +1445,40 @@ Model.createCollection = function createCollection(options, callback) {
options = void 0;
}
- const schemaCollation = get(this, 'schema.options.collation', null);
+ const schemaCollation = this &&
+ this.schema &&
+ this.schema.options &&
+ this.schema.options.collation;
if (schemaCollation != null) {
options = Object.assign({ collation: schemaCollation }, options);
}
- const capped = get(this, 'schema.options.capped');
- if (capped) {
- options = Object.assign({ capped: true }, capped, options);
+ const capped = this &&
+ this.schema &&
+ this.schema.options &&
+ this.schema.options.capped;
+ if (capped != null) {
+ if (typeof capped === 'number') {
+ options = Object.assign({ capped: true, size: capped }, options);
+ } else if (typeof capped === 'object') {
+ options = Object.assign({ capped: true }, capped, options);
+ }
+ }
+ const timeseries = this &&
+ this.schema &&
+ this.schema.options &&
+ this.schema.options.timeseries;
+ if (timeseries != null) {
+ options = Object.assign({ timeseries }, options);
+ if (options.expireAfterSeconds != null) {
+ // do nothing
+ } else if (options.expires != null) {
+ utils.expires(options);
+ } else if (this.schema.options.expireAfterSeconds != null) {
+ options.expireAfterSeconds = this.schema.options.expireAfterSeconds;
+ } else if (this.schema.options.expires != null) {
+ options.expires = this.schema.options.expires;
+ utils.expires(options);
+ }
}
callback = this.$handleCallbackError(callback);
@@ -1368,10 +1502,10 @@ Model.createCollection = function createCollection(options, callback) {
* the model's schema except the `_id` index, and build any indexes that
* are in your schema but not in MongoDB.
*
- * See the [introductory blog post](http://thecodebarbarian.com/whats-new-in-mongoose-5-2-syncindexes)
+ * See the [introductory blog post](https://thecodebarbarian.com/whats-new-in-mongoose-5-2-syncindexes)
* for more information.
*
- * ####Example:
+ * #### Example:
*
* const schema = new Schema({ name: { type: String, unique: true } });
* const Customer = mongoose.model('Customer', schema);
@@ -1382,33 +1516,39 @@ Model.createCollection = function createCollection(options, callback) {
* @param {Object} [options] options to pass to `ensureIndexes()`
* @param {Boolean} [options.background=null] if specified, overrides each index's `background` property
* @param {Function} [callback] optional callback
- * @return {Promise|undefined} Returns `undefined` if callback is specified, returns a promise if no callback.
+ * @return {Promise|undefined} Returns `undefined` if callback is specified, returns a promise if no callback, when the Promise resolves the value is a list of the dropped indexes.
* @api public
*/
Model.syncIndexes = function syncIndexes(options, callback) {
_checkContext(this, 'syncIndexes');
- callback = this.$handleCallbackError(callback);
-
- return this.db.base._promiseOrCallback(callback, cb => {
- cb = this.$wrapCallback(cb);
+ const model = this;
+ callback = model.$handleCallbackError(callback);
- this.createCollection(err => {
+ return model.db.base._promiseOrCallback(callback, cb => {
+ cb = model.$wrapCallback(cb);
+ model.createCollection(err => {
if (err != null && (err.name !== 'MongoServerError' || err.code !== 48)) {
return cb(err);
}
- this.cleanIndexes((err, dropped) => {
+ model.diffIndexes(err, (err, diffIndexesResult) => {
if (err != null) {
return cb(err);
}
- this.createIndexes(options, err => {
+ model.cleanIndexes({ ...options, toDrop: diffIndexesResult.toDrop }, (err, dropped) => {
if (err != null) {
return cb(err);
}
- cb(null, dropped);
+ model.createIndexes({ ...options, toCreate: diffIndexesResult.toCreate }, err => {
+ if (err != null) {
+ return cb(err);
+ }
+ cb(null, dropped);
+ });
});
});
+
});
}, this.events);
};
@@ -1418,69 +1558,97 @@ Model.syncIndexes = function syncIndexes(options, callback) {
* the result of this function would be the result of
* Model.syncIndexes().
*
- * @param {Object} options not used at all.
- * @param {Function} callback optional callback
- * @returns {Promise} which containts an object, {toDrop, toCreate}, which
- * are indexes that would be dropped in mongodb and indexes that would be created in mongodb.
+ * @param {Object} [options]
+ * @param {Function} [callback] optional callback
+ * @returns {Promise} which contains an object, {toDrop, toCreate}, which
+ * are indexes that would be dropped in MongoDB and indexes that would be created in MongoDB.
*/
Model.diffIndexes = function diffIndexes(options, callback) {
- const toDrop = [];
- const toCreate = [];
- callback = this.$handleCallbackError(callback);
- return this.db.base._promiseOrCallback(callback, cb => {
- cb = this.$wrapCallback(cb);
- this.listIndexes((err, indexes) => {
- if (indexes === undefined) {
- indexes = [];
- }
- const schemaIndexes = this.schema.indexes();
- // Iterate through the indexes created in mongodb and
- // compare against the indexes in the schema.
- for (const index of indexes) {
- let found = false;
- // Never try to drop `_id` index, MongoDB server doesn't allow it
- if (isDefaultIdIndex(index)) {
- continue;
- }
+ if (typeof options === 'function') {
+ callback = options;
+ options = null;
+ }
- for (const schemaIndex of schemaIndexes) {
- const key = schemaIndex[0];
- const options = _decorateDiscriminatorIndexOptions(this,
- utils.clone(schemaIndex[1]));
- if (isIndexEqual(key, options, index)) {
- found = true;
- }
- }
+ const model = this;
- if (!found) {
- toDrop.push(index.name);
- }
- }
- // Iterate through the indexes created on the schema and
- // compare against the indexes in mongodb.
- for (const schemaIndex of schemaIndexes) {
- const key = schemaIndex[0];
- let found = false;
- const options = _decorateDiscriminatorIndexOptions(this,
- utils.clone(schemaIndex[1]));
- for (const index of indexes) {
- if (isDefaultIdIndex(index)) {
- continue;
- }
- if (isIndexEqual(key, options, index)) {
- found = true;
- }
- }
- if (!found) {
- toCreate.push(key);
- }
+ callback = model.$handleCallbackError(callback);
+
+ return model.db.base._promiseOrCallback(callback, cb => {
+ cb = model.$wrapCallback(cb);
+ model.listIndexes((err, dbIndexes) => {
+ if (dbIndexes === undefined) {
+ dbIndexes = [];
}
+ dbIndexes = getRelatedDBIndexes(model, dbIndexes);
+
+ const schema = model.schema;
+ const schemaIndexes = getRelatedSchemaIndexes(model, schema.indexes());
+
+ const toDrop = getIndexesToDrop(schema, schemaIndexes, dbIndexes);
+ const toCreate = getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop);
+
cb(null, { toDrop, toCreate });
});
});
};
+function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop) {
+ const toCreate = [];
+
+ for (const [schemaIndexKeysObject, schemaIndexOptions] of schemaIndexes) {
+ let found = false;
+
+ const options = decorateDiscriminatorIndexOptions(schema, utils.clone(schemaIndexOptions));
+
+ for (const index of dbIndexes) {
+ if (isDefaultIdIndex(index)) {
+ continue;
+ }
+ if (
+ isIndexEqual(schemaIndexKeysObject, options, index) &&
+ !toDrop.includes(index.name)
+ ) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ toCreate.push(schemaIndexKeysObject);
+ }
+ }
+
+ return toCreate;
+}
+
+function getIndexesToDrop(schema, schemaIndexes, dbIndexes) {
+ const toDrop = [];
+
+ for (const dbIndex of dbIndexes) {
+ let found = false;
+ // Never try to drop `_id` index, MongoDB server doesn't allow it
+ if (isDefaultIdIndex(dbIndex)) {
+ continue;
+ }
+
+ for (const [schemaIndexKeysObject, schemaIndexOptions] of schemaIndexes) {
+ const options = decorateDiscriminatorIndexOptions(schema, utils.clone(schemaIndexOptions));
+ applySchemaCollation(schemaIndexKeysObject, options, schema.options);
+
+ if (isIndexEqual(schemaIndexKeysObject, options, dbIndex)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ toDrop.push(dbIndex.name);
+ }
+ }
+
+ return toDrop;
+}
/**
* Deletes all indexes that aren't defined in this model's schema. Used by
* `syncIndexes()`.
@@ -1492,67 +1660,55 @@ Model.diffIndexes = function diffIndexes(options, callback) {
* @api public
*/
-Model.cleanIndexes = function cleanIndexes(callback) {
+Model.cleanIndexes = function cleanIndexes(options, callback) {
_checkContext(this, 'cleanIndexes');
+ const model = this;
- callback = this.$handleCallbackError(callback);
+ if (typeof options === 'function') {
+ callback = options;
+ options = null;
+ }
- return this.db.base._promiseOrCallback(callback, cb => {
- const collection = this.$__collection;
+ callback = model.$handleCallbackError(callback);
- this.listIndexes((err, indexes) => {
+ return model.db.base._promiseOrCallback(callback, cb => {
+ const collection = model.$__collection;
+
+ if (Array.isArray(options && options.toDrop)) {
+ _dropIndexes(options.toDrop, collection, cb);
+ return;
+ }
+ return model.diffIndexes((err, res) => {
if (err != null) {
return cb(err);
}
- const schemaIndexes = this.schema.indexes();
- const toDrop = [];
+ const toDrop = res.toDrop;
+ _dropIndexes(toDrop, collection, cb);
+ });
- for (const index of indexes) {
- let found = false;
- // Never try to drop `_id` index, MongoDB server doesn't allow it
- if (isDefaultIdIndex(index)) {
- continue;
- }
+ });
+};
- for (const schemaIndex of schemaIndexes) {
- const key = schemaIndex[0];
- const options = _decorateDiscriminatorIndexOptions(this,
- utils.clone(schemaIndex[1]));
- if (isIndexEqual(key, options, index)) {
- found = true;
- }
- }
+function _dropIndexes(toDrop, collection, cb) {
+ if (toDrop.length === 0) {
+ return cb(null, []);
+ }
- if (!found) {
- toDrop.push(index.name);
- }
+ let remaining = toDrop.length;
+ let error = false;
+ toDrop.forEach(indexName => {
+ collection.dropIndex(indexName, err => {
+ if (err != null) {
+ error = true;
+ return cb(err);
}
-
- if (toDrop.length === 0) {
- return cb(null, []);
+ if (!error) {
+ --remaining || cb(null, toDrop);
}
-
- dropIndexes(toDrop, cb);
});
-
- function dropIndexes(toDrop, cb) {
- let remaining = toDrop.length;
- let error = false;
- toDrop.forEach(indexName => {
- collection.dropIndex(indexName, err => {
- if (err != null) {
- error = true;
- return cb(err);
- }
- if (!error) {
- --remaining || cb(null, toDrop);
- }
- });
- });
- }
});
-};
+}
/**
* Lists the indexes currently defined in MongoDB. This may or may not be
@@ -1590,7 +1746,7 @@ Model.listIndexes = function init(callback) {
* Sends `createIndex` commands to mongo for each index declared in the schema.
* The `createIndex` commands are sent in series.
*
- * ####Example:
+ * #### Example:
*
* Event.ensureIndexes(function (err) {
* if (err) return handleError(err);
@@ -1598,9 +1754,9 @@ Model.listIndexes = function init(callback) {
*
* After completion, an `index` event is emitted on this `Model` passing an error if one occurred.
*
- * ####Example:
+ * #### Example:
*
- * const eventSchema = new Schema({ thing: { type: 'string', unique: true }})
+ * const eventSchema = new Schema({ thing: { type: 'string', unique: true } })
* const Event = mongoose.model('Event', eventSchema);
*
* Event.on('index', function (err) {
@@ -1638,7 +1794,7 @@ Model.ensureIndexes = function ensureIndexes(options, callback) {
};
/**
- * Similar to `ensureIndexes()`, except for it uses the [`createIndex`](http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#createIndex)
+ * Similar to `ensureIndexes()`, except for it uses the [`createIndex`](https://mongodb.github.io/node-mongodb-native/4.9/classes/Db.html#createIndex)
* function.
*
* @param {Object} [options] internal options
@@ -1654,11 +1810,13 @@ Model.createIndexes = function createIndexes(options, callback) {
callback = options;
options = {};
}
+ callback = this.$handleCallbackError(callback);
options = options || {};
- options.createIndex = true;
+
return this.ensureIndexes(options, callback);
};
+
/*!
* ignore
*/
@@ -1681,7 +1839,7 @@ function _ensureIndexes(model, options, callback) {
utils.warn('mongoose: Cannot specify a custom index on `_id` for ' +
'model name "' + model.modelName + '", ' +
'MongoDB does not allow overwriting the default `_id` index. See ' +
- 'http://bit.ly/mongodb-id-index');
+ 'https://bit.ly/mongodb-id-index');
}
}
@@ -1704,7 +1862,17 @@ function _ensureIndexes(model, options, callback) {
const baseSchema = model.schema._baseSchema;
const baseSchemaIndexes = baseSchema ? baseSchema.indexes() : [];
- const create = function() {
+ immediate(function() {
+ // If buffering is off, do this manually.
+ if (options._automatic && !model.collection.collection) {
+ model.collection.addQueue(create, []);
+ } else {
+ create();
+ }
+ });
+
+
+ function create() {
if (options._automatic) {
if (model.schema.options.autoIndex === false ||
(model.schema.options.autoIndex == null && model.db.config.autoIndex === false)) {
@@ -1726,25 +1894,22 @@ function _ensureIndexes(model, options, callback) {
const indexFields = utils.clone(index[0]);
const indexOptions = utils.clone(index[1]);
- let isTextIndex = false;
- for (const key of Object.keys(indexFields)) {
- if (indexFields[key] === 'text') {
- isTextIndex = true;
- }
- }
+
delete indexOptions._autoIndex;
- _decorateDiscriminatorIndexOptions(model, indexOptions);
+ decorateDiscriminatorIndexOptions(model.schema, indexOptions);
applyWriteConcern(model.schema, indexOptions);
+ applySchemaCollation(indexFields, indexOptions, model.schema.options);
indexSingleStart(indexFields, options);
if ('background' in options) {
indexOptions.background = options.background;
}
- if (model.schema.options.hasOwnProperty('collation') &&
- !indexOptions.hasOwnProperty('collation') &&
- !isTextIndex) {
- indexOptions.collation = model.schema.options.collation;
+
+ if ('toCreate' in options) {
+ if (options.toCreate.length === 0) {
+ return done();
+ }
}
model.collection.createIndex(indexFields, indexOptions, utils.tick(function(err, name) {
@@ -1759,58 +1924,32 @@ function _ensureIndexes(model, options, callback) {
}
create();
}));
- };
-
- immediate(function() {
- // If buffering is off, do this manually.
- if (options._automatic && !model.collection.collection) {
- model.collection.addQueue(create, []);
- } else {
- create();
- }
- });
-}
-
-function _decorateDiscriminatorIndexOptions(model, indexOptions) {
- // If the model is a discriminator and it has a unique index, add a
- // partialFilterExpression by default so the unique index will only apply
- // to that discriminator.
- if (model.baseModelName != null &&
- !('partialFilterExpression' in indexOptions) &&
- !('sparse' in indexOptions)) {
- const value = (
- model.schema.discriminatorMapping &&
- model.schema.discriminatorMapping.value
- ) || model.modelName;
- const discriminatorKey = model.schema.options.discriminatorKey;
-
- indexOptions.partialFilterExpression = { [discriminatorKey]: value };
- }
- return indexOptions;
+ }
}
/**
* Schema the model uses.
*
* @property schema
- * @receiver Model
+ * @static
* @api public
* @memberOf Model
*/
Model.schema;
-/*!
+/**
* Connection instance the model uses.
*
* @property db
+ * @static
* @api public
* @memberOf Model
*/
Model.db;
-/*!
+/**
* Collection the model uses.
*
* @property collection
@@ -1852,7 +1991,7 @@ Model.discriminators;
/**
* Translate any aliases fields/conditions so the final query or document object is pure
*
- * ####Example:
+ * #### Example:
*
* Character
* .find(Character.translateAliases({
@@ -1860,10 +1999,11 @@ Model.discriminators;
* })
* .exec(function(err, characters) {})
*
- * ####Note:
+ * #### Note:
+ *
* Only translate arguments of object type anything else is returned raw
*
- * @param {Object} raw fields/conditions that may contain aliased keys
+ * @param {Object} fields fields/conditions that may contain aliased keys
* @return {Object} the translated 'pure' fields/conditions
*/
Model.translateAliases = function translateAliases(fields) {
@@ -1949,12 +2089,12 @@ Model.translateAliases = function translateAliases(fields) {
*
* This method is deprecated. See [Deprecation Warnings](../deprecations.html#remove) for details.
*
- * ####Example:
+ * #### Example:
*
* const res = await Character.remove({ name: 'Eddard Stark' });
* res.deletedCount; // Number of documents removed
*
- * ####Note:
+ * #### Note:
*
* This method sends a remove command directly to MongoDB, no Mongoose documents
* are involved. Because no Mongoose documents are involved, Mongoose does
@@ -1963,7 +2103,7 @@ Model.translateAliases = function translateAliases(fields) {
* @deprecated
* @param {Object} conditions
* @param {Object} [options]
- * @param {Session} [options.session=null] the [session](https://docs.mongodb.com/manual/reference/server-sessions/) associated with this operation.
+ * @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this operation.
* @param {Function} [callback]
* @return {Query}
* @api public
@@ -1996,17 +2136,17 @@ Model.remove = function remove(conditions, options, callback) {
* Behaves like `remove()`, but deletes at most one document regardless of the
* `single` option.
*
- * ####Example:
+ * #### Example:
*
* await Character.deleteOne({ name: 'Eddard Stark' }); // returns {deletedCount: 1}
*
- * ####Note:
+ * #### Note:
*
* This function triggers `deleteOne` query hooks. Read the
* [middleware docs](/docs/middleware.html#naming) to learn more.
*
* @param {Object} conditions
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
* @param {Function} [callback]
* @return {Query}
* @api public
@@ -2039,17 +2179,17 @@ Model.deleteOne = function deleteOne(conditions, options, callback) {
* Behaves like `remove()`, but deletes all documents that match `conditions`
* regardless of the `single` option.
*
- * ####Example:
+ * #### Example:
*
* await Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }); // returns {deletedCount: x} where x is the number of documents deleted.
*
- * ####Note:
+ * #### Note:
*
* This function triggers `deleteMany` query hooks. Read the
* [middleware docs](/docs/middleware.html#naming) to learn more.
*
* @param {Object} conditions
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
* @param {Function} [callback]
* @return {Query}
* @api public
@@ -2082,7 +2222,7 @@ Model.deleteMany = function deleteMany(conditions, options, callback) {
* See our [query casting tutorial](/docs/tutorials/query_casting.html) for
* more information on how Mongoose casts `filter`.
*
- * ####Examples:
+ * #### Example:
*
* // find all documents
* await MyModel.find({});
@@ -2100,8 +2240,8 @@ Model.deleteMany = function deleteMany(conditions, options, callback) {
* await MyModel.find({ name: /john/i }, null, { skip: 10 }).exec();
*
* @param {Object|ObjectId} filter
- * @param {Object|String|Array<String>} [projection] optional fields to return, see [`Query.prototype.select()`](http://mongoosejs.com/docs/api.html#query_Query-select)
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#query_Query-select)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
* @param {Function} [callback]
* @return {Query}
* @see field selection #query_Query-select
@@ -2128,14 +2268,7 @@ Model.find = function find(conditions, projection, options, callback) {
const mq = new this.Query({}, {}, this, this.$__collection);
mq.select(projection);
-
mq.setOptions(options);
- if (this.schema.discriminatorMapping &&
- this.schema.discriminatorMapping.isRoot &&
- mq.selectedInclusively()) {
- // Need to select discriminator key because original schema doesn't have it
- mq.select(this.schema.options.discriminatorKey);
- }
callback = this.$handleCallbackError(callback);
@@ -2158,7 +2291,7 @@ Model.find = function find(conditions, projection, options, callback) {
* to `findOne({})` and return arbitrary documents. However, mongoose
* translates `findById(undefined)` into `findOne({ _id: null })`.
*
- * ####Example:
+ * #### Example:
*
* // Find the adventure with the given `id`, or `null` if not found
* await Adventure.findById(id).exec();
@@ -2170,8 +2303,8 @@ Model.find = function find(conditions, projection, options, callback) {
* await Adventure.findById(id, 'name length').exec();
*
* @param {Any} id value of `_id` to query by
- * @param {Object|String|Array<String>} [projection] optional fields to return, see [`Query.prototype.select()`](#query_Query-select)
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](#query_Query-select)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
* @param {Function} [callback]
* @return {Query}
* @see field selection #query_Query-select
@@ -2201,7 +2334,7 @@ Model.findById = function findById(id, projection, options, callback) {
* mongoose will send an empty `findOne` command to MongoDB, which will return
* an arbitrary document. If you're querying by `_id`, use `findById()` instead.
*
- * ####Example:
+ * #### Example:
*
* // Find one adventure whose `country` is 'Croatia', otherwise `null`
* await Adventure.findOne({ country: 'Croatia' }).exec();
@@ -2213,8 +2346,8 @@ Model.findById = function findById(id, projection, options, callback) {
* await Adventure.findOne({ country: 'Croatia' }, 'name length').exec();
*
* @param {Object} [conditions]
- * @param {Object|String|Array<String>} [projection] optional fields to return, see [`Query.prototype.select()`](#query_Query-select)
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](#query_Query-select)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
* @param {Function} [callback]
* @return {Query}
* @see field selection #query_Query-select
@@ -2241,11 +2374,6 @@ Model.findOne = function findOne(conditions, projection, options, callback) {
const mq = new this.Query({}, {}, this, this.$__collection);
mq.select(projection);
mq.setOptions(options);
- if (this.schema.discriminatorMapping &&
- this.schema.discriminatorMapping.isRoot &&
- mq.selectedInclusively()) {
- mq.select(this.schema.options.discriminatorKey);
- }
callback = this.$handleCallbackError(callback);
return mq.findOne(conditions, callback);
@@ -2257,9 +2385,9 @@ Model.findOne = function findOne(conditions, projection, options, callback) {
* `estimatedDocumentCount()` uses collection metadata rather than scanning
* the entire collection.
*
- * ####Example:
+ * #### Example:
*
- * const numAdventures = Adventure.estimatedDocumentCount();
+ * const numAdventures = await Adventure.estimatedDocumentCount();
*
* @param {Object} [options]
* @param {Function} [callback]
@@ -2280,25 +2408,25 @@ Model.estimatedDocumentCount = function estimatedDocumentCount(options, callback
/**
* Counts number of documents matching `filter` in a database collection.
*
- * ####Example:
+ * #### Example:
*
* Adventure.countDocuments({ type: 'jungle' }, function (err, count) {
* console.log('there are %d jungle adventures', count);
* });
*
* If you want to count all documents in a large collection,
- * use the [`estimatedDocumentCount()` function](/docs/api.html#model_Model.estimatedDocumentCount)
+ * use the [`estimatedDocumentCount()` function](#model_Model-estimatedDocumentCount)
* instead. If you call `countDocuments({})`, MongoDB will always execute
* a full collection scan and **not** use any indexes.
*
* The `countDocuments()` function is similar to `count()`, but there are a
- * [few operators that `countDocuments()` does not support](https://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#countDocuments).
+ * [few operators that `countDocuments()` does not support](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#countDocuments).
* Below are the operators that `count()` supports but `countDocuments()` does not,
* and the suggested replacement:
*
- * - `$where`: [`$expr`](https://docs.mongodb.com/manual/reference/operator/query/expr/)
- * - `$near`: [`$geoWithin`](https://docs.mongodb.com/manual/reference/operator/query/geoWithin/) with [`$center`](https://docs.mongodb.com/manual/reference/operator/query/center/#op._S_center)
- * - `$nearSphere`: [`$geoWithin`](https://docs.mongodb.com/manual/reference/operator/query/geoWithin/) with [`$centerSphere`](https://docs.mongodb.com/manual/reference/operator/query/centerSphere/#op._S_centerSphere)
+ * - `$where`: [`$expr`](https://www.mongodb.com/docs/manual/reference/operator/query/expr/)
+ * - `$near`: [`$geoWithin`](https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/) with [`$center`](https://www.mongodb.com/docs/manual/reference/operator/query/center/#op._S_center)
+ * - `$nearSphere`: [`$geoWithin`](https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/) with [`$centerSphere`](https://www.mongodb.com/docs/manual/reference/operator/query/centerSphere/#op._S_centerSphere)
*
* @param {Object} filter
* @param {Function} [callback]
@@ -2332,10 +2460,10 @@ Model.countDocuments = function countDocuments(conditions, options, callback) {
* Counts number of documents that match `filter` in a database collection.
*
* This method is deprecated. If you want to count the number of documents in
- * a collection, e.g. `count({})`, use the [`estimatedDocumentCount()` function](/docs/api.html#model_Model.estimatedDocumentCount)
- * instead. Otherwise, use the [`countDocuments()`](/docs/api.html#model_Model.countDocuments) function instead.
+ * a collection, e.g. `count({})`, use the [`estimatedDocumentCount()` function](#model_Model-estimatedDocumentCount)
+ * instead. Otherwise, use the [`countDocuments()`](#model_Model-countDocuments) function instead.
*
- * ####Example:
+ * #### Example:
*
* const count = await Adventure.count({ type: 'jungle' });
* console.log('there are %d jungle adventures', count);
@@ -2367,9 +2495,9 @@ Model.count = function count(conditions, callback) {
*
* Passing a `callback` executes the query.
*
- * ####Example
+ * #### Example:
*
- * Link.distinct('url', { clicks: {$gt: 100}}, function (err, result) {
+ * Link.distinct('url', { clicks: { $gt: 100 } }, function (err, result) {
* if (err) return handleError(err);
*
* assert(Array.isArray(result));
@@ -2405,7 +2533,7 @@ Model.distinct = function distinct(field, conditions, callback) {
*
* For example, instead of writing:
*
- * User.find({age: {$gte: 21, $lte: 65}}, callback);
+ * User.find({ age: { $gte: 21, $lte: 65 } }, callback);
*
* we can instead write:
*
@@ -2459,20 +2587,7 @@ Model.$where = function $where() {
*
* Finds a matching document, updates it according to the `update` arg, passing any `options`, and returns the found document (if any) to the callback. The query executes if `callback` is passed else a Query object is returned.
*
- * ####Options:
- *
- * - `new`: bool - if true, return the modified document rather than the original. defaults to false (changed in 4.0)
- * - `upsert`: bool - creates the object if it doesn't exist. defaults to false.
- * - `overwrite`: bool - if true, replace the entire document.
- * - `fields`: {Object|String} - Field selection. Equivalent to `.select(fields).findOneAndUpdate()`
- * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
- * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
- * - `runValidators`: if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema.
- * - `setDefaultsOnInsert`: `true` by default. If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](http://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created.
- * - `rawResult`: if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify)
- * - `strict`: overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) for this update
- *
- * ####Examples:
+ * #### Example:
*
* A.findOneAndUpdate(conditions, update, options, callback) // executes
* A.findOneAndUpdate(conditions, update, options) // returns Query
@@ -2480,11 +2595,11 @@ Model.$where = function $where() {
* A.findOneAndUpdate(conditions, update) // returns Query
* A.findOneAndUpdate() // returns Query
*
- * ####Note:
+ * #### Note:
*
* All top level update keys which are not `atomic` operation names are treated as set operations:
*
- * ####Example:
+ * #### Example:
*
* const query = { name: 'borne' };
* Model.findOneAndUpdate(query, { name: 'jason bourne' }, options, callback)
@@ -2493,8 +2608,9 @@ Model.$where = function $where() {
* Model.findOneAndUpdate(query, { $set: { name: 'jason bourne' }}, options, callback)
*
* This helps prevent accidentally overwriting your document with `{ name: 'jason bourne' }`.
+ * To prevent this behaviour, see the `overwrite` option
*
- * ####Note:
+ * #### Note:
*
* `findOneAndX` and `findByIdAndX` functions support limited validation that
* you can enable by setting the `runValidators` option.
@@ -2508,19 +2624,26 @@ Model.$where = function $where() {
*
* @param {Object} [conditions]
* @param {Object} [update]
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
* @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
- * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html).
+ * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api/query.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html).
* @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html).
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
- * @param {Boolean} [options.overwrite=false] By default, if you don't include any [update operators](https://docs.mongodb.com/manual/reference/operator/update/) in `update`, Mongoose will wrap `update` in `$set` for you. This prevents you from accidentally overwriting the document. This option tells Mongoose to skip adding `$set`. An alternative to this would be using [Model.findOneAndReplace(conditions, update, options, callback)](https://mongoosejs.com/docs/api/model.html#model_Model.findOneAndReplace).
+ * @param {Boolean} [options.overwrite=false] By default, if you don't include any [update operators](https://www.mongodb.com/docs/manual/reference/operator/update/) in `update`, Mongoose will wrap `update` in `$set` for you. This prevents you from accidentally overwriting the document. This option tells Mongoose to skip adding `$set`. An alternative to this would be using [Model.findOneAndReplace(conditions, update, options, callback)](#model_Model-findOneAndReplace).
* @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
- * @param {Object|String|Array<String>} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select)
+ * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select)
+ * @param {Boolean} [options.new=false] if true, return the modified document rather than the original
+ * @param {Object|String} [options.fields] Field selection. Equivalent to `.select(fields).findOneAndUpdate()`
+ * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0
+ * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
+ * @param {Boolean} [options.runValidators] if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema
+ * @param {Boolean} [options.setDefaultsOnInsert=true] If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created
+ * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html)
* @param {Function} [callback]
* @return {Query}
* @see Tutorial /docs/tutorials/findoneandupdate.html
- * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
* @api public
*/
@@ -2563,12 +2686,13 @@ Model.findOneAndUpdate = function(conditions, update, options, callback) {
return mq.findOneAndUpdate(conditions, update, options, callback);
};
-/*!
+/**
* Decorate the update with a version key, if necessary
+ * @api private
*/
function _decorateUpdateWithVersionKey(update, options, versionKey) {
- if (!versionKey || !get(options, 'upsert', false)) {
+ if (!versionKey || !(options && options.upsert || false)) {
return;
}
@@ -2597,18 +2721,7 @@ function _decorateUpdateWithVersionKey(update, options, versionKey) {
*
* - `findOneAndUpdate()`
*
- * ####Options:
- *
- * - `new`: bool - true to return the modified document rather than the original. defaults to false
- * - `upsert`: bool - creates the object if it doesn't exist. defaults to false.
- * - `runValidators`: if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema.
- * - `setDefaultsOnInsert`: `true` by default. If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](http://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created.
- * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
- * - `select`: sets the document fields to return
- * - `rawResult`: if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify)
- * - `strict`: overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) for this update
- *
- * ####Examples:
+ * #### Example:
*
* A.findByIdAndUpdate(id, update, options, callback) // executes
* A.findByIdAndUpdate(id, update, options) // returns Query
@@ -2616,11 +2729,11 @@ function _decorateUpdateWithVersionKey(update, options, versionKey) {
* A.findByIdAndUpdate(id, update) // returns Query
* A.findByIdAndUpdate() // returns Query
*
- * ####Note:
+ * #### Note:
*
* All top level update keys which are not `atomic` operation names are treated as set operations:
*
- * ####Example:
+ * #### Example:
*
* Model.findByIdAndUpdate(id, { name: 'jason bourne' }, options, callback)
*
@@ -2628,8 +2741,9 @@ function _decorateUpdateWithVersionKey(update, options, versionKey) {
* Model.findByIdAndUpdate(id, { $set: { name: 'jason bourne' }}, options, callback)
*
* This helps prevent accidentally overwriting your document with `{ name: 'jason bourne' }`.
+ * To prevent this behaviour, see the `overwrite` option
*
- * ####Note:
+ * #### Note:
*
* `findOneAndX` and `findByIdAndX` functions support limited validation. You can
* enable validation by setting the `runValidators` option.
@@ -2637,25 +2751,30 @@ function _decorateUpdateWithVersionKey(update, options, versionKey) {
* If you need full-fledged validation, use the traditional approach of first
* retrieving the document.
*
- * Model.findById(id, function (err, doc) {
- * if (err) ..
- * doc.name = 'jason bourne';
- * doc.save(callback);
- * });
+ * const doc = await Model.findById(id)
+ * doc.name = 'jason bourne';
+ * await doc.save();
*
* @param {Object|Number|String} id value of `_id` to query by
* @param {Object} [update]
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
* @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
- * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html).
+ * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api/query.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html).
* @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html).
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
- * @param {Boolean} [options.overwrite=false] By default, if you don't include any [update operators](https://docs.mongodb.com/manual/reference/operator/update/) in `update`, Mongoose will wrap `update` in `$set` for you. This prevents you from accidentally overwriting the document. This option tells Mongoose to skip adding `$set`. An alternative to this would be using [Model.findOneAndReplace({ _id: id }, update, options, callback)](https://mongoosejs.com/docs/api/model.html#model_Model.findOneAndReplace).
+ * @param {Boolean} [options.overwrite=false] By default, if you don't include any [update operators](https://www.mongodb.com/docs/manual/reference/operator/update/) in `update`, Mongoose will wrap `update` in `$set` for you. This prevents you from accidentally overwriting the document. This option tells Mongoose to skip adding `$set`. An alternative to this would be using [Model.findOneAndReplace({ _id: id }, update, options, callback)](#model_Model-findOneAndReplace).
+ * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
+ * @param {Boolean} [options.runValidators] if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema
+ * @param {Boolean} [options.setDefaultsOnInsert=true] If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created
+ * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html)
+ * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
+ * @param {Boolean} [options.new=false] if true, return the modified document rather than the original
+ * @param {Object|String} [options.select] sets the document fields to return.
* @param {Function} [callback]
* @return {Query}
- * @see Model.findOneAndUpdate #model_Model.findOneAndUpdate
- * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
+ * @see Model.findOneAndUpdate #model_Model-findOneAndUpdate
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
* @api public
*/
@@ -2695,21 +2814,12 @@ Model.findByIdAndUpdate = function(id, update, options, callback) {
* - `findOneAndDelete()`
*
* This function differs slightly from `Model.findOneAndRemove()` in that
- * `findOneAndRemove()` becomes a [MongoDB `findAndModify()` command](https://docs.mongodb.com/manual/reference/method/db.collection.findAndModify/),
+ * `findOneAndRemove()` becomes a [MongoDB `findAndModify()` command](https://www.mongodb.com/docs/manual/reference/method/db.collection.findAndModify/),
* as opposed to a `findOneAndDelete()` command. For most mongoose use cases,
* this distinction is purely pedantic. You should use `findOneAndDelete()`
* unless you have a good reason not to.
*
- * ####Options:
- *
- * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
- * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
- * - `select`: sets the document fields to return, ex. `{ projection: { _id: 0 } }`
- * - `projection`: equivalent to `select`
- * - `rawResult`: if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify)
- * - `strict`: overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) for this update
- *
- * ####Examples:
+ * #### Example:
*
* A.findOneAndDelete(conditions, options, callback) // executes
* A.findOneAndDelete(conditions, options) // return Query
@@ -2723,17 +2833,19 @@ Model.findByIdAndUpdate = function(id, update, options, callback) {
* If you need full-fledged validation, use the traditional approach of first
* retrieving the document.
*
- * Model.findById(id, function (err, doc) {
- * if (err) ..
- * doc.name = 'jason bourne';
- * doc.save(callback);
- * });
+ * const doc = await Model.findById(id)
+ * doc.name = 'jason bourne';
+ * await doc.save();
*
* @param {Object} conditions
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
- * @param {Object|String|Array<String>} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
+ * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select)
* @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html).
+ * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html)
+ * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
+ * @param {Object|String} [options.select] sets the document fields to return.
+ * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0
* @param {Function} [callback]
* @return {Query}
* @api public
@@ -2778,12 +2890,12 @@ Model.findOneAndDelete = function(conditions, options, callback) {
* - `findOneAndDelete()`
*
* @param {Object|Number|String} id value of `_id` to query by
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
* @param {Function} [callback]
* @return {Query}
- * @see Model.findOneAndRemove #model_Model.findOneAndRemove
- * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
+ * @see Model.findOneAndRemove #model_Model-findOneAndRemove
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
*/
Model.findByIdAndDelete = function(id, options, callback) {
@@ -2813,32 +2925,27 @@ Model.findByIdAndDelete = function(id, options, callback) {
*
* - `findOneAndReplace()`
*
- * ####Options:
+ * #### Example:
*
- * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
- * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
- * - `select`: sets the document fields to return
- * - `projection`: like select, it determines which fields to return, ex. `{ projection: { _id: 0 } }`
- * - `rawResult`: if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify)
- * - `strict`: overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) for this update
- *
- * ####Examples:
- *
- * A.findOneAndReplace(conditions, options, callback) // executes
- * A.findOneAndReplace(conditions, options) // return Query
- * A.findOneAndReplace(conditions, callback) // executes
- * A.findOneAndReplace(conditions) // returns Query
- * A.findOneAndReplace() // returns Query
+ * A.findOneAndReplace(filter, replacement, options, callback) // executes
+ * A.findOneAndReplace(filter, replacement, options) // return Query
+ * A.findOneAndReplace(filter, replacement, callback) // executes
+ * A.findOneAndReplace(filter, replacement) // returns Query
+ * A.findOneAndReplace() // returns Query
*
* @param {Object} filter Replace the first document that matches this filter
* @param {Object} [replacement] Replace with this document
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
* @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
- * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html).
+ * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api/query.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html).
* @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html).
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
- * @param {Object|String|Array<String>} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select)
+ * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select)
+ * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
+ * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html)
+ * @param {Object|String} [options.select] sets the document fields to return.
+ * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0
* @param {Function} [callback]
* @return {Query}
* @api public
@@ -2849,8 +2956,10 @@ Model.findOneAndReplace = function(filter, replacement, options, callback) {
if (arguments.length === 1 && typeof filter === 'function') {
const msg = 'Model.findOneAndReplace(): First argument must not be a function.\n\n'
- + ' ' + this.modelName + '.findOneAndReplace(conditions, callback)\n'
- + ' ' + this.modelName + '.findOneAndReplace(conditions)\n'
+ + ' ' + this.modelName + '.findOneAndReplace(filter, replacement, options, callback)\n'
+ + ' ' + this.modelName + '.findOneAndReplace(filter, replacement, callback)\n'
+ + ' ' + this.modelName + '.findOneAndReplace(filter, replacement)\n'
+ + ' ' + this.modelName + '.findOneAndReplace(filter, callback)\n'
+ ' ' + this.modelName + '.findOneAndReplace()\n';
throw new TypeError(msg);
}
@@ -2890,16 +2999,7 @@ Model.findOneAndReplace = function(filter, replacement, options, callback) {
*
* - `findOneAndRemove()`
*
- * ####Options:
- *
- * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
- * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
- * - `select`: sets the document fields to return
- * - `projection`: like select, it determines which fields to return, ex. `{ projection: { _id: 0 } }`
- * - `rawResult`: if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify)
- * - `strict`: overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) for this update
- *
- * ####Examples:
+ * #### Example:
*
* A.findOneAndRemove(conditions, options, callback) // executes
* A.findOneAndRemove(conditions, options) // return Query
@@ -2913,20 +3013,22 @@ Model.findOneAndReplace = function(filter, replacement, options, callback) {
* If you need full-fledged validation, use the traditional approach of first
* retrieving the document.
*
- * Model.findById(id, function (err, doc) {
- * if (err) ..
- * doc.name = 'jason bourne';
- * doc.save(callback);
- * });
+ * const doc = await Model.findById(id);
+ * doc.name = 'jason bourne';
+ * await doc.save();
*
* @param {Object} conditions
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
* @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html).
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
- * @param {Object|String|Array<String>} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
+ * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select)
+ * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
+ * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html)
+ * @param {Object|String} [options.select] sets the document fields to return.
+ * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0
* @param {Function} [callback]
* @return {Query}
- * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
* @api public
*/
@@ -2970,14 +3072,7 @@ Model.findOneAndRemove = function(conditions, options, callback) {
*
* - `findOneAndRemove()`
*
- * ####Options:
- *
- * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
- * - `select`: sets the document fields to return
- * - `rawResult`: if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify)
- * - `strict`: overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) for this update
- *
- * ####Examples:
+ * #### Example:
*
* A.findByIdAndRemove(id, options, callback) // executes
* A.findByIdAndRemove(id, options) // return Query
@@ -2986,14 +3081,17 @@ Model.findOneAndRemove = function(conditions, options, callback) {
* A.findByIdAndRemove() // returns Query
*
* @param {Object|Number|String} id value of `_id` to query by
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
* @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html).
- * @param {Object|String|Array<String>} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select)
+ * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select)
+ * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
+ * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html)
+ * @param {Object|String} [options.select] sets the document fields to return.
* @param {Function} [callback]
* @return {Query}
- * @see Model.findOneAndRemove #model_Model.findOneAndRemove
- * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
+ * @see Model.findOneAndRemove #model_Model-findOneAndRemove
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
*/
Model.findByIdAndRemove = function(id, options, callback) {
@@ -3020,7 +3118,7 @@ Model.findByIdAndRemove = function(id, options, callback) {
*
* - `save()`
*
- * ####Example:
+ * #### Example:
*
* // Insert one new `Character` document
* await Character.create({ name: 'Jean-Luc Picard' });
@@ -3034,7 +3132,7 @@ Model.findByIdAndRemove = function(id, options, callback) {
* await Character.create([{ name: 'Jean-Luc Picard' }], { session });
*
* @param {Array|Object} docs Documents to insert, as a spread or array
- * @param {Object} [options] Options passed down to `save()`. To specify `options`, `docs` **must** be an array, not a spread.
+ * @param {Object} [options] Options passed down to `save()`. To specify `options`, `docs` **must** be an array, not a spread. See [Model.save](#model_Model-save) for available options.
* @param {Function} [callback] callback
* @return {Promise}
* @api public
@@ -3056,10 +3154,10 @@ Model.create = function create(doc, options, callback) {
options = {};
// Handle falsy callbacks re: #5061
if (typeof last === 'function' || (arguments.length > 1 && !last)) {
- cb = last;
- args = utils.args(arguments, 0, arguments.length - 1);
+ args = [...arguments];
+ cb = args.pop();
} else {
- args = utils.args(arguments);
+ args = [...arguments];
}
if (args.length === 2 &&
@@ -3072,7 +3170,7 @@ Model.create = function create(doc, options, callback) {
// to use a spread to specify options, see gh-7535
utils.warn('WARNING: to pass a `session` to `Model.create()` in ' +
'Mongoose, you **must** pass an array as the first argument. See: ' +
- 'https://mongoosejs.com/docs/api.html#model_Model.create');
+ 'https://mongoosejs.com/docs/api/model.html#model_Model-create');
}
}
@@ -3127,10 +3225,9 @@ Model.create = function create(doc, options, callback) {
}
const _done = (error, res) => {
const savedDocs = [];
- const len = res.length;
- for (let i = 0; i < len; ++i) {
- if (res[i].doc) {
- savedDocs.push(res[i].doc);
+ for (const val of res) {
+ if (val.doc) {
+ savedDocs.push(val.doc);
}
}
@@ -3138,7 +3235,7 @@ Model.create = function create(doc, options, callback) {
return cb(firstError, savedDocs);
}
- if (doc instanceof Array) {
+ if (Array.isArray(doc)) {
cb(null, savedDocs);
} else {
cb.apply(this, [null].concat(savedDocs));
@@ -3160,7 +3257,7 @@ Model.create = function create(doc, options, callback) {
/**
* _Requires a replica set running MongoDB >= 3.6.0._ Watches the
* underlying collection for changes using
- * [MongoDB change streams](https://docs.mongodb.com/manual/changeStreams/).
+ * [MongoDB change streams](https://www.mongodb.com/docs/manual/changeStreams/).
*
* This function does **not** trigger any middleware. In particular, it
* does **not** trigger aggregate middleware.
@@ -3172,7 +3269,7 @@ Model.create = function create(doc, options, callback) {
* - 'end': Emitted if the underlying stream is closed
* - 'close': Emitted if the underlying stream is closed
*
- * ####Example:
+ * #### Example:
*
* const doc = await Person.create({ name: 'Ned Stark' });
* const changeStream = Person.watch().on('change', change => console.log(change));
@@ -3184,7 +3281,8 @@ Model.create = function create(doc, options, callback) {
* await doc.remove();
*
* @param {Array} [pipeline]
- * @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#watch)
+ * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#watch)
+ * @param {Boolean} [options.hydrate=false] if true and `fullDocument: 'updateLookup'` is set, Mongoose will automatically hydrate `fullDocument` into a fully fledged Mongoose document
* @return {ChangeStream} mongoose-specific change stream wrapper, inherits from EventEmitter
* @api public
*/
@@ -3193,6 +3291,8 @@ Model.watch = function(pipeline, options) {
_checkContext(this, 'watch');
const changeStreamThunk = cb => {
+ pipeline = pipeline || [];
+ prepareDiscriminatorPipeline(pipeline, this.schema, 'fullDocument');
if (this.$__collection.buffer) {
this.$__collection.addQueue(() => {
if (this.closed) {
@@ -3207,19 +3307,22 @@ Model.watch = function(pipeline, options) {
}
};
+ options = options || {};
+ options.model = this;
+
return new ChangeStream(changeStreamThunk, pipeline, options);
};
/**
- * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions)
- * for benefits like causal consistency, [retryable writes](https://docs.mongodb.com/manual/core/retryable-writes/),
- * and [transactions](http://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html).
+ * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://www.mongodb.com/docs/manual/release-notes/3.6/#client-sessions)
+ * for benefits like causal consistency, [retryable writes](https://www.mongodb.com/docs/manual/core/retryable-writes/),
+ * and [transactions](https://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html).
*
* Calling `MyModel.startSession()` is equivalent to calling `MyModel.db.startSession()`.
*
* This function does not trigger any middleware.
*
- * ####Example:
+ * #### Example:
*
* const session = await Person.startSession();
* let doc = await Person.findOne({ name: 'Ned Stark' }, null, { session });
@@ -3230,7 +3333,7 @@ Model.watch = function(pipeline, options) {
* // secondary that is experiencing replication lag.
* doc = await Person.findOne({ name: 'Ned Stark' }, null, { session, readPreference: 'secondary' });
*
- * @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html#startSession)
+ * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/MongoClient.html#startSession)
* @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency
* @param {Function} [callback]
* @return {Promise<ClientSession>} promise that resolves to a MongoDB driver `ClientSession`
@@ -3252,7 +3355,7 @@ Model.startSession = function() {
* Mongoose always validates each document **before** sending `insertMany`
* to MongoDB. So if one document has a validation error, no documents will
* be saved, unless you set
- * [the `ordered` option to false](https://docs.mongodb.com/manual/reference/method/db.collection.insertMany/#error-handling).
+ * [the `ordered` option to false](https://www.mongodb.com/docs/manual/reference/method/db.collection.insertMany/#error-handling).
*
* This function does **not** trigger save middleware.
*
@@ -3260,18 +3363,18 @@ Model.startSession = function() {
*
* - `insertMany()`
*
- * ####Example:
+ * #### Example:
*
* const arr = [{ name: 'Star Wars' }, { name: 'The Empire Strikes Back' }];
* Movies.insertMany(arr, function(error, docs) {});
*
* @param {Array|Object|*} doc(s)
- * @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#insertMany)
- * @param {Boolean} [options.ordered = true] if true, will fail fast on the first error encountered. If false, will insert all the documents it can and report errors later. An `insertMany()` with `ordered = false` is called an "unordered" `insertMany()`.
- * @param {Boolean} [options.rawResult = false] if false, the returned promise resolves to the documents that passed mongoose document validation. If `true`, will return the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~insertWriteOpCallback) with a `mongoose` property that contains `validationErrors` if this is an unordered `insertMany`.
- * @param {Boolean} [options.lean = false] if `true`, skips hydrating and validating the documents. This option is useful if you need the extra performance, but Mongoose won't validate the documents before inserting.
- * @param {Number} [options.limit = null] this limits the number of documents being processed (validation/casting) by mongoose in parallel, this does **NOT** send the documents in batches to MongoDB. Use this option if you're processing a large number of documents and your app is running out of memory.
- * @param {String|Object|Array} [options.populate = null] populates the result documents. This option is a no-op if `rawResult` is set.
+ * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#insertMany)
+ * @param {Boolean} [options.ordered=true] if true, will fail fast on the first error encountered. If false, will insert all the documents it can and report errors later. An `insertMany()` with `ordered = false` is called an "unordered" `insertMany()`.
+ * @param {Boolean} [options.rawResult=false] if false, the returned promise resolves to the documents that passed mongoose document validation. If `true`, will return the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/InsertManyResult.html) with a `mongoose` property that contains `validationErrors` and `results` if this is an unordered `insertMany`.
+ * @param {Boolean} [options.lean=false] if `true`, skips hydrating the documents. This means Mongoose will **not** cast or validate any of the documents passed to `insertMany()`. This option is useful if you need the extra performance, but comes with data integrity risk. Consider using with [`castObject()`](#model_Model-castObject).
+ * @param {Number} [options.limit=null] this limits the number of documents being processed (validation/casting) by mongoose in parallel, this does **NOT** send the documents in batches to MongoDB. Use this option if you're processing a large number of documents and your app is running out of memory.
+ * @param {String|Object|Array} [options.populate=null] populates the result documents. This option is a no-op if `rawResult` is set.
* @param {Function} [callback] callback
* @return {Promise} resolving to the raw result from the MongoDB driver if `options.rawResult` was `true`, or the documents that passed validation, otherwise
* @api public
@@ -3289,8 +3392,16 @@ Model.insertMany = function(arr, options, callback) {
}, this.events);
};
-/*!
+/**
* ignore
+ *
+ * @param {Array} arr
+ * @param {Object} options
+ * @param {Function} callback
+ * @api private
+ * @memberOf Model
+ * @method $__insertMany
+ * @static
*/
Model.$__insertMany = function(arr, options, callback) {
@@ -3305,18 +3416,27 @@ Model.$__insertMany = function(arr, options, callback) {
}
callback = callback || utils.noop;
options = options || {};
- const limit = get(options, 'limit', 1000);
- const rawResult = get(options, 'rawResult', false);
- const ordered = get(options, 'ordered', true);
- const lean = get(options, 'lean', false);
+ const limit = options.limit || 1000;
+ const rawResult = !!options.rawResult;
+ const ordered = typeof options.ordered === 'boolean' ? options.ordered : true;
+ const lean = !!options.lean;
if (!Array.isArray(arr)) {
arr = [arr];
}
const validationErrors = [];
- const toExecute = arr.map(doc =>
+ const validationErrorsToOriginalOrder = new Map();
+ const results = ordered ? null : new Array(arr.length);
+ const toExecute = arr.map((doc, index) =>
callback => {
+ // If option `lean` is set to true bypass validation and hydration
+ if (lean) {
+ // we have to execute callback at the nextTick to be compatible
+ // with parallelLimit, as `results` variable has TDZ issue if we
+ // execute the callback synchronously
+ return immediate(() => callback(null, doc));
+ }
if (!(doc instanceof _this)) {
try {
doc = new _this(doc);
@@ -3327,13 +3447,6 @@ Model.$__insertMany = function(arr, options, callback) {
if (options.session != null) {
doc.$session(options.session);
}
- // If option `lean` is set to true bypass validation
- if (lean) {
- // we have to execute callback at the nextTick to be compatible
- // with parallelLimit, as `results` variable has TDZ issue if we
- // execute the callback synchronously
- return immediate(() => callback(null, doc));
- }
doc.$validate({ __noPromise: true }, function(error) {
if (error) {
// Option `ordered` signals that insert should be continued after reaching
@@ -3341,6 +3454,8 @@ Model.$__insertMany = function(arr, options, callback) {
// failed. It's up to the next function to filter out all failed models
if (ordered === false) {
validationErrors.push(error);
+ validationErrorsToOriginalOrder.set(error, index);
+ results[index] = error;
return callback(null, null);
}
return callback(error);
@@ -3354,14 +3469,38 @@ Model.$__insertMany = function(arr, options, callback) {
callback(error, null);
return;
}
+
+ const originalDocIndex = new Map();
+ const validDocIndexToOriginalIndex = new Map();
+ for (let i = 0; i < docs.length; ++i) {
+ originalDocIndex.set(docs[i], i);
+ }
+
// We filter all failed pre-validations by removing nulls
const docAttributes = docs.filter(function(doc) {
return doc != null;
});
+ for (let i = 0; i < docAttributes.length; ++i) {
+ validDocIndexToOriginalIndex.set(i, originalDocIndex.get(docAttributes[i]));
+ }
+
+ // Make sure validation errors are in the same order as the
+ // original documents, so if both doc1 and doc2 both fail validation,
+ // `Model.insertMany([doc1, doc2])` will always have doc1's validation
+ // error before doc2's. Re: gh-12791.
+ if (validationErrors.length > 0) {
+ validationErrors.sort((err1, err2) => {
+ return validationErrorsToOriginalOrder.get(err1) - validationErrorsToOriginalOrder.get(err2);
+ });
+ }
+
// Quickly escape while there aren't any valid docAttributes
- if (docAttributes.length < 1) {
+ if (docAttributes.length === 0) {
if (rawResult) {
const res = {
+ acknowledged: true,
+ insertedCount: 0,
+ insertedIds: {},
mongoose: {
validationErrors: validationErrors
}
@@ -3371,11 +3510,12 @@ Model.$__insertMany = function(arr, options, callback) {
callback(null, []);
return;
}
- const docObjects = docAttributes.map(function(doc) {
+ const docObjects = lean ? docAttributes : docAttributes.map(function(doc) {
if (doc.$__schema.options.versionKey) {
doc[doc.$__schema.options.versionKey] = 0;
}
- if (doc.initializeTimestamps) {
+ const shouldSetTimestamps = (!options || options.timestamps !== false) && doc.initializeTimestamps && (!doc.$__ || doc.$__.timestamps !== false);
+ if (shouldSetTimestamps) {
return doc.initializeTimestamps().toObject(internalToObjectOptions);
}
return doc.toObject(internalToObjectOptions);
@@ -3386,12 +3526,33 @@ Model.$__insertMany = function(arr, options, callback) {
// `writeErrors` is a property reported by the MongoDB driver,
// just not if there's only 1 error.
if (error.writeErrors == null &&
- get(error, 'result.result.writeErrors') != null) {
+ (error.result && error.result.result && error.result.result.writeErrors) != null) {
error.writeErrors = error.result.result.writeErrors;
}
// `insertedDocs` is a Mongoose-specific property
- const erroredIndexes = new Set(get(error, 'writeErrors', []).map(err => err.index));
+ const erroredIndexes = new Set((error && error.writeErrors || []).map(err => err.index));
+
+ for (let i = 0; i < error.writeErrors.length; ++i) {
+ const originalIndex = validDocIndexToOriginalIndex.get(error.writeErrors[i].index);
+ error.writeErrors[i] = {
+ ...error.writeErrors[i],
+ index: originalIndex
+ };
+ if (!ordered) {
+ results[originalIndex] = error.writeErrors[i];
+ }
+ }
+
+ if (!ordered) {
+ for (let i = 0; i < results.length; ++i) {
+ if (results[i] === void 0) {
+ results[i] = docs[i];
+ }
+ }
+
+ error.results = results;
+ }
let firstErroredIndex = -1;
error.insertedDocs = docAttributes.
@@ -3411,26 +3572,45 @@ Model.$__insertMany = function(arr, options, callback) {
return !isErrored;
}).
map(function setIsNewForInsertedDoc(doc) {
+ if (lean) {
+ return doc;
+ }
doc.$__reset();
_setIsNew(doc, false);
return doc;
});
+ if (rawResult && ordered === false) {
+ error.mongoose = {
+ validationErrors: validationErrors,
+ results: results
+ };
+ }
+
callback(error, null);
return;
}
- for (const attribute of docAttributes) {
- attribute.$__reset();
- _setIsNew(attribute, false);
+ if (!lean) {
+ for (const attribute of docAttributes) {
+ attribute.$__reset();
+ _setIsNew(attribute, false);
+ }
}
if (rawResult) {
if (ordered === false) {
+ for (let i = 0; i < results.length; ++i) {
+ if (results[i] === void 0) {
+ results[i] = docs[i];
+ }
+ }
+
// Decorate with mongoose validation errors in case of unordered,
// because then still do `insertMany()`
res.mongoose = {
- validationErrors: validationErrors
+ validationErrors: validationErrors,
+ results: results
};
}
return callback(null, res);
@@ -3464,6 +3644,7 @@ function _setIsNew(doc, val) {
const subdocs = doc.$getAllSubdocs();
for (const subdoc of subdocs) {
subdoc.$isNew = val;
+ subdoc.$emit('isNew', val);
}
}
@@ -3478,9 +3659,9 @@ function _setIsNew(doc, val) {
*
* This function does **not** trigger any middleware, neither `save()`, nor `update()`.
* If you need to trigger
- * `save()` middleware for every document use [`create()`](http://mongoosejs.com/docs/api.html#model_Model.create) instead.
+ * `save()` middleware for every document use [`create()`](#model_Model-create) instead.
*
- * ####Example:
+ * #### Example:
*
* Character.bulkWrite([
* {
@@ -3502,9 +3683,7 @@ function _setIsNew(doc, val) {
* },
* {
* deleteOne: {
- * {
- * filter: { name: 'Eddard Stark' }
- * }
+ * filter: { name: 'Eddard Stark' }
* }
* }
* ]).then(res => {
@@ -3512,7 +3691,7 @@ function _setIsNew(doc, val) {
* console.log(res.insertedCount, res.modifiedCount, res.deletedCount);
* });
*
- * The [supported operations](https://docs.mongodb.com/manual/reference/method/db.collection.bulkWrite/#db.collection.bulkWrite) are:
+ * The [supported operations](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#db.collection.bulkWrite) are:
*
* - `insertOne`
* - `updateOne`
@@ -3523,33 +3702,34 @@ function _setIsNew(doc, val) {
*
* @param {Array} ops
* @param {Object} [ops.insertOne.document] The document to insert
- * @param {Object} [opts.updateOne.filter] Update the first document that matches this filter
- * @param {Object} [opts.updateOne.update] An object containing [update operators](https://docs.mongodb.com/manual/reference/operator/update/)
- * @param {Boolean} [opts.updateOne.upsert=false] If true, insert a doc if none match
- * @param {Boolean} [opts.updateOne.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation
- * @param {Object} [opts.updateOne.collation] The [MongoDB collation](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations) to use
- * @param {Array} [opts.updateOne.arrayFilters] The [array filters](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html) used in `update`
- * @param {Object} [opts.updateMany.filter] Update all the documents that match this filter
- * @param {Object} [opts.updateMany.update] An object containing [update operators](https://docs.mongodb.com/manual/reference/operator/update/)
- * @param {Boolean} [opts.updateMany.upsert=false] If true, insert a doc if no documents match `filter`
- * @param {Boolean} [opts.updateMany.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation
- * @param {Object} [opts.updateMany.collation] The [MongoDB collation](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations) to use
- * @param {Array} [opts.updateMany.arrayFilters] The [array filters](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html) used in `update`
- * @param {Object} [opts.deleteOne.filter] Delete the first document that matches this filter
- * @param {Object} [opts.deleteMany.filter] Delete all documents that match this filter
- * @param {Object} [opts.replaceOne.filter] Replace the first document that matches this filter
- * @param {Object} [opts.replaceOne.replacement] The replacement document
- * @param {Boolean} [opts.replaceOne.upsert=false] If true, insert a doc if no documents match `filter`
+ * @param {Object} [ops.updateOne.filter] Update the first document that matches this filter
+ * @param {Object} [ops.updateOne.update] An object containing [update operators](https://www.mongodb.com/docs/manual/reference/operator/update/)
+ * @param {Boolean} [ops.updateOne.upsert=false] If true, insert a doc if none match
+ * @param {Boolean} [ops.updateOne.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation
+ * @param {Object} [ops.updateOne.collation] The [MongoDB collation](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations) to use
+ * @param {Array} [ops.updateOne.arrayFilters] The [array filters](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html) used in `update`
+ * @param {Object} [ops.updateMany.filter] Update all the documents that match this filter
+ * @param {Object} [ops.updateMany.update] An object containing [update operators](https://www.mongodb.com/docs/manual/reference/operator/update/)
+ * @param {Boolean} [ops.updateMany.upsert=false] If true, insert a doc if no documents match `filter`
+ * @param {Boolean} [ops.updateMany.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation
+ * @param {Object} [ops.updateMany.collation] The [MongoDB collation](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations) to use
+ * @param {Array} [ops.updateMany.arrayFilters] The [array filters](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html) used in `update`
+ * @param {Object} [ops.deleteOne.filter] Delete the first document that matches this filter
+ * @param {Object} [ops.deleteMany.filter] Delete all documents that match this filter
+ * @param {Object} [ops.replaceOne.filter] Replace the first document that matches this filter
+ * @param {Object} [ops.replaceOne.replacement] The replacement document
+ * @param {Boolean} [ops.replaceOne.upsert=false] If true, insert a doc if no documents match `filter`
* @param {Object} [options]
* @param {Boolean} [options.ordered=true] If true, execute writes in order and stop at the first error. If false, execute writes in parallel and continue until all writes have either succeeded or errored.
* @param {ClientSession} [options.session=null] The session associated with this bulk write. See [transactions docs](/docs/transactions.html).
- * @param {String|number} [options.w=1] The [write concern](https://docs.mongodb.com/manual/reference/write-concern/). See [`Query#w()`](/docs/api.html#query_Query-w) for more information.
- * @param {number} [options.wtimeout=null] The [write concern timeout](https://docs.mongodb.com/manual/reference/write-concern/#wtimeout).
- * @param {Boolean} [options.j=true] If false, disable [journal acknowledgement](https://docs.mongodb.com/manual/reference/write-concern/#j-option)
- * @param {Boolean} [options.bypassDocumentValidation=false] If true, disable [MongoDB server-side schema validation](https://docs.mongodb.com/manual/core/schema-validation/) for all writes in this bulk.
+ * @param {String|number} [options.w=1] The [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/). See [`Query#w()`](/docs/api/query.html#query_Query-w) for more information.
+ * @param {number} [options.wtimeout=null] The [write concern timeout](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout).
+ * @param {Boolean} [options.j=true] If false, disable [journal acknowledgement](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option)
+ * @param {Boolean} [options.skipValidation=false] Set to true to skip Mongoose schema validation on bulk write operations. Mongoose currently runs validation on `insertOne` and `replaceOne` operations by default.
+ * @param {Boolean} [options.bypassDocumentValidation=false] If true, disable [MongoDB server-side schema validation](https://www.mongodb.com/docs/manual/core/schema-validation/) for all writes in this bulk.
* @param {Boolean} [options.strict=null] Overwrites the [`strict` option](/docs/guide.html#strict) on schema. If false, allows filtering and writing fields not defined in the schema for all writes in this bulk.
* @param {Function} [callback] callback `function(error, bulkWriteOpResult) {}`
- * @return {Promise} resolves to a [`BulkWriteOpResult`](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#~BulkWriteOpResult) if the operation succeeds
+ * @return {Promise} resolves to a [`BulkWriteOpResult`](https://mongodb.github.io/node-mongodb-native/4.9/classes/BulkWriteResult.html) if the operation succeeds
* @api public
*/
@@ -3561,29 +3741,88 @@ Model.bulkWrite = function(ops, options, callback) {
options = null;
}
options = options || {};
+ const ordered = options.ordered == null ? true : options.ordered;
const validations = ops.map(op => castBulkWrite(this, op, options));
callback = this.$handleCallbackError(callback);
return this.db.base._promiseOrCallback(callback, cb => {
cb = this.$wrapCallback(cb);
- each(validations, (fn, cb) => fn(cb), error => {
- if (error) {
- return cb(error);
+ if (ordered) {
+ each(validations, (fn, cb) => fn(cb), error => {
+ if (error) {
+ return cb(error);
+ }
+
+ if (ops.length === 0) {
+ return cb(null, getDefaultBulkwriteResult());
+ }
+
+ try {
+ this.$__collection.bulkWrite(ops, options, (error, res) => {
+ if (error) {
+ return cb(error);
+ }
+
+ cb(null, res);
+ });
+ } catch (err) {
+ return cb(err);
+ }
+ });
+
+ return;
+ }
+
+ let remaining = validations.length;
+ let validOps = [];
+ let validationErrors = [];
+ if (remaining === 0) {
+ completeUnorderedValidation.call(this);
+ } else {
+ for (let i = 0; i < validations.length; ++i) {
+ validations[i]((err) => {
+ if (err == null) {
+ validOps.push(i);
+ } else {
+ validationErrors.push({ index: i, error: err });
+ }
+ if (--remaining <= 0) {
+ completeUnorderedValidation.call(this);
+ }
+ });
}
+ }
+
+ validationErrors = validationErrors.
+ sort((v1, v2) => v1.index - v2.index).
+ map(v => v.error);
- if (ops.length === 0) {
+ function completeUnorderedValidation() {
+ validOps = validOps.sort().map(index => ops[index]);
+
+ if (validOps.length === 0) {
return cb(null, getDefaultBulkwriteResult());
}
- this.$__collection.bulkWrite(ops, options, (error, res) => {
+ this.$__collection.bulkWrite(validOps, options, (error, res) => {
if (error) {
+ if (validationErrors.length > 0) {
+ error.mongoose = error.mongoose || {};
+ error.mongoose.validationErrors = validationErrors;
+ }
+
return cb(error);
}
+ if (validationErrors.length > 0) {
+ res.mongoose = res.mongoose || {};
+ res.mongoose.validationErrors = validationErrors;
+ }
+
cb(null, res);
});
- });
+ }
}, this.events);
};
@@ -3593,38 +3832,59 @@ Model.bulkWrite = function(ops, options, callback) {
*
* `bulkSave` uses `bulkWrite` under the hood, so it's mostly useful when dealing with many documents (10K+)
*
- * @param {[Document]} documents
+ * @param {Array<Document>} documents
+ * @param {Object} [options] options passed to the underlying `bulkWrite()`
+ * @param {Boolean} [options.timestamps] defaults to `null`, when set to false, mongoose will not add/update timestamps to the documents.
+ * @param {ClientSession} [options.session=null] The session associated with this bulk write. See [transactions docs](/docs/transactions.html).
+ * @param {String|number} [options.w=1] The [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/). See [`Query#w()`](/docs/api/query.html#query_Query-w) for more information.
+ * @param {number} [options.wtimeout=null] The [write concern timeout](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout).
+ * @param {Boolean} [options.j=true] If false, disable [journal acknowledgement](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option)
*
*/
-Model.bulkSave = function(documents) {
- const preSavePromises = documents.map(buildPreSavePromise);
-
- const writeOperations = this.buildBulkWriteOperations(documents, { skipValidation: true });
-
- let bulkWriteResultPromise;
- return Promise.all(preSavePromises)
- .then(() => bulkWriteResultPromise = this.bulkWrite(writeOperations))
- .then(() => documents.map(buildSuccessfulWriteHandlerPromise))
- .then(() => bulkWriteResultPromise)
- .catch((err) => {
- if (!get(err, 'writeErrors.length')) {
- throw err;
+Model.bulkSave = async function(documents, options) {
+ options = options || {};
+
+ const writeOperations = this.buildBulkWriteOperations(documents, { skipValidation: true, timestamps: options.timestamps });
+
+ if (options.timestamps != null) {
+ for (const document of documents) {
+ document.$__.saveOptions = document.$__.saveOptions || {};
+ document.$__.saveOptions.timestamps = options.timestamps;
+ }
+ } else {
+ for (const document of documents) {
+ if (document.$__.timestamps != null) {
+ document.$__.saveOptions = document.$__.saveOptions || {};
+ document.$__.saveOptions.timestamps = document.$__.timestamps;
}
- return Promise.all(
- documents.map((document) => {
- const documentError = err.writeErrors.find(writeError => {
- const writeErrorDocumentId = writeError.err.op._id || writeError.err.op.q._id;
- return writeErrorDocumentId.toString() === document._id.toString();
- });
+ }
+ }
- if (documentError == null) {
- return buildSuccessfulWriteHandlerPromise(document);
- }
- })
- ).then(() => {
- throw err;
+ await Promise.all(documents.map(buildPreSavePromise));
+
+ const { bulkWriteResult, bulkWriteError } = await this.bulkWrite(writeOperations, options).then(
+ (res) => ({ bulkWriteResult: res, bulkWriteError: null }),
+ (err) => ({ bulkWriteResult: null, bulkWriteError: err })
+ );
+
+ await Promise.all(
+ documents.map(async(document) => {
+ const documentError = bulkWriteError && bulkWriteError.writeErrors.find(writeError => {
+ const writeErrorDocumentId = writeError.err.op._id || writeError.err.op.q._id;
+ return writeErrorDocumentId.toString() === document._id.toString();
});
- });
+
+ if (documentError == null) {
+ await handleSuccessfulWrite(document);
+ }
+ })
+ );
+
+ if (bulkWriteError && bulkWriteError.writeErrors && bulkWriteError.writeErrors.length) {
+ throw bulkWriteError;
+ }
+
+ return bulkWriteResult;
};
function buildPreSavePromise(document) {
@@ -3639,34 +3899,157 @@ function buildPreSavePromise(document) {
});
}
-function buildSuccessfulWriteHandlerPromise(document) {
+function handleSuccessfulWrite(document) {
return new Promise((resolve, reject) => {
- handleSuccessfulWrite(document, resolve, reject);
+ if (document.$isNew) {
+ _setIsNew(document, false);
+ }
+
+ document.$__reset();
+ document.schema.s.hooks.execPost('save', document, [document], {}, (err) => {
+ if (err) {
+ reject(err);
+ return;
+ }
+ resolve();
+ });
+
});
}
-function handleSuccessfulWrite(document, resolve, reject) {
- if (document.$isNew) {
- _setIsNew(document, false);
+/**
+ * Apply defaults to the given document or POJO.
+ *
+ * @param {Object|Document} obj object or document to apply defaults on
+ * @returns {Object|Document}
+ * @api public
+ */
+
+Model.applyDefaults = function applyDefaults(doc) {
+ if (doc.$__ != null) {
+ applyDefaultsHelper(doc, doc.$__.fields, doc.$__.exclude);
+
+ for (const subdoc of doc.$getAllSubdocs()) {
+ applyDefaults(subdoc, subdoc.$__.fields, subdoc.$__.exclude);
+ }
+
+ return doc;
}
- document.$__reset();
- document.schema.s.hooks.execPost('save', document, {}, (err) => {
- if (err) {
- reject(err);
- return;
+ applyDefaultsToPOJO(doc, this.schema);
+
+ return doc;
+};
+
+/**
+ * Cast the given POJO to the model's schema
+ *
+ * #### Example:
+ *
+ * const Test = mongoose.model('Test', Schema({ num: Number }));
+ *
+ * const obj = Test.castObject({ num: '42' });
+ * obj.num; // 42 as a number
+ *
+ * Test.castObject({ num: 'not a number' }); // Throws a ValidationError
+ *
+ * @param {Object} obj object or document to cast
+ * @param {Object} options options passed to castObject
+ * @param {Boolean} options.ignoreCastErrors If set to `true` will not throw a ValidationError and only return values that were successfully cast.
+ * @returns {Object} POJO casted to the model's schema
+ * @throws {ValidationError} if casting failed for at least one path
+ * @api public
+ */
+
+Model.castObject = function castObject(obj, options) {
+ options = options || {};
+ const ret = {};
+
+ const schema = this.schema;
+ const paths = Object.keys(schema.paths);
+
+ for (const path of paths) {
+ const schemaType = schema.path(path);
+ if (!schemaType || !schemaType.$isMongooseArray) {
+ continue;
}
- resolve();
- });
-}
+
+ const val = get(obj, path);
+ pushNestedArrayPaths(paths, val, path);
+ }
+
+ let error = null;
+
+ for (const path of paths) {
+ const schemaType = schema.path(path);
+ if (schemaType == null) {
+ continue;
+ }
+
+ let val = get(obj, path, void 0);
+
+ if (val == null) {
+ continue;
+ }
+
+ const pieces = path.indexOf('.') === -1 ? [path] : path.split('.');
+ let cur = ret;
+ for (let i = 0; i < pieces.length - 1; ++i) {
+ if (cur[pieces[i]] == null) {
+ cur[pieces[i]] = isNaN(pieces[i + 1]) ? {} : [];
+ }
+ cur = cur[pieces[i]];
+ }
+
+ if (schemaType.$isMongooseDocumentArray) {
+ continue;
+ }
+ if (schemaType.$isSingleNested || schemaType.$isMongooseDocumentArrayElement) {
+ try {
+ val = Model.castObject.call(schemaType.caster, val);
+ } catch (err) {
+ if (!options.ignoreCastErrors) {
+ error = error || new ValidationError();
+ error.addError(path, err);
+ }
+ continue;
+ }
+
+ cur[pieces[pieces.length - 1]] = val;
+ continue;
+ }
+
+ try {
+ val = schemaType.cast(val);
+ cur[pieces[pieces.length - 1]] = val;
+ } catch (err) {
+ if (!options.ignoreCastErrors) {
+ error = error || new ValidationError();
+ error.addError(path, err);
+ }
+
+ continue;
+ }
+ }
+
+ if (error != null) {
+ throw error;
+ }
+
+ return ret;
+};
/**
+ * Build bulk write operations for `bulkSave()`.
*
- * @param {[Document]} documents The array of documents to build write operations of
+ * @param {Array<Document>} documents The array of documents to build write operations of
* @param {Object} options
* @param {Boolean} options.skipValidation defaults to `false`, when set to true, building the write operations will bypass validating the documents.
- * @returns
+ * @param {Boolean} options.timestamps defaults to `null`, when set to false, mongoose will not add/update timestamps to the documents.
+ * @return {Array<Promise>} Returns a array of all Promises the function executes to be awaited.
+ * @api private
*/
+
Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, options) {
if (!Array.isArray(documents)) {
throw new Error(`bulkSave expects an array of documents to be passed, received \`${documents}\` instead`);
@@ -3687,9 +4070,9 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
const isANewDocument = document.isNew;
if (isANewDocument) {
- accumulator.push({
- insertOne: { document }
- });
+ const writeOperation = { insertOne: { document } };
+ utils.injectTimestampsOption(writeOperation.insertOne, options.timestamps);
+ accumulator.push(writeOperation);
return accumulator;
}
@@ -3704,13 +4087,9 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
_applyCustomWhere(document, where);
document.$__version(where, delta);
-
- accumulator.push({
- updateOne: {
- filter: where,
- update: changes
- }
- });
+ const writeOperation = { updateOne: { filter: where, update: changes } };
+ utils.injectTimestampsOption(writeOperation.updateOne, options.timestamps);
+ accumulator.push(writeOperation);
return accumulator;
}
@@ -3729,26 +4108,36 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
}
};
+
/**
* Shortcut for creating a new Document from existing raw data, pre-saved in the DB.
* The document returned has no paths marked as modified initially.
*
- * ####Example:
+ * #### Example:
*
* // hydrate previous data into a Mongoose document
* const mongooseCandy = Candy.hydrate({ _id: '54108337212ffb6d459f854c', type: 'jelly bean' });
*
* @param {Object} obj
- * @param {Object|String|Array<String>} [projection] optional projection containing which fields should be selected for this document
+ * @param {Object|String|String[]} [projection] optional projection containing which fields should be selected for this document
+ * @param {Object} [options] optional options
+ * @param {Boolean} [options.setters=false] if true, apply schema setters when hydrating
* @return {Document} document instance
* @api public
*/
-Model.hydrate = function(obj, projection) {
+Model.hydrate = function(obj, projection, options) {
_checkContext(this, 'hydrate');
+ if (projection != null) {
+ if (obj != null && obj.$__ != null) {
+ obj = obj.toObject(internalToObjectOptions);
+ }
+ obj = applyProjection(obj, projection);
+ }
+
const document = require('./queryhelpers').createModel(this, obj, projection);
- document.$init(obj);
+ document.$init(obj, options);
return document;
};
@@ -3761,7 +4150,7 @@ Model.hydrate = function(obj, projection) {
*
* This method is deprecated. See [Deprecation Warnings](../deprecations.html#update) for details.
*
- * ####Examples:
+ * #### Example:
*
* MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn);
*
@@ -3771,14 +4160,14 @@ Model.hydrate = function(obj, projection) {
* // had `ferret` set to `true`, `nModified` will be 0.
* res.nModified;
*
- * ####Valid options:
+ * #### Valid options:
*
* - `strict` (boolean): overrides the [schema-level `strict` option](/docs/guide.html#strict) for this update
* - `upsert` (boolean): whether to create the doc if it doesn't match (false)
- * - `writeConcern` (object): sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern)
+ * - `writeConcern` (object): sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern)
* - `multi` (boolean): whether multiple documents should be updated (false)
* - `runValidators`: if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema.
- * - `setDefaultsOnInsert` (boolean): if this and `upsert` are true, mongoose will apply the [defaults](http://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on [MongoDB's `$setOnInsert` operator](https://docs.mongodb.org/v2.4/reference/operator/update/setOnInsert/).
+ * - `setDefaultsOnInsert` (boolean): if this and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on [MongoDB's `$setOnInsert` operator](https://www.mongodb.com/docs/manual/reference/operator/update/setOnInsert/).
* - `timestamps` (boolean): If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
* - `overwrite` (boolean): disables update-only mode, allowing you to overwrite the doc (false)
*
@@ -3789,11 +4178,11 @@ Model.hydrate = function(obj, projection) {
* - `err` is the error if any occurred
* - `rawResponse` is the full response from Mongo
*
- * ####Note:
+ * #### Note:
*
* All top level keys which are not `atomic` operation names are treated as set operations:
*
- * ####Example:
+ * #### Example:
*
* const query = { name: 'borne' };
* Model.update(query, { name: 'jason bourne' }, options, callback);
@@ -3804,29 +4193,28 @@ Model.hydrate = function(obj, projection) {
*
* This helps prevent accidentally overwriting all documents in your collection with `{ name: 'jason bourne' }`.
*
- * ####Note:
+ * #### Note:
*
* Be careful to not use an existing model instance for the update clause (this won't work and can cause weird behavior like infinite loops). Also, ensure that the update clause does not have an _id property, which causes Mongo to return a "Mod on _id not allowed" error.
*
* @deprecated
- * @see strict http://mongoosejs.com/docs/guide.html#strict
- * @see response http://docs.mongodb.org/v2.6/reference/command/update/#output
+ * @see strict https://mongoosejs.com/docs/guide.html#strict
+ * @see response https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#update
* @param {Object} filter
* @param {Object} doc
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](/docs/api.html#query_Query-setOptions)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](/docs/api/query.html#query_Query-setOptions)
* @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](/docs/guide.html#strict)
* @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
- * @param {Object} [options.writeConcern=null] sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern)
+ * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern)
* @param {Boolean} [options.multi=false] whether multiple documents should be updated or just the first one that matches `filter`.
* @param {Boolean} [options.runValidators=false] if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema.
- * @param {Boolean} [options.setDefaultsOnInsert=false] `true` by default. If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](http://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created.
+ * @param {Boolean} [options.setDefaultsOnInsert=false] `true` by default. If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created.
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
- * @param {Boolean} [options.overwrite=false] By default, if you don't include any [update operators](https://docs.mongodb.com/manual/reference/operator/update/) in `doc`, Mongoose will wrap `doc` in `$set` for you. This prevents you from accidentally overwriting the document. This option tells Mongoose to skip adding `$set`.
- * @param {Function} [callback] params are (error, [updateWriteOpResult](https://mongodb.github.io/node-mongodb-native/3.6/api/Collection.html#~updateWriteOpResult))
- * @param {Function} [callback]
+ * @param {Boolean} [options.overwrite=false] By default, if you don't include any [update operators](https://www.mongodb.com/docs/manual/reference/operator/update/) in `doc`, Mongoose will wrap `doc` in `$set` for you. This prevents you from accidentally overwriting the document. This option tells Mongoose to skip adding `$set`.
+ * @param {Function} [callback] params are (error, [updateWriteOpResult](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html))
* @return {Query}
- * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
- * @see writeOpResult https://mongodb.github.io/node-mongodb-native/3.6/api/Collection.html#~updateWriteOpResult
+ * @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output
+ * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html
* @see Query docs https://mongoosejs.com/docs/queries.html
* @api public
*/
@@ -3845,7 +4233,8 @@ Model.update = function update(conditions, doc, options, callback) {
* **Note** updateMany will _not_ fire update middleware. Use `pre('updateMany')`
* and `post('updateMany')` instead.
*
- * ####Example:
+ * #### Example:
+ *
* const res = await Person.updateMany({ name: /Stark$/ }, { isDeleted: true });
* res.matchedCount; // Number of documents matched
* res.modifiedCount; // Number of documents modified
@@ -3859,16 +4248,16 @@ Model.update = function update(conditions, doc, options, callback) {
*
* @param {Object} filter
* @param {Object|Array} update
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
* @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
- * @param {Object} [options.writeConcern=null] sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern)
+ * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern)
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
* @param {Function} [callback] `function(error, res) {}` where `res` has 5 properties: `modifiedCount`, `matchedCount`, `acknowledged`, `upsertedId`, and `upsertedCount`.
* @return {Query}
* @see Query docs https://mongoosejs.com/docs/queries.html
- * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
- * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
+ * @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output
+ * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html
* @api public
*/
@@ -3885,7 +4274,8 @@ Model.updateMany = function updateMany(conditions, doc, options, callback) {
* - MongoDB will update _only_ the first document that matches `filter` regardless of the value of the `multi` option.
* - Use `replaceOne()` if you want to overwrite an entire document rather than using atomic operators like `$set`.
*
- * ####Example:
+ * #### Example:
+ *
* const res = await Person.updateOne({ name: 'Jean-Luc Picard' }, { ship: 'USS Enterprise' });
* res.matchedCount; // Number of documents matched
* res.modifiedCount; // Number of documents modified
@@ -3899,16 +4289,16 @@ Model.updateMany = function updateMany(conditions, doc, options, callback) {
*
* @param {Object} filter
* @param {Object|Array} update
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
* @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
- * @param {Object} [options.writeConcern=null] sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern)
+ * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern)
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
* @param {Function} [callback] params are (error, writeOpResult)
* @return {Query}
* @see Query docs https://mongoosejs.com/docs/queries.html
- * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
- * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
+ * @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output
+ * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html
* @api public
*/
@@ -3922,7 +4312,8 @@ Model.updateOne = function updateOne(conditions, doc, options, callback) {
* Same as `update()`, except MongoDB replace the existing document with the
* given document (no atomic operators like `$set`).
*
- * ####Example:
+ * #### Example:
+ *
* const res = await Person.replaceOne({ _id: 24601 }, { name: 'Jean Valjean' });
* res.matchedCount; // Number of documents matched
* res.modifiedCount; // Number of documents modified
@@ -3936,15 +4327,15 @@ Model.updateOne = function updateOne(conditions, doc, options, callback) {
*
* @param {Object} filter
* @param {Object} doc
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
* @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
- * @param {Object} [options.writeConcern=null] sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern)
+ * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern)
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
* @param {Function} [callback] `function(error, res) {}` where `res` has 3 properties: `n`, `nModified`, `ok`.
* @return {Query}
* @see Query docs https://mongoosejs.com/docs/queries.html
- * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
+ * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html
* @return {Query}
* @api public
*/
@@ -3952,7 +4343,7 @@ Model.updateOne = function updateOne(conditions, doc, options, callback) {
Model.replaceOne = function replaceOne(conditions, doc, options, callback) {
_checkContext(this, 'replaceOne');
- const versionKey = get(this, 'schema.options.versionKey', null);
+ const versionKey = this && this.schema && this.schema.options && this.schema.options.versionKey || null;
if (versionKey && !doc[versionKey]) {
doc[versionKey] = 0;
}
@@ -3960,9 +4351,10 @@ Model.replaceOne = function replaceOne(conditions, doc, options, callback) {
return _update(this, 'replaceOne', conditions, doc, options, callback);
};
-/*!
+/**
* Common code for `updateOne()`, `updateMany()`, `replaceOne()`, and `update()`
* because they need to do the same thing
+ * @api private
*/
function _update(model, op, conditions, doc, options, callback) {
@@ -3977,7 +4369,10 @@ function _update(model, op, conditions, doc, options, callback) {
}
options = typeof options === 'function' ? options : utils.clone(options);
- const versionKey = get(model, 'schema.options.versionKey', null);
+ const versionKey = model &&
+ model.schema &&
+ model.schema.options &&
+ model.schema.options.versionKey || null;
_decorateUpdateWithVersionKey(doc, options, versionKey);
return mq[op](conditions, doc, options, callback);
@@ -3986,54 +4381,34 @@ function _update(model, op, conditions, doc, options, callback) {
/**
* Executes a mapReduce command.
*
- * `o` is an object specifying all mapReduce options as well as the map and reduce functions. All options are delegated to the driver implementation. See [node-mongodb-native mapReduce() documentation](http://mongodb.github.io/node-mongodb-native/api-generated/collection.html#mapreduce) for more detail about options.
+ * `opts` is an object specifying all mapReduce options as well as the map and reduce functions. All options are delegated to the driver implementation. See [node-mongodb-native mapReduce() documentation](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#mapReduce) for more detail about options.
*
* This function does not trigger any middleware.
*
- * ####Example:
+ * #### Example:
*
- * const o = {};
+ * const opts = {};
* // `map()` and `reduce()` are run on the MongoDB server, not Node.js,
* // these functions are converted to strings
- * o.map = function () { emit(this.name, 1) };
- * o.reduce = function (k, vals) { return vals.length };
- * User.mapReduce(o, function (err, results) {
+ * opts.map = function () { emit(this.name, 1) };
+ * opts.reduce = function (k, vals) { return vals.length };
+ * User.mapReduce(opts, function (err, results) {
* console.log(results)
* })
*
- * ####Other options:
- *
- * - `query` {Object} query filter object.
- * - `sort` {Object} sort input objects using this key
- * - `limit` {Number} max number of documents
- * - `keeptemp` {Boolean, default:false} keep temporary data
- * - `finalize` {Function} finalize function
- * - `scope` {Object} scope variables exposed to map/reduce/finalize during execution
- * - `jsMode` {Boolean, default:false} it is possible to make the execution stay in JS. Provided in MongoDB > 2.0.X
- * - `verbose` {Boolean, default:false} provide statistics on job execution time.
- * - `readPreference` {String}
- * - `out*` {Object, default: {inline:1}} sets the output target for the map reduce job.
+ * If `opts.out` is set to `replace`, `merge`, or `reduce`, a Model instance is returned that can be used for further querying. Queries run against this model are all executed with the [`lean` option](/docs/tutorials/lean.html); meaning only the js object is returned and no Mongoose magic is applied (getters, setters, etc).
*
- * ####* out options:
+ * #### Example:
*
- * - `{inline:1}` the results are returned in an array
- * - `{replace: 'collectionName'}` add the results to collectionName: the results replace the collection
- * - `{reduce: 'collectionName'}` add the results to collectionName: if dups are detected, uses the reducer / finalize functions
- * - `{merge: 'collectionName'}` add the results to collectionName: if dups exist the new docs overwrite the old
- *
- * If `options.out` is set to `replace`, `merge`, or `reduce`, a Model instance is returned that can be used for further querying. Queries run against this model are all executed with the [`lean` option](/docs/tutorials/lean.html); meaning only the js object is returned and no Mongoose magic is applied (getters, setters, etc).
- *
- * ####Example:
- *
- * const o = {};
+ * const opts = {};
* // You can also define `map()` and `reduce()` as strings if your
* // linter complains about `emit()` not being defined
- * o.map = 'function () { emit(this.name, 1) }';
- * o.reduce = 'function (k, vals) { return vals.length }';
- * o.out = { replace: 'createdCollectionNameForResults' }
- * o.verbose = true;
+ * opts.map = 'function () { emit(this.name, 1) }';
+ * opts.reduce = 'function (k, vals) { return vals.length }';
+ * opts.out = { replace: 'createdCollectionNameForResults' }
+ * opts.verbose = true;
*
- * User.mapReduce(o, function (err, model, stats) {
+ * User.mapReduce(opts, function (err, model, stats) {
* console.log('map reduce took %d ms', stats.processtime)
* model.find().where('value').gt(10).exec(function (err, docs) {
* console.log(docs);
@@ -4042,8 +4417,8 @@ function _update(model, op, conditions, doc, options, callback) {
*
* // `mapReduce()` returns a promise. However, ES6 promises can only
* // resolve to exactly one value,
- * o.resolveToObject = true;
- * const promise = User.mapReduce(o);
+ * opts.resolveToObject = true;
+ * const promise = User.mapReduce(opts);
* promise.then(function (res) {
* const model = res.model;
* const stats = res.stats;
@@ -4053,14 +4428,28 @@ function _update(model, op, conditions, doc, options, callback) {
* console.log(docs);
* }).then(null, handleError).end()
*
- * @param {Object} o an object specifying map-reduce options
+ * @param {Object} opts an object specifying map-reduce options
+ * @param {Boolean} [opts.verbose=false] provide statistics on job execution time
+ * @param {ReadPreference|String} [opts.readPreference] a read-preference string or a read-preference instance
+ * @param {Boolean} [opts.jsMode=false] it is possible to make the execution stay in JS. Provided in MongoDB > 2.0.X
+ * @param {Object} [opts.scope] scope variables exposed to map/reduce/finalize during execution
+ * @param {Function} [opts.finalize] finalize function
+ * @param {Boolean} [opts.keeptemp=false] keep temporary data
+ * @param {Number} [opts.limit] max number of documents
+ * @param {Object} [opts.sort] sort input objects using this key
+ * @param {Object} [opts.query] query filter object
+ * @param {Object} [opts.out] sets the output target for the map reduce job
+ * @param {Number} [opts.out.inline=1] the results are returned in an array
+ * @param {String} [opts.out.replace] add the results to collectionName: the results replace the collection
+ * @param {String} [opts.out.reduce] add the results to collectionName: if dups are detected, uses the reducer / finalize functions
+ * @param {String} [opts.out.merge] add the results to collectionName: if dups exist the new docs overwrite the old
* @param {Function} [callback] optional callback
- * @see http://www.mongodb.org/display/DOCS/MapReduce
+ * @see MongoDB MapReduce https://www.mongodb.com/docs/manual/core/map-reduce/
* @return {Promise}
* @api public
*/
-Model.mapReduce = function mapReduce(o, callback) {
+Model.mapReduce = function mapReduce(opts, callback) {
_checkContext(this, 'mapReduce');
callback = this.$handleCallbackError(callback);
@@ -4073,20 +4462,20 @@ Model.mapReduce = function mapReduce(o, callback) {
Model.mapReduce.schema = new Schema({}, opts);
}
- if (!o.out) o.out = { inline: 1 };
- if (o.verbose !== false) o.verbose = true;
+ if (!opts.out) opts.out = { inline: 1 };
+ if (opts.verbose !== false) opts.verbose = true;
- o.map = String(o.map);
- o.reduce = String(o.reduce);
+ opts.map = String(opts.map);
+ opts.reduce = String(opts.reduce);
- if (o.query) {
- let q = new this.Query(o.query);
+ if (opts.query) {
+ let q = new this.Query(opts.query);
q.cast(this);
- o.query = q._conditions;
+ opts.query = q._conditions;
q = undefined;
}
- this.$__collection.mapReduce(null, null, o, (err, res) => {
+ this.$__collection.mapReduce(null, null, opts, (err, res) => {
if (err) {
return cb(err);
}
@@ -4108,7 +4497,7 @@ Model.mapReduce = function mapReduce(o, callback) {
};
/**
- * Performs [aggregations](http://docs.mongodb.org/manual/applications/aggregation/) on the models collection.
+ * Performs [aggregations](https://www.mongodb.com/docs/manual/applications/aggregation/) on the models collection.
*
* If a `callback` is passed, the `aggregate` is executed and a `Promise` is returned. If a callback is not passed, the `aggregate` itself is returned.
*
@@ -4116,7 +4505,7 @@ Model.mapReduce = function mapReduce(o, callback) {
*
* - `aggregate()`
*
- * ####Example:
+ * #### Example:
*
* // Find the max balance of all accounts
* const res = await Users.aggregate([
@@ -4133,7 +4522,7 @@ Model.mapReduce = function mapReduce(o, callback) {
* exec();
* console.log(res); // [ { maxBalance: 98 } ]
*
- * ####NOTE:
+ * #### Note:
*
* - Mongoose does **not** cast aggregation pipelines to the model's schema because `$project` and `$group` operators allow redefining the "shape" of the documents at any stage of the pipeline, which may leave documents in an incompatible format. You can use the [mongoose-cast-aggregation plugin](https://github.com/AbdelrahmanHafez/mongoose-cast-aggregation) to enable minimal casting for aggregation pipelines.
* - The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned).
@@ -4142,10 +4531,10 @@ Model.mapReduce = function mapReduce(o, callback) {
*
* - [Mongoose `Aggregate`](/docs/api/aggregate.html)
* - [An Introduction to Mongoose Aggregate](https://masteringjs.io/tutorials/mongoose/aggregate)
- * - [MongoDB Aggregation docs](http://docs.mongodb.org/manual/applications/aggregation/)
+ * - [MongoDB Aggregation docs](https://www.mongodb.com/docs/manual/applications/aggregation/)
*
* @see Aggregate #aggregate_Aggregate
- * @see MongoDB http://docs.mongodb.org/manual/applications/aggregation/
+ * @see MongoDB https://www.mongodb.com/docs/manual/applications/aggregation/
* @param {Array} [pipeline] aggregation pipeline as an array of objects
* @param {Object} [options] aggregation options
* @param {Function} [callback]
@@ -4156,7 +4545,7 @@ Model.mapReduce = function mapReduce(o, callback) {
Model.aggregate = function aggregate(pipeline, options, callback) {
_checkContext(this, 'aggregate');
- if (arguments.length > 3 || get(pipeline, 'constructor.name') === 'Object') {
+ if (arguments.length > 3 || (pipeline && pipeline.constructor && pipeline.constructor.name) === 'Object') {
throw new MongooseError('Mongoose 5.x disallows passing a spread of operators ' +
'to `Model.aggregate()`. Instead of ' +
'`Model.aggregate({ $match }, { $skip })`, do ' +
@@ -4175,7 +4564,6 @@ Model.aggregate = function aggregate(pipeline, options, callback) {
const aggregate = new Aggregate(pipeline || []);
aggregate.model(this);
-
if (options != null) {
aggregate.option(options);
}
@@ -4195,7 +4583,7 @@ Model.aggregate = function aggregate(pipeline, options, callback) {
* Casts and validates the given object against this model's schema, passing the
* given `context` to custom validators.
*
- * ####Example:
+ * #### Example:
*
* const Model = mongoose.model('Test', Schema({
* name: { type: String, required: true },
@@ -4210,7 +4598,7 @@ Model.aggregate = function aggregate(pipeline, options, callback) {
* }
*
* @param {Object} obj
- * @param {Array} pathsToValidate
+ * @param {Array|String} pathsToValidate
* @param {Object} [context]
* @param {Function} [callback]
* @return {Promise|undefined}
@@ -4225,11 +4613,15 @@ Model.validate = function validate(obj, pathsToValidate, context, callback) {
}
return this.db.base._promiseOrCallback(callback, cb => {
- const schema = this.schema;
+ let schema = this.schema;
+ const discriminatorKey = schema.options.discriminatorKey;
+ if (schema.discriminators != null && obj != null && obj[discriminatorKey] != null) {
+ schema = getSchemaDiscriminatorByValue(schema, obj[discriminatorKey]) || schema;
+ }
let paths = Object.keys(schema.paths);
if (pathsToValidate != null) {
- const _pathsToValidate = new Set(pathsToValidate);
+ const _pathsToValidate = typeof pathsToValidate === 'string' ? new Set(pathsToValidate.split(' ')) : new Set(pathsToValidate);
paths = paths.filter(p => {
const pieces = p.split('.');
let cur = pieces[0];
@@ -4247,12 +4639,12 @@ Model.validate = function validate(obj, pathsToValidate, context, callback) {
for (const path of paths) {
const schemaType = schema.path(path);
- if (!schemaType || !schemaType.$isMongooseArray) {
+ if (!schemaType || !schemaType.$isMongooseArray || schemaType.$isMongooseDocumentArray) {
continue;
}
const val = get(obj, path);
- pushNestedArrayPaths(val, path);
+ pushNestedArrayPaths(paths, val, path);
}
let remaining = paths.length;
@@ -4265,7 +4657,7 @@ Model.validate = function validate(obj, pathsToValidate, context, callback) {
continue;
}
- const pieces = path.split('.');
+ const pieces = path.indexOf('.') === -1 ? [path] : path.split('.');
let cur = obj;
for (let i = 0; i < pieces.length - 1; ++i) {
cur = cur[pieces[i]];
@@ -4289,32 +4681,12 @@ Model.validate = function validate(obj, pathsToValidate, context, callback) {
schemaType.doValidate(val, err => {
if (err) {
error = error || new ValidationError();
- if (err instanceof ValidationError) {
- for (const _err of Object.keys(err.errors)) {
- error.addError(`${path}.${err.errors[_err].path}`, _err);
- }
- } else {
- error.addError(err.path, err);
- }
+ error.addError(path, err);
}
_checkDone();
}, context, { path: path });
}
- function pushNestedArrayPaths(nestedArray, path) {
- if (nestedArray == null) {
- return;
- }
-
- for (let i = 0; i < nestedArray.length; ++i) {
- if (Array.isArray(nestedArray[i])) {
- pushNestedArrayPaths(nestedArray[i], path + '.' + i);
- } else {
- paths.push(path + '.' + i);
- }
- }
- }
-
function _checkDone() {
if (--remaining <= 0) {
return cb(error);
@@ -4329,17 +4701,17 @@ Model.validate = function validate(obj, pathsToValidate, context, callback) {
* Changed in Mongoose 6: the model you call `populate()` on should be the
* "local field" model, **not** the "foreign field" model.
*
- * ####Available top-level options:
+ * #### Available top-level options:
*
* - path: space delimited path(s) to populate
* - select: optional fields to select
* - match: optional query conditions to match
* - model: optional name of the model to use for population
* - options: optional query options like sort, limit, etc
- * - justOne: optional boolean, if true Mongoose will always set `path` to an array. Inferred from schema by default.
+ * - justOne: optional boolean, if true Mongoose will always set `path` to a document, or `null` if no document was found. If false, Mongoose will always set `path` to an array, which will be empty if no documents are found. Inferred from schema by default.
* - strictPopulate: optional boolean, set to `false` to allow populating paths that aren't in the schema.
*
- * ####Examples:
+ * #### Example:
*
* const Dog = mongoose.model('Dog', new Schema({ name: String, breed: String }));
* const Person = mongoose.model('Person', new Schema({
@@ -4364,10 +4736,11 @@ Model.validate = function validate(obj, pathsToValidate, context, callback) {
* @param {Document|Array} docs Either a single document or array of documents to populate.
* @param {Object|String} options Either the paths to populate or an object specifying all parameters
* @param {string} [options.path=null] The path to populate.
+ * @param {string|PopulateOptions} [options.populate=null] Recursively populate paths in the populated documents. See [deep populate docs](/docs/populate.html#deep-populate).
* @param {boolean} [options.retainNullValues=false] By default, Mongoose removes null and undefined values from populated arrays. Use this option to make `populate()` retain `null` and `undefined` array entries.
* @param {boolean} [options.getters=false] If true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](/docs/schematypes.html#schematype-options).
* @param {boolean} [options.clone=false] When you do `BlogPost.find().populate('author')`, blog posts with the same author will share 1 copy of an `author` doc. Enable this option to make Mongoose clone populated docs before assigning them.
- * @param {Object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://docs.mongodb.com/manual/tutorial/query-documents/), or a function that returns a filter object.
+ * @param {Object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://www.mongodb.com/docs/manual/tutorial/query-documents/), or a function that returns a filter object.
* @param {Boolean} [options.skipInvalidIds=false] By default, Mongoose throws a cast error if `localField` and `foreignField` schemas don't line up. If you enable this option, Mongoose will instead filter out any `localField` properties that cannot be casted to `foreignField`'s schema type.
* @param {Number} [options.perDocumentLimit=null] For legacy reasons, `limit` with `populate()` may give incorrect results because it only executes a single query for every document being populated. If you set `perDocumentLimit`, Mongoose will ensure correct `limit` per document by executing a separate query for each document to `populate()`. For example, `.find().populate({ path: 'test', perDocumentLimit: 2 })` will execute 2 additional queries if `.find()` returns 2 documents.
* @param {Boolean} [options.strictPopulate=true] Set to false to allow populating paths that aren't defined in the given model's schema.
@@ -4385,7 +4758,6 @@ Model.populate = function(docs, paths, callback) {
// normalized paths
paths = utils.populate(paths);
-
// data that should persist across subPopulate calls
const cache = {};
@@ -4396,13 +4768,14 @@ Model.populate = function(docs, paths, callback) {
}, this.events);
};
-/*!
+/**
* Populate helper
*
* @param {Model} model the model to use
* @param {Document|Array} docs Either a single document or array of documents to populate.
* @param {Object} paths
- * @param {Function} [cb(err,doc)] Optional callback, executed upon completion. Receives `err` and the `doc(s)`.
+ * @param {never} cache Unused
+ * @param {Function} [callback] Optional callback, executed upon completion. Receives `err` and the `doc(s)`.
* @return {Function}
* @api private
*/
@@ -4412,7 +4785,6 @@ function _populate(model, docs, paths, cache, callback) {
if (paths.length === 0) {
return callback(null, docs);
}
-
// each path has its own query options and must be executed separately
for (const path of paths) {
populate(model, docs, path, next);
@@ -4436,6 +4808,17 @@ const excludeIdReg = /\s?-_id\s?/;
const excludeIdRegGlobal = /\s?-_id\s?/g;
function populate(model, docs, options, callback) {
+ const populateOptions = { ...options };
+ if (options.strictPopulate == null) {
+ if (options._localModel != null && options._localModel.schema._userProvidedOptions.strictPopulate != null) {
+ populateOptions.strictPopulate = options._localModel.schema._userProvidedOptions.strictPopulate;
+ } else if (options._localModel != null && model.base.options.strictPopulate != null) {
+ populateOptions.strictPopulate = model.base.options.strictPopulate;
+ } else if (model.base.options.strictPopulate != null) {
+ populateOptions.strictPopulate = model.base.options.strictPopulate;
+ }
+ }
+
// normalize single / multiple docs passed
if (!Array.isArray(docs)) {
docs = [docs];
@@ -4444,13 +4827,13 @@ function populate(model, docs, options, callback) {
return callback();
}
- const modelsMap = getModelsMapForPopulate(model, docs, options);
+ const modelsMap = getModelsMapForPopulate(model, docs, populateOptions);
+
if (modelsMap instanceof MongooseError) {
return immediate(function() {
callback(modelsMap);
});
}
-
const len = modelsMap.length;
let vals = [];
@@ -4469,9 +4852,23 @@ function populate(model, docs, options, callback) {
ids = utils.array.unique(ids);
const assignmentOpts = {};
- assignmentOpts.sort = get(mod, 'options.options.sort', void 0);
+ assignmentOpts.sort = mod &&
+ mod.options &&
+ mod.options.options &&
+ mod.options.options.sort || void 0;
assignmentOpts.excludeId = excludeIdReg.test(select) || (select && select._id === 0);
+ // Lean transform may delete `_id`, which would cause assignment
+ // to fail. So delay running lean transform until _after_
+ // `_assign()`
+ if (mod.options &&
+ mod.options.options &&
+ mod.options.options.lean &&
+ mod.options.options.lean.transform) {
+ mod.options.options._leanTransform = mod.options.options.lean.transform;
+ mod.options.options.lean = true;
+ }
+
if (ids.length === 0 || ids.every(utils.isNullOrUndefined)) {
// Ensure that we set to 0 or empty array even
// if we don't actually execute a query to make sure there's a value
@@ -4482,8 +4879,11 @@ function populate(model, docs, options, callback) {
}
hasOne = true;
+ if (typeof populateOptions.foreignField === 'string') {
+ mod.foreignField.clear();
+ mod.foreignField.add(populateOptions.foreignField);
+ }
const match = createPopulateQueryFilter(ids, mod.match, mod.foreignField, mod.model, mod.options.skipInvalidIds);
-
if (assignmentOpts.excludeId) {
// override the exclusion from the query so we can use the _id
// for document matching during assignment. we'll delete the
@@ -4506,14 +4906,14 @@ function populate(model, docs, options, callback) {
}
if (!hasOne) {
// If models but no docs, skip further deep populate.
- if (modelsMap.length > 0) {
+ if (modelsMap.length !== 0) {
return callback();
}
// If no models to populate but we have a nested populate,
// keep trying, re: gh-8946
- if (options.populate != null) {
- const opts = utils.populate(options.populate).map(pop => Object.assign({}, pop, {
- path: options.path + '.' + pop.path
+ if (populateOptions.populate != null) {
+ const opts = utils.populate(populateOptions.populate).map(pop => Object.assign({}, pop, {
+ path: populateOptions.path + '.' + pop.path
}));
return model.populate(docs, opts, callback);
}
@@ -4540,12 +4940,24 @@ function populate(model, docs, options, callback) {
for (const val of vals) {
mod.options._childDocs.push(val);
}
- _assign(model, vals, mod, assignmentOpts);
+ try {
+ _assign(model, vals, mod, assignmentOpts);
+ } catch (err) {
+ return callback(err);
+ }
}
for (const arr of params) {
removeDeselectedForeignField(arr[0].foreignField, arr[0].options, vals);
}
+ for (const arr of params) {
+ const mod = arr[0];
+ if (mod.options && mod.options.options && mod.options.options._leanTransform) {
+ for (const doc of vals) {
+ mod.options.options._leanTransform(doc);
+ }
+ }
+ }
callback();
}
}
@@ -4555,7 +4967,7 @@ function populate(model, docs, options, callback) {
*/
function _execPopulateQuery(mod, match, select, assignmentOpts, callback) {
- const subPopulate = utils.clone(mod.options.populate);
+ let subPopulate = utils.clone(mod.options.populate);
const queryOptions = Object.assign({
skip: mod.options.skip,
limit: mod.options.limit,
@@ -4572,7 +4984,6 @@ function _execPopulateQuery(mod, match, select, assignmentOpts, callback) {
} else if (queryOptions.limit != null) {
queryOptions.limit = queryOptions.limit * mod.ids.length;
}
-
const query = mod.model.find(match, select, queryOptions);
// If we're doing virtual populate and projection is inclusive and foreign
// field is not selected, automatically select it because mongoose needs it.
@@ -4600,10 +5011,22 @@ function _execPopulateQuery(mod, match, select, assignmentOpts, callback) {
if (mod.model.baseModelName != null) {
if (Array.isArray(subPopulate)) {
subPopulate.forEach(pop => { pop.strictPopulate = false; });
+ } else if (typeof subPopulate === 'string') {
+ subPopulate = { path: subPopulate, strictPopulate: false };
} else {
subPopulate.strictPopulate = false;
}
}
+ const basePath = mod.options._fullPath || mod.options.path;
+
+ if (Array.isArray(subPopulate)) {
+ for (const pop of subPopulate) {
+ pop._fullPath = basePath + '.' + pop.path;
+ }
+ } else if (typeof subPopulate === 'object') {
+ subPopulate._fullPath = basePath + '.' + subPopulate.path;
+ }
+
query.populate(subPopulate);
}
@@ -4617,6 +5040,7 @@ function _execPopulateQuery(mod, match, select, assignmentOpts, callback) {
}
callback(null, docs);
});
+
}
/*!
@@ -4628,7 +5052,9 @@ function _assign(model, vals, mod, assignmentOpts) {
const isVirtual = mod.isVirtual;
const justOne = mod.justOne;
let _val;
- const lean = get(options, 'options.lean', false);
+ const lean = options &&
+ options.options &&
+ options.options.lean || false;
const len = vals.length;
const rawOrder = {};
const rawDocs = {};
@@ -4645,7 +5071,6 @@ function _assign(model, vals, mod, assignmentOpts) {
if (val == null) {
continue;
}
-
for (const foreignField of mod.foreignField) {
_val = utils.getValue(foreignField, val);
if (Array.isArray(_val)) {
@@ -4699,7 +5124,7 @@ function _assign(model, vals, mod, assignmentOpts) {
}
// flag each as result of population
if (!lean) {
- val.$__.wasPopulated = true;
+ val.$__.wasPopulated = val.$__.wasPopulated || { value: _val };
}
}
}
@@ -4709,6 +5134,7 @@ function _assign(model, vals, mod, assignmentOpts) {
// If virtual, make sure to not mutate original field
rawIds: mod.isVirtual ? allIds : mod.allIds,
allIds: allIds,
+ unpopulatedValues: mod.unpopulatedValues,
foreignField: mod.foreignField,
rawDocs: rawDocs,
rawOrder: rawOrder,
@@ -4726,7 +5152,7 @@ function _assign(model, vals, mod, assignmentOpts) {
});
}
-/*!
+/**
* Compiler utility.
*
* @param {String|Function} name model name or class extending Model
@@ -4734,6 +5160,7 @@ function _assign(model, vals, mod, assignmentOpts) {
* @param {String} collectionName
* @param {Connection} connection
* @param {Mongoose} base mongoose instance
+ * @api private
*/
Model.compile = function compile(name, schema, collectionName, connection, base) {
@@ -4745,7 +5172,6 @@ Model.compile = function compile(name, schema, collectionName, connection, base)
o[schema.options.versionKey] = Number;
schema.add(o);
}
-
let model;
if (typeof name === 'function' && name.prototype instanceof Model) {
model = name;
@@ -4783,12 +5209,13 @@ Model.compile = function compile(name, schema, collectionName, connection, base)
model.modelName = name;
if (!(model.prototype instanceof Model)) {
- model.__proto__ = Model;
- model.prototype.__proto__ = Model.prototype;
+ Object.setPrototypeOf(model, Model);
+ Object.setPrototypeOf(model.prototype, Model.prototype);
}
model.model = function model(name) {
return this.db.model(name);
};
+
model.db = connection;
model.prototype.db = connection;
model.prototype[modelDbSymbol] = connection;
@@ -4834,19 +5261,21 @@ Model.compile = function compile(name, schema, collectionName, connection, base)
model.Query = function() {
Query.apply(this, arguments);
};
- model.Query.prototype = Object.create(Query.prototype);
+ Object.setPrototypeOf(model.Query.prototype, Query.prototype);
model.Query.base = Query.base;
+ model.Query.prototype.constructor = Query;
applyQueryMiddleware(model.Query, model);
applyQueryMethods(model, schema.query);
return model;
};
-/*!
+/**
* Register custom query methods for this model
*
* @param {Model} model
* @param {Schema} schema
+ * @api private
*/
function applyQueryMethods(model, methods) {
@@ -4855,13 +5284,17 @@ function applyQueryMethods(model, methods) {
}
}
-/*!
+/**
* Subclass this model with `conn`, `schema`, and `collection` settings.
*
* @param {Connection} conn
* @param {Schema} [schema]
* @param {String} [collection]
* @return {Model}
+ * @api private
+ * @memberOf Model
+ * @static
+ * @method __subclass
*/
Model.__subclass = function subclass(conn, schema, collection) {
@@ -4875,8 +5308,8 @@ Model.__subclass = function subclass(conn, schema, collection) {
_this.call(this, doc, fields, skipId);
};
- Model.__proto__ = _this;
- Model.prototype.__proto__ = _this.prototype;
+ Object.setPrototypeOf(Model, _this);
+ Object.setPrototypeOf(Model.prototype, _this.prototype);
Model.db = conn;
Model.prototype.db = conn;
Model.prototype[modelDbSymbol] = conn;
@@ -4938,8 +5371,14 @@ Model.$handleCallbackError = function(callback) {
};
};
-/*!
+/**
* ignore
+ *
+ * @param {Function} callback
+ * @api private
+ * @method $wrapCallback
+ * @memberOf Model
+ * @static
*/
Model.$wrapCallback = function(callback) {
@@ -4962,7 +5401,7 @@ Model.$wrapCallback = function(callback) {
* Helper for console.log. Given a model named 'MyModel', returns the string
* `'Model { MyModel }'`.
*
- * ####Example:
+ * #### Example:
*
* const MyModel = mongoose.model('Test', Schema({ name: String }));
* MyModel.inspect(); // 'Model { Test }'
@@ -4976,10 +5415,7 @@ Model.inspect = function() {
};
if (util.inspect.custom) {
- /*!
- * Avoid Node deprecation warning DEP0079
- */
-
+ // Avoid Node deprecation warning DEP0079
Model[util.inspect.custom] = Model.inspect;
}
diff --git a/lib/options/PopulateOptions.js b/lib/options/PopulateOptions.js
index 5b9819460dc..ad743fe0fee 100644
--- a/lib/options/PopulateOptions.js
+++ b/lib/options/PopulateOptions.js
@@ -33,4 +33,4 @@ class PopulateOptions {
* @api public
*/
-module.exports = PopulateOptions;
\ No newline at end of file
+module.exports = PopulateOptions;
diff --git a/lib/options/SchemaArrayOptions.js b/lib/options/SchemaArrayOptions.js
index e3734ae4992..54ad4f09058 100644
--- a/lib/options/SchemaArrayOptions.js
+++ b/lib/options/SchemaArrayOptions.js
@@ -5,7 +5,7 @@ const SchemaTypeOptions = require('./SchemaTypeOptions');
/**
* The options defined on an Array schematype.
*
- * ####Example:
+ * #### Example:
*
* const schema = new Schema({ tags: [String] });
* schema.path('tags').options; // SchemaArrayOptions instance
@@ -26,7 +26,7 @@ const opts = require('./propertyOptions');
* @api public
* @property enum
* @memberOf SchemaArrayOptions
- * @type Array
+ * @type {Array}
* @instance
*/
@@ -36,7 +36,7 @@ Object.defineProperty(SchemaArrayOptions.prototype, 'enum', opts);
* If set, specifies the type of this array's values. Equivalent to setting
* `type` to an array whose first element is `of`.
*
- * ####Example:
+ * #### Example:
*
* // `arr` is an array of numbers.
* new Schema({ arr: [Number] });
@@ -46,14 +46,33 @@ Object.defineProperty(SchemaArrayOptions.prototype, 'enum', opts);
* @api public
* @property of
* @memberOf SchemaArrayOptions
- * @type Function|String
+ * @type {Function|String}
* @instance
*/
Object.defineProperty(SchemaArrayOptions.prototype, 'of', opts);
+/**
+ * If set to `false`, will always deactivate casting non-array values to arrays.
+ * If set to `true`, will cast non-array values to arrays if `init` and `SchemaArray.options.castNonArrays` are also `true`
+ *
+ * #### Example:
+ *
+ * const Model = db.model('Test', new Schema({ x1: { castNonArrays: false, type: [String] } }));
+ * const doc = new Model({ x1: "some non-array value" });
+ * await doc.validate(); // Errors with "CastError"
+ *
+ * @api public
+ * @property castNonArrays
+ * @memberOf SchemaArrayOptions
+ * @type {Boolean}
+ * @instance
+ */
+
+Object.defineProperty(SchemaArrayOptions.prototype, 'castNonArrays', opts);
+
/*!
* ignore
*/
-module.exports = SchemaArrayOptions;
\ No newline at end of file
+module.exports = SchemaArrayOptions;
diff --git a/lib/options/SchemaBufferOptions.js b/lib/options/SchemaBufferOptions.js
index 258130dfee8..377e3566a58 100644
--- a/lib/options/SchemaBufferOptions.js
+++ b/lib/options/SchemaBufferOptions.js
@@ -5,7 +5,7 @@ const SchemaTypeOptions = require('./SchemaTypeOptions');
/**
* The options defined on a Buffer schematype.
*
- * ####Example:
+ * #### Example:
*
* const schema = new Schema({ bitmap: Buffer });
* schema.path('bitmap').options; // SchemaBufferOptions instance
@@ -25,7 +25,7 @@ const opts = require('./propertyOptions');
* @api public
* @property subtype
* @memberOf SchemaBufferOptions
- * @type Number
+ * @type {Number}
* @instance
*/
@@ -35,4 +35,4 @@ Object.defineProperty(SchemaBufferOptions.prototype, 'subtype', opts);
* ignore
*/
-module.exports = SchemaBufferOptions;
\ No newline at end of file
+module.exports = SchemaBufferOptions;
diff --git a/lib/options/SchemaDateOptions.js b/lib/options/SchemaDateOptions.js
index 09bf27f6adf..c7d3d4e1044 100644
--- a/lib/options/SchemaDateOptions.js
+++ b/lib/options/SchemaDateOptions.js
@@ -5,7 +5,7 @@ const SchemaTypeOptions = require('./SchemaTypeOptions');
/**
* The options defined on a Date schematype.
*
- * ####Example:
+ * #### Example:
*
* const schema = new Schema({ startedAt: Date });
* schema.path('startedAt').options; // SchemaDateOptions instance
@@ -26,7 +26,7 @@ const opts = require('./propertyOptions');
* @api public
* @property min
* @memberOf SchemaDateOptions
- * @type Date
+ * @type {Date}
* @instance
*/
@@ -39,7 +39,7 @@ Object.defineProperty(SchemaDateOptions.prototype, 'min', opts);
* @api public
* @property max
* @memberOf SchemaDateOptions
- * @type Date
+ * @type {Date}
* @instance
*/
@@ -48,10 +48,17 @@ Object.defineProperty(SchemaDateOptions.prototype, 'max', opts);
/**
* If set, Mongoose creates a TTL index on this path.
*
+ * mongo TTL index `expireAfterSeconds` value will take 'expires' value expressed in seconds.
+ *
+ * #### Example:
+ *
+ * const schema = new Schema({ "expireAt": { type: Date, expires: 11 } });
+ * // if 'expireAt' is set, then document expires at expireAt + 11 seconds
+ *
* @api public
* @property expires
* @memberOf SchemaDateOptions
- * @type Date
+ * @type {Date}
* @instance
*/
@@ -61,4 +68,4 @@ Object.defineProperty(SchemaDateOptions.prototype, 'expires', opts);
* ignore
*/
-module.exports = SchemaDateOptions;
\ No newline at end of file
+module.exports = SchemaDateOptions;
diff --git a/lib/options/SchemaDocumentArrayOptions.js b/lib/options/SchemaDocumentArrayOptions.js
index f3fb7a9777e..b826b87877b 100644
--- a/lib/options/SchemaDocumentArrayOptions.js
+++ b/lib/options/SchemaDocumentArrayOptions.js
@@ -5,7 +5,7 @@ const SchemaTypeOptions = require('./SchemaTypeOptions');
/**
* The options defined on an Document Array schematype.
*
- * ####Example:
+ * #### Example:
*
* const schema = new Schema({ users: [{ name: string }] });
* schema.path('users').options; // SchemaDocumentArrayOptions instance
@@ -23,7 +23,7 @@ const opts = require('./propertyOptions');
* If `true`, Mongoose will skip building any indexes defined in this array's schema.
* If not set, Mongoose will build all indexes defined in this array's schema.
*
- * ####Example:
+ * #### Example:
*
* const childSchema = Schema({ name: { type: String, index: true } });
* // If `excludeIndexes` is `true`, Mongoose will skip building an index
@@ -35,7 +35,7 @@ const opts = require('./propertyOptions');
* @api public
* @property excludeIndexes
* @memberOf SchemaDocumentArrayOptions
- * @type Array
+ * @type {Array}
* @instance
*/
@@ -44,7 +44,7 @@ Object.defineProperty(SchemaDocumentArrayOptions.prototype, 'excludeIndexes', op
/**
* If set, overwrites the child schema's `_id` option.
*
- * ####Example:
+ * #### Example:
*
* const childSchema = Schema({ name: String });
* const parentSchema = Schema({
@@ -55,7 +55,7 @@ Object.defineProperty(SchemaDocumentArrayOptions.prototype, 'excludeIndexes', op
* @api public
* @property _id
* @memberOf SchemaDocumentArrayOptions
- * @type Array
+ * @type {Array}
* @instance
*/
@@ -65,4 +65,4 @@ Object.defineProperty(SchemaDocumentArrayOptions.prototype, '_id', opts);
* ignore
*/
-module.exports = SchemaDocumentArrayOptions;
\ No newline at end of file
+module.exports = SchemaDocumentArrayOptions;
diff --git a/lib/options/SchemaMapOptions.js b/lib/options/SchemaMapOptions.js
index 335d84a9876..bbabaa0700d 100644
--- a/lib/options/SchemaMapOptions.js
+++ b/lib/options/SchemaMapOptions.js
@@ -5,7 +5,7 @@ const SchemaTypeOptions = require('./SchemaTypeOptions');
/**
* The options defined on a Map schematype.
*
- * ####Example:
+ * #### Example:
*
* const schema = new Schema({ socialMediaHandles: { type: Map, of: String } });
* schema.path('socialMediaHandles').options; // SchemaMapOptions instance
@@ -25,7 +25,7 @@ const opts = require('./propertyOptions');
*
* If not set, Mongoose will not cast the map's values.
*
- * ####Example:
+ * #### Example:
*
* // Mongoose will cast `socialMediaHandles` values to strings
* const schema = new Schema({ socialMediaHandles: { type: Map, of: String } });
@@ -34,10 +34,10 @@ const opts = require('./propertyOptions');
* @api public
* @property of
* @memberOf SchemaMapOptions
- * @type Function|string
+ * @type {Function|string}
* @instance
*/
Object.defineProperty(SchemaMapOptions.prototype, 'of', opts);
-module.exports = SchemaMapOptions;
\ No newline at end of file
+module.exports = SchemaMapOptions;
diff --git a/lib/options/SchemaNumberOptions.js b/lib/options/SchemaNumberOptions.js
index 38553fb78b1..bd91da01b19 100644
--- a/lib/options/SchemaNumberOptions.js
+++ b/lib/options/SchemaNumberOptions.js
@@ -5,7 +5,7 @@ const SchemaTypeOptions = require('./SchemaTypeOptions');
/**
* The options defined on a Number schematype.
*
- * ####Example:
+ * #### Example:
*
* const schema = new Schema({ count: Number });
* schema.path('count').options; // SchemaNumberOptions instance
@@ -26,7 +26,7 @@ const opts = require('./propertyOptions');
* @api public
* @property min
* @memberOf SchemaNumberOptions
- * @type Number
+ * @type {Number}
* @instance
*/
@@ -39,7 +39,7 @@ Object.defineProperty(SchemaNumberOptions.prototype, 'min', opts);
* @api public
* @property max
* @memberOf SchemaNumberOptions
- * @type Number
+ * @type {Number}
* @instance
*/
@@ -49,7 +49,8 @@ Object.defineProperty(SchemaNumberOptions.prototype, 'max', opts);
* If set, Mongoose adds a validator that checks that this path is strictly
* equal to one of the given values.
*
- * ####Example:
+ * #### Example:
+ *
* const schema = new Schema({
* favoritePrime: {
* type: Number,
@@ -61,7 +62,7 @@ Object.defineProperty(SchemaNumberOptions.prototype, 'max', opts);
* @api public
* @property enum
* @memberOf SchemaNumberOptions
- * @type Array
+ * @type {Array}
* @instance
*/
@@ -70,7 +71,8 @@ Object.defineProperty(SchemaNumberOptions.prototype, 'enum', opts);
/**
* Sets default [populate options](/docs/populate.html#query-conditions).
*
- * ####Example:
+ * #### Example:
+ *
* const schema = new Schema({
* child: {
* type: Number,
@@ -86,7 +88,7 @@ Object.defineProperty(SchemaNumberOptions.prototype, 'enum', opts);
* @api public
* @property populate
* @memberOf SchemaNumberOptions
- * @type Object
+ * @type {Object}
* @instance
*/
@@ -96,4 +98,4 @@ Object.defineProperty(SchemaNumberOptions.prototype, 'populate', opts);
* ignore
*/
-module.exports = SchemaNumberOptions;
\ No newline at end of file
+module.exports = SchemaNumberOptions;
diff --git a/lib/options/SchemaObjectIdOptions.js b/lib/options/SchemaObjectIdOptions.js
index e07ed9ecc02..37048e92c4d 100644
--- a/lib/options/SchemaObjectIdOptions.js
+++ b/lib/options/SchemaObjectIdOptions.js
@@ -5,7 +5,7 @@ const SchemaTypeOptions = require('./SchemaTypeOptions');
/**
* The options defined on an ObjectId schematype.
*
- * ####Example:
+ * #### Example:
*
* const schema = new Schema({ testId: mongoose.ObjectId });
* schema.path('testId').options; // SchemaObjectIdOptions instance
@@ -25,7 +25,7 @@ const opts = require('./propertyOptions');
* @api public
* @property auto
* @memberOf SchemaObjectIdOptions
- * @type Boolean
+ * @type {Boolean}
* @instance
*/
@@ -34,7 +34,8 @@ Object.defineProperty(SchemaObjectIdOptions.prototype, 'auto', opts);
/**
* Sets default [populate options](/docs/populate.html#query-conditions).
*
- * ####Example:
+ * #### Example:
+ *
* const schema = new Schema({
* child: {
* type: 'ObjectId',
@@ -50,7 +51,7 @@ Object.defineProperty(SchemaObjectIdOptions.prototype, 'auto', opts);
* @api public
* @property populate
* @memberOf SchemaObjectIdOptions
- * @type Object
+ * @type {Object}
* @instance
*/
@@ -60,4 +61,4 @@ Object.defineProperty(SchemaObjectIdOptions.prototype, 'populate', opts);
* ignore
*/
-module.exports = SchemaObjectIdOptions;
\ No newline at end of file
+module.exports = SchemaObjectIdOptions;
diff --git a/lib/options/SchemaStringOptions.js b/lib/options/SchemaStringOptions.js
index f67476fcf96..49836ef13d0 100644
--- a/lib/options/SchemaStringOptions.js
+++ b/lib/options/SchemaStringOptions.js
@@ -5,7 +5,7 @@ const SchemaTypeOptions = require('./SchemaTypeOptions');
/**
* The options defined on a string schematype.
*
- * ####Example:
+ * #### Example:
*
* const schema = new Schema({ name: String });
* schema.path('name').options; // SchemaStringOptions instance
@@ -25,7 +25,7 @@ const opts = require('./propertyOptions');
* @api public
* @property enum
* @memberOf SchemaStringOptions
- * @type Array
+ * @type {Array}
* @instance
*/
@@ -38,7 +38,7 @@ Object.defineProperty(SchemaStringOptions.prototype, 'enum', opts);
* @api public
* @property match
* @memberOf SchemaStringOptions
- * @type RegExp
+ * @type {RegExp}
* @instance
*/
@@ -51,7 +51,7 @@ Object.defineProperty(SchemaStringOptions.prototype, 'match', opts);
* @api public
* @property lowercase
* @memberOf SchemaStringOptions
- * @type Boolean
+ * @type {Boolean}
* @instance
*/
@@ -64,7 +64,7 @@ Object.defineProperty(SchemaStringOptions.prototype, 'lowercase', opts);
* @api public
* @property trim
* @memberOf SchemaStringOptions
- * @type Boolean
+ * @type {Boolean}
* @instance
*/
@@ -77,7 +77,7 @@ Object.defineProperty(SchemaStringOptions.prototype, 'trim', opts);
* @api public
* @property uppercase
* @memberOf SchemaStringOptions
- * @type Boolean
+ * @type {Boolean}
* @instance
*/
@@ -94,7 +94,7 @@ Object.defineProperty(SchemaStringOptions.prototype, 'uppercase', opts);
* @api public
* @property minLength
* @memberOf SchemaStringOptions
- * @type Number
+ * @type {Number}
* @instance
*/
@@ -112,7 +112,7 @@ Object.defineProperty(SchemaStringOptions.prototype, 'minlength', opts);
* @api public
* @property maxLength
* @memberOf SchemaStringOptions
- * @type Number
+ * @type {Number}
* @instance
*/
@@ -125,7 +125,7 @@ Object.defineProperty(SchemaStringOptions.prototype, 'maxlength', opts);
* @api public
* @property populate
* @memberOf SchemaStringOptions
- * @type Object
+ * @type {Object}
* @instance
*/
diff --git a/lib/options/SchemaSubdocumentOptions.js b/lib/options/SchemaSubdocumentOptions.js
index 167565376ea..6782120350a 100644
--- a/lib/options/SchemaSubdocumentOptions.js
+++ b/lib/options/SchemaSubdocumentOptions.js
@@ -5,7 +5,7 @@ const SchemaTypeOptions = require('./SchemaTypeOptions');
/**
* The options defined on a single nested schematype.
*
- * ####Example:
+ * #### Example:
*
* const schema = Schema({ child: Schema({ name: String }) });
* schema.path('child').options; // SchemaSubdocumentOptions instance
@@ -22,7 +22,7 @@ const opts = require('./propertyOptions');
/**
* If set, overwrites the child schema's `_id` option.
*
- * ####Example:
+ * #### Example:
*
* const childSchema = Schema({ name: String });
* const parentSchema = Schema({
@@ -33,10 +33,10 @@ const opts = require('./propertyOptions');
* @api public
* @property of
* @memberOf SchemaSubdocumentOptions
- * @type Function|string
+ * @type {Function|string}
* @instance
*/
Object.defineProperty(SchemaSubdocumentOptions.prototype, '_id', opts);
-module.exports = SchemaSubdocumentOptions;
\ No newline at end of file
+module.exports = SchemaSubdocumentOptions;
diff --git a/lib/options/SchemaTypeOptions.js b/lib/options/SchemaTypeOptions.js
index 16c3df34980..f2376431034 100644
--- a/lib/options/SchemaTypeOptions.js
+++ b/lib/options/SchemaTypeOptions.js
@@ -5,7 +5,7 @@ const clone = require('../helpers/clone');
/**
* The options defined on a schematype.
*
- * ####Example:
+ * #### Example:
*
* const schema = new Schema({ name: String });
* schema.path('name').options instanceof mongoose.SchemaTypeOptions; // true
@@ -31,7 +31,7 @@ const opts = require('./propertyOptions');
* @api public
* @property type
* @memberOf SchemaTypeOptions
- * @type Function|String|Object
+ * @type {Function|String|Object}
* @instance
*/
@@ -43,7 +43,7 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'type', opts);
* @api public
* @property validate
* @memberOf SchemaTypeOptions
- * @type Function|Object
+ * @type {Function|Object}
* @instance
*/
@@ -53,7 +53,7 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'validate', opts);
* Allows overriding casting logic for this individual path. If a string, the
* given string overwrites Mongoose's default cast error message.
*
- * ####Example:
+ * #### Example:
*
* const schema = new Schema({
* num: {
@@ -74,7 +74,7 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'validate', opts);
* @api public
* @property cast
* @memberOf SchemaTypeOptions
- * @type String
+ * @type {String}
* @instance
*/
@@ -88,7 +88,7 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'cast', opts);
* @api public
* @property required
* @memberOf SchemaTypeOptions
- * @type Function|Boolean
+ * @type {Function|Boolean}
* @instance
*/
@@ -101,7 +101,7 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'required', opts);
* @api public
* @property default
* @memberOf SchemaTypeOptions
- * @type Function|Any
+ * @type {Function|Any}
* @instance
*/
@@ -113,12 +113,25 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'default', opts);
* @api public
* @property ref
* @memberOf SchemaTypeOptions
- * @type Function|String
+ * @type {Function|String}
* @instance
*/
Object.defineProperty(SchemaTypeOptions.prototype, 'ref', opts);
+/**
+ * The path in the document that `populate()` should use to find the model
+ * to use.
+ *
+ * @api public
+ * @property ref
+ * @memberOf SchemaTypeOptions
+ * @type {Function|String}
+ * @instance
+ */
+
+Object.defineProperty(SchemaTypeOptions.prototype, 'refPath', opts);
+
/**
* Whether to include or exclude this path by default when loading documents
* using `find()`, `findOne()`, etc.
@@ -126,7 +139,7 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'ref', opts);
* @api public
* @property select
* @memberOf SchemaTypeOptions
- * @type Boolean|Number
+ * @type {Boolean|Number}
* @instance
*/
@@ -139,7 +152,7 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'select', opts);
* @api public
* @property index
* @memberOf SchemaTypeOptions
- * @type Boolean|Number|Object
+ * @type {Boolean|Number|Object}
* @instance
*/
@@ -153,7 +166,7 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'index', opts);
* @api public
* @property unique
* @memberOf SchemaTypeOptions
- * @type Boolean|Number
+ * @type {Boolean|Number}
* @instance
*/
@@ -162,12 +175,12 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'unique', opts);
/**
* If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose will
* disallow changes to this path once the document
- * is saved to the database for the first time. Read more about [immutability in Mongoose here](http://thecodebarbarian.com/whats-new-in-mongoose-5-6-immutable-properties.html).
+ * is saved to the database for the first time. Read more about [immutability in Mongoose here](https://thecodebarbarian.com/whats-new-in-mongoose-5-6-immutable-properties.html).
*
* @api public
* @property immutable
* @memberOf SchemaTypeOptions
- * @type Function|Boolean
+ * @type {Function|Boolean}
* @instance
*/
@@ -180,7 +193,7 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'immutable', opts);
* @api public
* @property sparse
* @memberOf SchemaTypeOptions
- * @type Boolean|Number
+ * @type {Boolean|Number}
* @instance
*/
@@ -193,7 +206,7 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'sparse', opts);
* @api public
* @property text
* @memberOf SchemaTypeOptions
- * @type Boolean|Number|Object
+ * @type {Boolean|Number|Object}
* @instance
*/
@@ -203,7 +216,7 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'text', opts);
* Define a transform function for this individual schema type.
* Only called when calling `toJSON()` or `toObject()`.
*
- * ####Example:
+ * #### Example:
*
* const schema = Schema({
* myDate: {
@@ -222,10 +235,10 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'text', opts);
* @api public
* @property transform
* @memberOf SchemaTypeOptions
- * @type Function
+ * @type {Function}
* @instance
*/
Object.defineProperty(SchemaTypeOptions.prototype, 'transform', opts);
-module.exports = SchemaTypeOptions;
\ No newline at end of file
+module.exports = SchemaTypeOptions;
diff --git a/lib/options/VirtualOptions.js b/lib/options/VirtualOptions.js
index a26641459ec..3db53b99d27 100644
--- a/lib/options/VirtualOptions.js
+++ b/lib/options/VirtualOptions.js
@@ -19,7 +19,7 @@ class VirtualOptions {
* @api public
* @property ref
* @memberOf VirtualOptions
- * @type String|Model|Function
+ * @type {String|Model|Function}
* @instance
*/
@@ -32,7 +32,7 @@ Object.defineProperty(VirtualOptions.prototype, 'ref', opts);
* @api public
* @property refPath
* @memberOf VirtualOptions
- * @type String|Function
+ * @type {String|Function}
* @instance
*/
@@ -45,7 +45,7 @@ Object.defineProperty(VirtualOptions.prototype, 'refPath', opts);
* @api public
* @property localField
* @memberOf VirtualOptions
- * @type String|Function
+ * @type {String|Function}
* @instance
*/
@@ -58,7 +58,7 @@ Object.defineProperty(VirtualOptions.prototype, 'localField', opts);
* @api public
* @property foreignField
* @memberOf VirtualOptions
- * @type String|Function
+ * @type {String|Function}
* @instance
*/
@@ -71,7 +71,7 @@ Object.defineProperty(VirtualOptions.prototype, 'foreignField', opts);
* @api public
* @property justOne
* @memberOf VirtualOptions
- * @type Boolean
+ * @type {Boolean}
* @instance
*/
@@ -86,7 +86,7 @@ Object.defineProperty(VirtualOptions.prototype, 'justOne', opts);
* @api public
* @property count
* @memberOf VirtualOptions
- * @type Boolean
+ * @type {Boolean}
* @instance
*/
@@ -99,7 +99,7 @@ Object.defineProperty(VirtualOptions.prototype, 'count', opts);
* @api public
* @property match
* @memberOf VirtualOptions
- * @type Object|Function
+ * @type {Object|Function}
* @instance
*/
@@ -115,7 +115,7 @@ Object.defineProperty(VirtualOptions.prototype, 'match', opts);
* @api public
* @property options
* @memberOf VirtualOptions
- * @type Object
+ * @type {Object}
* @instance
*/
@@ -127,7 +127,7 @@ Object.defineProperty(VirtualOptions.prototype, 'options', opts);
* @api public
* @property skip
* @memberOf VirtualOptions
- * @type Number
+ * @type {Number}
* @instance
*/
@@ -139,7 +139,7 @@ Object.defineProperty(VirtualOptions.prototype, 'skip', opts);
* @api public
* @property limit
* @memberOf VirtualOptions
- * @type Number
+ * @type {Number}
* @instance
*/
@@ -155,10 +155,10 @@ Object.defineProperty(VirtualOptions.prototype, 'limit', opts);
* @api public
* @property perDocumentLimit
* @memberOf VirtualOptions
- * @type Number
+ * @type {Number}
* @instance
*/
Object.defineProperty(VirtualOptions.prototype, 'perDocumentLimit', opts);
-module.exports = VirtualOptions;
\ No newline at end of file
+module.exports = VirtualOptions;
diff --git a/lib/options/propertyOptions.js b/lib/options/propertyOptions.js
index b7488a37ee9..cb95f35e88a 100644
--- a/lib/options/propertyOptions.js
+++ b/lib/options/propertyOptions.js
@@ -5,4 +5,4 @@ module.exports = Object.freeze({
configurable: true,
writable: true,
value: void 0
-});
\ No newline at end of file
+});
diff --git a/lib/options/removeOptions.js b/lib/options/removeOptions.js
index 0d915864500..3e09bbc0f89 100644
--- a/lib/options/removeOptions.js
+++ b/lib/options/removeOptions.js
@@ -11,4 +11,4 @@ class RemoveOptions {
}
}
-module.exports = RemoveOptions;
\ No newline at end of file
+module.exports = RemoveOptions;
diff --git a/lib/options/saveOptions.js b/lib/options/saveOptions.js
index 22ef4375fbe..66c1608b1d5 100644
--- a/lib/options/saveOptions.js
+++ b/lib/options/saveOptions.js
@@ -11,4 +11,4 @@ class SaveOptions {
}
}
-module.exports = SaveOptions;
\ No newline at end of file
+module.exports = SaveOptions;
diff --git a/lib/plugins/clearValidating.js b/lib/plugins/clearValidating.js
deleted file mode 100644
index a377bb7219b..00000000000
--- a/lib/plugins/clearValidating.js
+++ /dev/null
@@ -1,28 +0,0 @@
-'use strict';
-
-/*!
- * ignore
- */
-
-module.exports = function(schema) {
- // `this.$__.validating` tracks whether there are multiple validations running
- // in parallel. We need to clear `this.$__.validating` before post hooks for gh-8597
- const unshift = true;
- schema.s.hooks.post('validate', false, function() {
- if (this.ownerDocument) {
- return;
- }
-
- this.$__.validating = null;
- }, unshift);
-
- schema.s.hooks.post('validate', false, function(error, res, next) {
- if (this.ownerDocument) {
- next();
- return;
- }
-
- this.$__.validating = null;
- next();
- }, unshift);
-};
diff --git a/lib/plugins/index.js b/lib/plugins/index.js
new file mode 100644
index 00000000000..69fa6ad284c
--- /dev/null
+++ b/lib/plugins/index.js
@@ -0,0 +1,7 @@
+'use strict';
+
+exports.removeSubdocs = require('./removeSubdocs');
+exports.saveSubdocs = require('./saveSubdocs');
+exports.sharding = require('./sharding');
+exports.trackTransaction = require('./trackTransaction');
+exports.validateBeforeSave = require('./validateBeforeSave');
diff --git a/lib/plugins/removeSubdocs.js b/lib/plugins/removeSubdocs.js
index c2756fc5374..e320f782a0e 100644
--- a/lib/plugins/removeSubdocs.js
+++ b/lib/plugins/removeSubdocs.js
@@ -6,10 +6,10 @@ const each = require('../helpers/each');
* ignore
*/
-module.exports = function(schema) {
+module.exports = function removeSubdocs(schema) {
const unshift = true;
- schema.s.hooks.pre('remove', false, function(next) {
- if (this.ownerDocument) {
+ schema.s.hooks.pre('remove', false, function removeSubDocsPreRemove(next) {
+ if (this.$isSubdocument) {
next();
return;
}
diff --git a/lib/plugins/saveSubdocs.js b/lib/plugins/saveSubdocs.js
index fcc73d88a71..758acbbfe2e 100644
--- a/lib/plugins/saveSubdocs.js
+++ b/lib/plugins/saveSubdocs.js
@@ -6,10 +6,10 @@ const each = require('../helpers/each');
* ignore
*/
-module.exports = function(schema) {
+module.exports = function saveSubdocs(schema) {
const unshift = true;
- schema.s.hooks.pre('save', false, function(next) {
- if (this.ownerDocument) {
+ schema.s.hooks.pre('save', false, function saveSubdocsPreSave(next) {
+ if (this.$isSubdocument) {
next();
return;
}
@@ -36,8 +36,8 @@ module.exports = function(schema) {
});
}, null, unshift);
- schema.s.hooks.post('save', function(doc, next) {
- if (this.ownerDocument) {
+ schema.s.hooks.post('save', function saveSubdocsPostSave(doc, next) {
+ if (this.$isSubdocument) {
next();
return;
}
diff --git a/lib/plugins/sharding.js b/lib/plugins/sharding.js
index 020ec06c633..7d905f31c0f 100644
--- a/lib/plugins/sharding.js
+++ b/lib/plugins/sharding.js
@@ -8,19 +8,19 @@ const utils = require('../utils');
*/
module.exports = function shardingPlugin(schema) {
- schema.post('init', function() {
+ schema.post('init', function shardingPluginPostInit() {
storeShard.call(this);
return this;
});
- schema.pre('save', function(next) {
+ schema.pre('save', function shardingPluginPreSave(next) {
applyWhere.call(this);
next();
});
- schema.pre('remove', function(next) {
+ schema.pre('remove', function shardingPluginPreRemove(next) {
applyWhere.call(this);
next();
});
- schema.post('save', function() {
+ schema.post('save', function shardingPluginPostSave() {
storeShard.call(this);
});
};
diff --git a/lib/plugins/trackTransaction.js b/lib/plugins/trackTransaction.js
index 30ded8785f2..1a409a026eb 100644
--- a/lib/plugins/trackTransaction.js
+++ b/lib/plugins/trackTransaction.js
@@ -2,9 +2,10 @@
const arrayAtomicsSymbol = require('../helpers/symbols').arrayAtomicsSymbol;
const sessionNewDocuments = require('../helpers/symbols').sessionNewDocuments;
+const utils = require('../utils');
module.exports = function trackTransaction(schema) {
- schema.pre('save', function() {
+ schema.pre('save', function trackTransactionPreSave() {
const session = this.$session();
if (session == null) {
return;
@@ -22,14 +23,14 @@ module.exports = function trackTransaction(schema) {
initialState.versionKey = this.get(this.$__schema.options.versionKey);
}
- initialState.modifiedPaths = new Set(Object.keys(this.$__.activePaths.states.modify));
+ initialState.modifiedPaths = new Set(Object.keys(this.$__.activePaths.getStatePaths('modify')));
initialState.atomics = _getAtomics(this);
session[sessionNewDocuments].set(this, initialState);
} else {
const state = session[sessionNewDocuments].get(this);
- for (const path of Object.keys(this.$__.activePaths.states.modify)) {
+ for (const path of Object.keys(this.$__.activePaths.getStatePaths('modify'))) {
state.modifiedPaths.add(path);
}
state.atomics = _getAtomics(this, state.atomics);
@@ -46,11 +47,11 @@ function _getAtomics(doc, previous) {
for (const path of pathsToCheck) {
const val = doc.$__getValue(path);
if (val != null &&
- val instanceof Array &&
- val.isMongooseDocumentArray &&
+ Array.isArray(val) &&
+ utils.isMongooseDocumentArray(val) &&
val.length &&
val[arrayAtomicsSymbol] != null &&
- Object.keys(val[arrayAtomicsSymbol]).length > 0) {
+ Object.keys(val[arrayAtomicsSymbol]).length !== 0) {
const existing = previous.get(path) || {};
pathToAtomics.set(path, mergeAtomics(existing, val[arrayAtomicsSymbol]));
}
@@ -61,7 +62,7 @@ function _getAtomics(doc, previous) {
const path = dirt.path;
const val = dirt.value;
- if (val != null && val[arrayAtomicsSymbol] != null && Object.keys(val[arrayAtomicsSymbol]).length > 0) {
+ if (val != null && val[arrayAtomicsSymbol] != null && Object.keys(val[arrayAtomicsSymbol]).length !== 0) {
const existing = previous.get(path) || {};
pathToAtomics.set(path, mergeAtomics(existing, val[arrayAtomicsSymbol]));
}
@@ -88,4 +89,4 @@ function mergeAtomics(destination, source) {
}
return destination;
-}
\ No newline at end of file
+}
diff --git a/lib/plugins/validateBeforeSave.js b/lib/plugins/validateBeforeSave.js
index b9a93b29907..7ebdf4b993f 100644
--- a/lib/plugins/validateBeforeSave.js
+++ b/lib/plugins/validateBeforeSave.js
@@ -4,12 +4,12 @@
* ignore
*/
-module.exports = function(schema) {
+module.exports = function validateBeforeSave(schema) {
const unshift = true;
schema.pre('save', false, function validateBeforeSave(next, options) {
const _this = this;
// Nested docs have their own presave
- if (this.ownerDocument) {
+ if (this.$isSubdocument) {
return next();
}
diff --git a/lib/query.js b/lib/query.js
index 11a16c04232..4dc900f4f78 100644
--- a/lib/query.js
+++ b/lib/query.js
@@ -11,13 +11,14 @@ const MongooseError = require('./error/mongooseError');
const ObjectParameterError = require('./error/objectParameter');
const QueryCursor = require('./cursor/QueryCursor');
const ReadPreference = require('./driver').get().ReadPreference;
-const applyGlobalMaxTimeMS = require('./helpers/query/applyGlobalMaxTimeMS');
+const ValidationError = require('./error/validation');
+const { applyGlobalMaxTimeMS, applyGlobalDiskUse } = require('./helpers/query/applyGlobalOption');
const applyWriteConcern = require('./helpers/schema/applyWriteConcern');
const cast = require('./cast');
const castArrayFilters = require('./helpers/update/castArrayFilters');
+const castNumber = require('./cast/number');
const castUpdate = require('./helpers/query/castUpdate');
const completeMany = require('./helpers/query/completeMany');
-const get = require('./helpers/get');
const promiseOrCallback = require('./helpers/promiseOrCallback');
const getDiscriminatorByValue = require('./helpers/discriminator/getDiscriminatorByValue');
const hasDollarKeys = require('./helpers/query/hasDollarKeys');
@@ -25,6 +26,8 @@ const helpers = require('./queryhelpers');
const immediate = require('./helpers/immediate');
const isExclusive = require('./helpers/projection/isExclusive');
const isInclusive = require('./helpers/projection/isInclusive');
+const isSubpath = require('./helpers/projection/isSubpath');
+const mpath = require('mpath');
const mquery = require('mquery');
const parseProjection = require('./helpers/projection/parseProjection');
const removeUnusedArrayFilters = require('./helpers/update/removeUnusedArrayFilters');
@@ -32,19 +35,44 @@ const sanitizeFilter = require('./helpers/query/sanitizeFilter');
const sanitizeProjection = require('./helpers/query/sanitizeProjection');
const selectPopulatedFields = require('./helpers/query/selectPopulatedFields');
const setDefaultsOnInsert = require('./helpers/setDefaultsOnInsert');
-const slice = require('sliced');
const updateValidators = require('./helpers/updateValidators');
const util = require('util');
const utils = require('./utils');
const validOps = require('./helpers/query/validOps');
const wrapThunk = require('./helpers/query/wrapThunk');
+const queryOptionMethods = new Set([
+ 'allowDiskUse',
+ 'batchSize',
+ 'collation',
+ 'comment',
+ 'explain',
+ 'hint',
+ 'j',
+ 'lean',
+ 'limit',
+ 'maxScan',
+ 'maxTimeMS',
+ 'maxscan',
+ 'populate',
+ 'projection',
+ 'read',
+ 'select',
+ 'skip',
+ 'slice',
+ 'sort',
+ 'tailable',
+ 'w',
+ 'writeConcern',
+ 'wtimeout'
+]);
+
/**
* Query constructor used for building queries. You do not need
* to instantiate a `Query` directly. Instead use Model functions like
- * [`Model.find()`](/docs/api.html#find_find).
+ * [`Model.find()`](/docs/api/model.html#model_Model-find).
*
- * ####Example:
+ * #### Example:
*
* const query = MyModel.find(); // `query` is an instance of `Query`
* query.setOptions({ lean : true });
@@ -97,7 +125,10 @@ function Query(conditions, options, model, collection) {
}
// inherit mquery
- mquery.call(this, this.mongooseCollection, options);
+ mquery.call(this, null, options);
+ if (collection) {
+ this.collection(collection);
+ }
if (conditions) {
this.find(conditions);
@@ -109,7 +140,10 @@ function Query(conditions, options, model, collection) {
// versions of MongoDB
this.$useProjection = true;
- const collation = get(this, 'schema.options.collation', null);
+ const collation = this &&
+ this.schema &&
+ this.schema.options &&
+ this.schema.options.collation || null;
if (collation != null) {
this.options.collation = collation;
}
@@ -119,22 +153,24 @@ function Query(conditions, options, model, collection) {
* inherit mquery
*/
-Query.prototype = new mquery;
+Query.prototype = new mquery();
Query.prototype.constructor = Query;
Query.base = mquery.prototype;
/**
* Flag to opt out of using `$geoWithin`.
*
- * mongoose.Query.use$geoWithin = false;
+ * ```javascript
+ * mongoose.Query.use$geoWithin = false;
+ * ```
*
* MongoDB 2.4 deprecated the use of `$within`, replacing it with `$geoWithin`. Mongoose uses `$geoWithin` by default (which is 100% backward compatible with `$within`). If you are running an older version of MongoDB, set this flag to `false` so your `within()` queries continue to work.
*
- * @see http://docs.mongodb.org/manual/reference/operator/geoWithin/
+ * @see geoWithin https://www.mongodb.com/docs/manual/reference/operator/geoWithin/
* @default true
* @property use$geoWithin
* @memberOf Query
- * @receiver Query
+ * @static
* @api public
*/
@@ -143,7 +179,7 @@ Query.use$geoWithin = mquery.use$geoWithin;
/**
* Converts this query to a customized, reusable query constructor with all arguments and options retained.
*
- * ####Example
+ * #### Example:
*
* // Create a query for adventure movies and read from the primary
* // node in the replica-set unless it is down, in which case we'll
@@ -225,7 +261,8 @@ Query.prototype.toConstructor = function toConstructor() {
/**
* Make a copy of this query so you can re-execute it.
*
- * ####Example:
+ * #### Example:
+ *
* const q = Book.findOne({ title: 'Casino Royale' });
* await q.exec();
* await q.exec(); // Throws an error because you can't execute a query twice
@@ -243,7 +280,7 @@ Query.prototype.clone = function clone() {
const model = this.model;
const collection = this.mongooseCollection;
- const q = new this.constructor({}, {}, model, collection);
+ const q = new this.model.Query({}, {}, model, collection);
// Need to handle `sort()` separately because entries-style `sort()` syntax
// `sort([['prop1', 1]])` confuses mquery into losing the outer nested array.
@@ -273,7 +310,7 @@ Query.prototype.clone = function clone() {
/**
* Specifies a javascript function or expression to pass to MongoDBs query system.
*
- * ####Example
+ * #### Example:
*
* query.$where('this.comments.length === 10 || this.name.length === 5')
*
@@ -283,12 +320,12 @@ Query.prototype.clone = function clone() {
* return this.comments.length === 10 || this.name.length === 5;
* })
*
- * ####NOTE:
+ * #### Note:
*
* Only use `$where` when you have a condition that cannot be met using other MongoDB operators like `$lt`.
- * **Be sure to read about all of [its caveats](http://docs.mongodb.org/manual/reference/operator/where/) before using.**
+ * **Be sure to read about all of [its caveats](https://www.mongodb.com/docs/manual/reference/operator/where/) before using.**
*
- * @see $where http://docs.mongodb.org/manual/reference/operator/where/
+ * @see $where https://www.mongodb.com/docs/manual/reference/operator/where/
* @method $where
* @param {String|Function} js javascript string or function
* @return {Query} this
@@ -301,7 +338,7 @@ Query.prototype.clone = function clone() {
/**
* Specifies a `path` for use with chaining.
*
- * ####Example
+ * #### Example:
*
* // instead of writing:
* User.find({age: {$gte: 21, $lte: 65}}, callback);
@@ -331,22 +368,38 @@ Query.prototype.clone = function clone() {
/**
* Specifies a `$slice` projection for an array.
*
- * ####Example
+ * #### Example:
+ *
+ * query.slice('comments', 5); // Returns the first 5 comments
+ * query.slice('comments', -5); // Returns the last 5 comments
+ * query.slice('comments', [10, 5]); // Returns the first 5 comments after the 10-th
+ * query.where('comments').slice(5); // Returns the first 5 comments
+ * query.where('comments').slice([-10, 5]); // Returns the first 5 comments after the 10-th to last
+ *
+ * **Note:** If the absolute value of the number of elements to be sliced is greater than the number of elements in the array, all array elements will be returned.
+ *
+ * // Given `arr`: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ * query.slice('arr', 20); // Returns [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ * query.slice('arr', -20); // Returns [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ *
+ * **Note:** If the number of elements to skip is positive and greater than the number of elements in the array, an empty array will be returned.
+ *
+ * // Given `arr`: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ * query.slice('arr', [20, 5]); // Returns []
*
- * query.slice('comments', 5)
- * query.slice('comments', -5)
- * query.slice('comments', [10, 5])
- * query.where('comments').slice(5)
- * query.where('comments').slice([-10, 5])
+ * **Note:** If the number of elements to skip is negative and its absolute value is greater than the number of elements in the array, the starting position is the start of the array.
+ *
+ * // Given `arr`: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ * query.slice('arr', [-20, 5]); // Returns [1, 2, 3, 4, 5]
*
* @method slice
* @memberOf Query
* @instance
* @param {String} [path]
- * @param {Number} val number/range of elements to slice
+ * @param {Number|Array} val number of elements to slice or array with number of elements to skip and number of elements to slice
* @return {Query} this
- * @see mongodb http://www.mongodb.org/display/DOCS/Retrieving+a+Subset+of+Fields#RetrievingaSubsetofFields-RetrievingaSubrangeofArrayElements
- * @see $slice http://docs.mongodb.org/manual/reference/projection/slice/#prj._S_slice
+ * @see mongodb https://www.mongodb.com/docs/manual/tutorial/query-documents/#projection
+ * @see $slice https://www.mongodb.com/docs/manual/reference/projection/slice/#prj._S_slice
* @api public
*/
@@ -377,14 +430,14 @@ Query.prototype.slice = function() {
if ('number' === typeof arguments[0]) {
this._ensurePath('slice');
path = this._path;
- val = slice(arguments);
+ val = [arguments[0], arguments[1]];
} else {
path = arguments[0];
val = arguments[1];
}
} else if (arguments.length === 3) {
path = arguments[0];
- val = slice(arguments, 1);
+ val = [arguments[1], arguments[2]];
}
const p = {};
@@ -409,7 +462,7 @@ Query.prototype._validateOp = function() {
/**
* Specifies the complementary comparison value for paths specified with `where()`
*
- * ####Example
+ * #### Example:
*
* User.where('age').equals(49);
*
@@ -428,11 +481,11 @@ Query.prototype._validateOp = function() {
/**
* Specifies arguments for an `$or` condition.
*
- * ####Example
+ * #### Example:
*
- * query.or([{ color: 'red' }, { status: 'emergency' }])
+ * query.or([{ color: 'red' }, { status: 'emergency' }]);
*
- * @see $or http://docs.mongodb.org/manual/reference/operator/or/
+ * @see $or https://www.mongodb.com/docs/manual/reference/operator/or/
* @method or
* @memberOf Query
* @instance
@@ -444,11 +497,11 @@ Query.prototype._validateOp = function() {
/**
* Specifies arguments for a `$nor` condition.
*
- * ####Example
+ * #### Example:
*
- * query.nor([{ color: 'green' }, { status: 'ok' }])
+ * query.nor([{ color: 'green' }, { status: 'ok' }]);
*
- * @see $nor http://docs.mongodb.org/manual/reference/operator/nor/
+ * @see $nor https://www.mongodb.com/docs/manual/reference/operator/nor/
* @method nor
* @memberOf Query
* @instance
@@ -460,14 +513,14 @@ Query.prototype._validateOp = function() {
/**
* Specifies arguments for a `$and` condition.
*
- * ####Example
+ * #### Example:
*
* query.and([{ color: 'green' }, { status: 'ok' }])
*
* @method and
* @memberOf Query
* @instance
- * @see $and http://docs.mongodb.org/manual/reference/operator/and/
+ * @see $and https://www.mongodb.com/docs/manual/reference/operator/and/
* @param {Array} array array of conditions
* @return {Query} this
* @api public
@@ -478,19 +531,19 @@ Query.prototype._validateOp = function() {
*
* When called with one argument, the most recent path passed to `where()` is used.
*
- * ####Example
+ * #### Example:
*
- * Thing.find().where('age').gt(21)
+ * Thing.find().where('age').gt(21);
*
* // or
- * Thing.find().gt('age', 21)
+ * Thing.find().gt('age', 21);
*
* @method gt
* @memberOf Query
* @instance
* @param {String} [path]
* @param {Number} val
- * @see $gt http://docs.mongodb.org/manual/reference/operator/gt/
+ * @see $gt https://www.mongodb.com/docs/manual/reference/operator/gt/
* @api public
*/
@@ -504,7 +557,7 @@ Query.prototype._validateOp = function() {
* @instance
* @param {String} [path]
* @param {Number} val
- * @see $gte http://docs.mongodb.org/manual/reference/operator/gte/
+ * @see $gte https://www.mongodb.com/docs/manual/reference/operator/gte/
* @api public
*/
@@ -518,7 +571,7 @@ Query.prototype._validateOp = function() {
* @instance
* @param {String} [path]
* @param {Number} val
- * @see $lt http://docs.mongodb.org/manual/reference/operator/lt/
+ * @see $lt https://www.mongodb.com/docs/manual/reference/operator/lt/
* @api public
*/
@@ -528,7 +581,7 @@ Query.prototype._validateOp = function() {
* When called with one argument, the most recent path passed to `where()` is used.
*
* @method lte
- * @see $lte http://docs.mongodb.org/manual/reference/operator/lte/
+ * @see $lte https://www.mongodb.com/docs/manual/reference/operator/lte/
* @memberOf Query
* @instance
* @param {String} [path]
@@ -541,7 +594,7 @@ Query.prototype._validateOp = function() {
*
* When called with one argument, the most recent path passed to `where()` is used.
*
- * @see $ne http://docs.mongodb.org/manual/reference/operator/ne/
+ * @see $ne https://www.mongodb.com/docs/manual/reference/operator/ne/
* @method ne
* @memberOf Query
* @instance
@@ -555,7 +608,7 @@ Query.prototype._validateOp = function() {
*
* When called with one argument, the most recent path passed to `where()` is used.
*
- * @see $in http://docs.mongodb.org/manual/reference/operator/in/
+ * @see $in https://www.mongodb.com/docs/manual/reference/operator/in/
* @method in
* @memberOf Query
* @instance
@@ -569,7 +622,7 @@ Query.prototype._validateOp = function() {
*
* When called with one argument, the most recent path passed to `where()` is used.
*
- * @see $nin http://docs.mongodb.org/manual/reference/operator/nin/
+ * @see $nin https://www.mongodb.com/docs/manual/reference/operator/nin/
* @method nin
* @memberOf Query
* @instance
@@ -583,13 +636,13 @@ Query.prototype._validateOp = function() {
*
* When called with one argument, the most recent path passed to `where()` is used.
*
- * ####Example:
+ * #### Example:
*
* MyModel.find().where('pets').all(['dog', 'cat', 'ferret']);
* // Equivalent:
* MyModel.find().all('pets', ['dog', 'cat', 'ferret']);
*
- * @see $all http://docs.mongodb.org/manual/reference/operator/all/
+ * @see $all https://www.mongodb.com/docs/manual/reference/operator/all/
* @method all
* @memberOf Query
* @instance
@@ -603,13 +656,13 @@ Query.prototype._validateOp = function() {
*
* When called with one argument, the most recent path passed to `where()` is used.
*
- * ####Example
+ * #### Example:
*
* const docs = await MyModel.where('tags').size(0).exec();
* assert(Array.isArray(docs));
* console.log('documents with 0 tags', docs);
*
- * @see $size http://docs.mongodb.org/manual/reference/operator/size/
+ * @see $size https://www.mongodb.com/docs/manual/reference/operator/size/
* @method size
* @memberOf Query
* @instance
@@ -623,7 +676,7 @@ Query.prototype._validateOp = function() {
*
* When called with one argument, the most recent path passed to `where()` is used.
*
- * @see $regex http://docs.mongodb.org/manual/reference/operator/regex/
+ * @see $regex https://www.mongodb.com/docs/manual/reference/operator/regex/
* @method regex
* @memberOf Query
* @instance
@@ -637,7 +690,7 @@ Query.prototype._validateOp = function() {
*
* When called with one argument, the most recent path passed to `where()` is used.
*
- * @see $maxDistance http://docs.mongodb.org/manual/reference/operator/maxDistance/
+ * @see $maxDistance https://www.mongodb.com/docs/manual/reference/operator/maxDistance/
* @method maxDistance
* @memberOf Query
* @instance
@@ -650,7 +703,7 @@ Query.prototype._validateOp = function() {
* Specifies a `$mod` condition, filters documents for documents whose
* `path` property is a number that is equal to `remainder` modulo `divisor`.
*
- * ####Example
+ * #### Example:
*
* // All find products whose inventory is odd
* Product.find().mod('inventory', [2, 1]);
@@ -664,7 +717,7 @@ Query.prototype._validateOp = function() {
* @param {String} [path]
* @param {Array} val must be of length 2, first element is `divisor`, 2nd element is `remainder`.
* @return {Query} this
- * @see $mod http://docs.mongodb.org/manual/reference/operator/mod/
+ * @see $mod https://www.mongodb.com/docs/manual/reference/operator/mod/
* @api public
*/
@@ -678,10 +731,10 @@ Query.prototype.mod = function() {
path = this._path;
} else if (arguments.length === 2 && !Array.isArray(arguments[1])) {
this._ensurePath('mod');
- val = slice(arguments);
+ val = [arguments[0], arguments[1]];
path = this._path;
} else if (arguments.length === 3) {
- val = slice(arguments, 1);
+ val = [arguments[1], arguments[2]];
path = arguments[0];
} else {
val = arguments[1];
@@ -696,7 +749,7 @@ Query.prototype.mod = function() {
/**
* Specifies an `$exists` condition
*
- * ####Example
+ * #### Example:
*
* // { name: { $exists: true }}
* Thing.where('name').exists()
@@ -713,14 +766,14 @@ Query.prototype.mod = function() {
* @param {String} [path]
* @param {Boolean} val
* @return {Query} this
- * @see $exists http://docs.mongodb.org/manual/reference/operator/exists/
+ * @see $exists https://www.mongodb.com/docs/manual/reference/operator/exists/
* @api public
*/
/**
* Specifies an `$elemMatch` condition
*
- * ####Example
+ * #### Example:
*
* query.elemMatch('comment', { author: 'autobot', votes: {$gte: 5}})
*
@@ -742,14 +795,14 @@ Query.prototype.mod = function() {
* @param {String|Object|Function} path
* @param {Object|Function} filter
* @return {Query} this
- * @see $elemMatch http://docs.mongodb.org/manual/reference/operator/elemMatch/
+ * @see $elemMatch https://www.mongodb.com/docs/manual/reference/operator/elemMatch/
* @api public
*/
/**
* Defines a `$within` or `$geoWithin` argument for geo-spatial queries.
*
- * ####Example
+ * #### Example:
*
* query.where(path).within().box()
* query.where(path).within().circle()
@@ -765,20 +818,20 @@ Query.prototype.mod = function() {
*
* **MUST** be used after `where()`.
*
- * ####NOTE:
+ * #### Note:
*
* As of Mongoose 3.7, `$geoWithin` is always used for queries. To change this behavior, see [Query.use$geoWithin](#query_Query-use%2524geoWithin).
*
- * ####NOTE:
+ * #### Note:
*
* In Mongoose 3.7, `within` changed from a getter to a function. If you need the old syntax, use [this](https://github.com/ebensing/mongoose-within).
*
* @method within
- * @see $polygon http://docs.mongodb.org/manual/reference/operator/polygon/
- * @see $box http://docs.mongodb.org/manual/reference/operator/box/
- * @see $geometry http://docs.mongodb.org/manual/reference/operator/geometry/
- * @see $center http://docs.mongodb.org/manual/reference/operator/center/
- * @see $centerSphere http://docs.mongodb.org/manual/reference/operator/centerSphere/
+ * @see $polygon https://www.mongodb.com/docs/manual/reference/operator/polygon/
+ * @see $box https://www.mongodb.com/docs/manual/reference/operator/box/
+ * @see $geometry https://www.mongodb.com/docs/manual/reference/operator/geometry/
+ * @see $center https://www.mongodb.com/docs/manual/reference/operator/center/
+ * @see $centerSphere https://www.mongodb.com/docs/manual/reference/operator/centerSphere/
* @memberOf Query
* @instance
* @return {Query} this
@@ -788,11 +841,11 @@ Query.prototype.mod = function() {
/**
* Specifies the maximum number of documents the query will return.
*
- * ####Example
+ * #### Example:
*
- * query.limit(20)
+ * query.limit(20);
*
- * ####Note
+ * #### Note:
*
* Cannot be used with `distinct()`
*
@@ -803,14 +856,29 @@ Query.prototype.mod = function() {
* @api public
*/
+Query.prototype.limit = function limit(v) {
+ this._validate('limit');
+
+ if (typeof v === 'string') {
+ try {
+ v = castNumber(v);
+ } catch (err) {
+ throw new CastError('Number', v, 'limit');
+ }
+ }
+
+ this.options.limit = v;
+ return this;
+};
+
/**
* Specifies the number of documents to skip.
*
- * ####Example
+ * #### Example:
*
- * query.skip(100).limit(20)
+ * query.skip(100).limit(20);
*
- * ####Note
+ * #### Note:
*
* Cannot be used with `distinct()`
*
@@ -818,18 +886,33 @@ Query.prototype.mod = function() {
* @memberOf Query
* @instance
* @param {Number} val
- * @see cursor.skip http://docs.mongodb.org/manual/reference/method/cursor.skip/
+ * @see cursor.skip https://www.mongodb.com/docs/manual/reference/method/cursor.skip/
* @api public
*/
+Query.prototype.skip = function skip(v) {
+ this._validate('skip');
+
+ if (typeof v === 'string') {
+ try {
+ v = castNumber(v);
+ } catch (err) {
+ throw new CastError('Number', v, 'skip');
+ }
+ }
+
+ this.options.skip = v;
+ return this;
+};
+
/**
* Specifies the maxScan option.
*
- * ####Example
+ * #### Example:
*
- * query.maxScan(100)
+ * query.maxScan(100);
*
- * ####Note
+ * #### Note:
*
* Cannot be used with `distinct()`
*
@@ -837,18 +920,18 @@ Query.prototype.mod = function() {
* @memberOf Query
* @instance
* @param {Number} val
- * @see maxScan http://docs.mongodb.org/manual/reference/operator/maxScan/
+ * @see maxScan https://www.mongodb.com/docs/v4.0/reference/method/cursor.maxScan/
* @api public
*/
/**
* Specifies the batchSize option.
*
- * ####Example
+ * #### Example:
*
* query.batchSize(100)
*
- * ####Note
+ * #### Note:
*
* Cannot be used with `distinct()`
*
@@ -856,18 +939,18 @@ Query.prototype.mod = function() {
* @memberOf Query
* @instance
* @param {Number} val
- * @see batchSize http://docs.mongodb.org/manual/reference/method/cursor.batchSize/
+ * @see batchSize https://www.mongodb.com/docs/manual/reference/method/cursor.batchSize/
* @api public
*/
/**
* Specifies the `comment` option.
*
- * ####Example
+ * #### Example:
*
* query.comment('login query')
*
- * ####Note
+ * #### Note:
*
* Cannot be used with `distinct()`
*
@@ -875,27 +958,26 @@ Query.prototype.mod = function() {
* @memberOf Query
* @instance
* @param {String} val
- * @see comment http://docs.mongodb.org/manual/reference/operator/comment/
+ * @see comment https://www.mongodb.com/docs/manual/reference/operator/comment/
* @api public
*/
/**
* Specifies this query as a `snapshot` query.
*
- * ####Example
+ * #### Example:
*
- * query.snapshot() // true
- * query.snapshot(true)
- * query.snapshot(false)
+ * query.snapshot(); // true
+ * query.snapshot(true);
+ * query.snapshot(false);
*
- * ####Note
+ * #### Note:
*
* Cannot be used with `distinct()`
*
* @method snapshot
* @memberOf Query
* @instance
- * @see snapshot http://docs.mongodb.org/manual/reference/operator/snapshot/
* @return {Query} this
* @api public
*/
@@ -903,11 +985,11 @@ Query.prototype.mod = function() {
/**
* Sets query hints.
*
- * ####Example
+ * #### Example:
*
- * query.hint({ indexA: 1, indexB: -1})
+ * query.hint({ indexA: 1, indexB: -1 });
*
- * ####Note
+ * #### Note:
*
* Cannot be used with `distinct()`
*
@@ -916,7 +998,7 @@ Query.prototype.mod = function() {
* @instance
* @param {Object} val a hint object
* @return {Query} this
- * @see $hint http://docs.mongodb.org/manual/reference/operator/hint/
+ * @see $hint https://www.mongodb.com/docs/manual/reference/operator/hint/
* @api public
*/
@@ -927,7 +1009,7 @@ Query.prototype.mod = function() {
* Unlike `projection()`, the `select()` function modifies the current
* projection in place. This function overwrites the existing projection.
*
- * ####Example:
+ * #### Example:
*
* const q = Model.find();
* q.projection(); // null
@@ -964,13 +1046,13 @@ Query.prototype.projection = function(arg) {
/**
* Specifies which document fields to include or exclude (also known as the query "projection")
*
- * When using string syntax, prefixing a path with `-` will flag that path as excluded. When a path does not have the `-` prefix, it is included. Lastly, if a path is prefixed with `+`, it forces inclusion of the path, which is useful for paths excluded at the [schema level](/docs/api.html#schematype_SchemaType-select).
+ * When using string syntax, prefixing a path with `-` will flag that path as excluded. When a path does not have the `-` prefix, it is included. Lastly, if a path is prefixed with `+`, it forces inclusion of the path, which is useful for paths excluded at the [schema level](/docs/api/schematype.html#schematype_SchemaType-select).
*
* A projection _must_ be either inclusive or exclusive. In other words, you must
* either list the fields to include (which excludes all others), or list the fields
- * to exclude (which implies all other fields are included). The [`_id` field is the only exception because MongoDB includes it by default](https://docs.mongodb.com/manual/tutorial/project-fields-from-query-results/#suppress-id-field).
+ * to exclude (which implies all other fields are included). The [`_id` field is the only exception because MongoDB includes it by default](https://www.mongodb.com/docs/manual/tutorial/project-fields-from-query-results/#suppress-id-field).
*
- * ####Example
+ * #### Example:
*
* // include a and b, exclude other fields
* query.select('a b');
@@ -995,13 +1077,17 @@ Query.prototype.projection = function(arg) {
* query.select({ a: 1, b: 1 });
* query.select({ c: 0, d: 0 });
*
+ * Additional calls to select can override the previous selection:
+ * query.select({ a: 1, b: 1 }).select({ b: 0 }); // selection is now { a: 1 }
+ * query.select({ a: 0, b: 0 }).select({ b: 1 }); // selection is now { a: 0 }
+ *
*
* @method select
* @memberOf Query
* @instance
- * @param {Object|String|Array<String>} arg
+ * @param {Object|String|String[]} arg
* @return {Query} this
- * @see SchemaType
+ * @see SchemaType /docs/api/schematype.html
* @api public
*/
@@ -1026,17 +1112,50 @@ Query.prototype.select = function select() {
sanitizeProjection = this._mongooseOptions.sanitizeProjection;
}
+ function sanitizeValue(value) {
+ return typeof value === 'string' && sanitizeProjection ? value = 1 : value;
+ }
+
arg = parseProjection(arg);
if (utils.isObject(arg)) {
- const keys = Object.keys(arg);
- for (let i = 0; i < keys.length; ++i) {
- let value = arg[keys[i]];
- if (typeof value === 'string' && sanitizeProjection) {
- value = 1;
+ if (this.selectedInclusively()) {
+ Object.entries(arg).forEach(([key, value]) => {
+ if (value) {
+ // Add the field to the projection
+ fields[key] = userProvidedFields[key] = sanitizeValue(value);
+ } else {
+ // Remove the field from the projection
+ Object.keys(userProvidedFields).forEach(field => {
+ if (isSubpath(key, field)) {
+ delete fields[field];
+ delete userProvidedFields[field];
+ }
+ });
+ }
+ });
+ } else if (this.selectedExclusively()) {
+ Object.entries(arg).forEach(([key, value]) => {
+ if (!value) {
+ // Add the field to the projection
+ fields[key] = userProvidedFields[key] = sanitizeValue(value);
+ } else {
+ // Remove the field from the projection
+ Object.keys(userProvidedFields).forEach(field => {
+ if (isSubpath(key, field)) {
+ delete fields[field];
+ delete userProvidedFields[field];
+ }
+ });
+ }
+ });
+ } else {
+ const keys = Object.keys(arg);
+ for (let i = 0; i < keys.length; ++i) {
+ const value = arg[keys[i]];
+ fields[keys[i]] = sanitizeValue(value);
+ userProvidedFields[keys[i]] = sanitizeValue(value);
}
- fields[keys[i]] = value;
- userProvidedFields[keys[i]] = value;
}
return this;
}
@@ -1044,49 +1163,30 @@ Query.prototype.select = function select() {
throw new TypeError('Invalid select() argument. Must be string or object.');
};
-/**
- * _DEPRECATED_ Sets the slaveOk option.
- *
- * **Deprecated** in MongoDB 2.2 in favor of [read preferences](#query_Query-read).
- *
- * ####Example:
- *
- * query.slaveOk() // true
- * query.slaveOk(true)
- * query.slaveOk(false)
- *
- * @method slaveOk
- * @memberOf Query
- * @instance
- * @deprecated use read() preferences instead if on mongodb >= 2.2
- * @param {Boolean} v defaults to true
- * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference
- * @see slaveOk http://docs.mongodb.org/manual/reference/method/rs.slaveOk/
- * @see read() #query_Query-read
- * @return {Query} this
- * @api public
- */
-
/**
* Determines the MongoDB nodes from which to read.
*
- * ####Preferences:
+ * #### Preferences:
*
- * primary - (default) Read from primary only. Operations will produce an error if primary is unavailable. Cannot be combined with tags.
- * secondary Read from secondary if available, otherwise error.
- * primaryPreferred Read from primary if available, otherwise a secondary.
- * secondaryPreferred Read from a secondary if available, otherwise read from the primary.
- * nearest All operations read from among the nearest candidates, but unlike other modes, this option will include both the primary and all secondaries in the random selection.
+ * ```
+ * primary - (default) Read from primary only. Operations will produce an error if primary is unavailable. Cannot be combined with tags.
+ * secondary Read from secondary if available, otherwise error.
+ * primaryPreferred Read from primary if available, otherwise a secondary.
+ * secondaryPreferred Read from a secondary if available, otherwise read from the primary.
+ * nearest All operations read from among the nearest candidates, but unlike other modes, this option will include both the primary and all secondaries in the random selection.
+ * ```
*
* Aliases
*
- * p primary
- * pp primaryPreferred
- * s secondary
- * sp secondaryPreferred
- * n nearest
+ * ```
+ * p primary
+ * pp primaryPreferred
+ * s secondary
+ * sp secondaryPreferred
+ * n nearest
+ * ```
*
- * ####Example:
+ * #### Example:
*
* new Query().read('primary')
* new Query().read('p') // same as primary
@@ -1106,15 +1206,14 @@ Query.prototype.select = function select() {
* // read from secondaries with matching tags
* new Query().read('s', [{ dc:'sf', s: 1 },{ dc:'ma', s: 2 }])
*
- * Read more about how to use read preferences [here](http://docs.mongodb.org/manual/applications/replication/#read-preference) and [here](http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences).
+ * Read more about how to use read preferences [here](https://www.mongodb.com/docs/manual/applications/replication/#read-preference).
*
* @method read
* @memberOf Query
* @instance
* @param {String} pref one of the listed preference options or aliases
* @param {Array} [tags] optional tags for this query
- * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference
- * @see driver http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences
+ * @see mongodb https://www.mongodb.com/docs/manual/applications/replication/#read-preference
* @return {Query} this
* @api public
*/
@@ -1126,8 +1225,13 @@ Query.prototype.read = function read(pref, tags) {
return this;
};
-/*!
- * ignore
+/**
+ * Overwrite default `.toString` to make logging more useful
+ *
+ * @memberOf Query
+ * @instance
+ * @method toString
+ * @api private
*/
Query.prototype.toString = function toString() {
@@ -1159,13 +1263,13 @@ Query.prototype.toString = function toString() {
};
/**
- * Sets the [MongoDB session](https://docs.mongodb.com/manual/reference/server-sessions/)
+ * Sets the [MongoDB session](https://www.mongodb.com/docs/manual/reference/server-sessions/)
* associated with this query. Sessions are how you mark a query as part of a
* [transaction](/docs/transactions.html).
*
* Calling `session(null)` removes the session from this query.
*
- * ####Example:
+ * #### Example:
*
* const s = await mongoose.startSession();
* await mongoose.model('Person').findOne({ name: 'Axl Rose' }).session(s);
@@ -1174,8 +1278,8 @@ Query.prototype.toString = function toString() {
* @memberOf Query
* @instance
* @param {ClientSession} [session] from `await conn.startSession()`
- * @see Connection.prototype.startSession() /docs/api.html#connection_Connection-startSession
- * @see mongoose.startSession() /docs/api.html#mongoose_Mongoose-startSession
+ * @see Connection.prototype.startSession() /docs/api/connection.html#connection_Connection-startSession
+ * @see mongoose.startSession() /docs/api/mongoose.html#mongoose_Mongoose-startSession
* @return {Query} this
* @api public
*/
@@ -1193,7 +1297,7 @@ Query.prototype.session = function session(v) {
*
* - `w`: Sets the specified number of `mongod` servers, or tag set of `mongod` servers, that must acknowledge this write before this write is considered successful.
* - `j`: Boolean, set to `true` to request acknowledgement that this operation has been persisted to MongoDB's on-disk journal.
- * - `wtimeout`: If [`w > 1`](/docs/api.html#query_Query-w), the maximum amount of time to wait for this write to propagate through the replica set before this operation fails. The default is `0`, which means no timeout.
+ * - `wtimeout`: If [`w > 1`](#query_Query-w), the maximum amount of time to wait for this write to propagate through the replica set before this operation fails. The default is `0`, which means no timeout.
*
* This option is only valid for operations that write to the database:
*
@@ -1209,7 +1313,7 @@ Query.prototype.session = function session(v) {
*
* Defaults to the schema's [`writeConcern` option](/docs/guide.html#writeConcern)
*
- * ####Example:
+ * #### Example:
*
* // The 'majority' option means the `deleteOne()` promise won't resolve
* // until the `deleteOne()` has propagated to the majority of the replica set
@@ -1221,7 +1325,7 @@ Query.prototype.session = function session(v) {
* @memberOf Query
* @instance
* @param {Object} writeConcern the write concern value to set
- * @see mongodb https://mongodb.github.io/node-mongodb-native/3.1/api/global.html#WriteConcern
+ * @see WriteConcernSettings https://mongodb.github.io/node-mongodb-native/4.9/interfaces/WriteConcernSettings.html
* @return {Query} this
* @api public
*/
@@ -1252,7 +1356,7 @@ Query.prototype.writeConcern = function writeConcern(val) {
*
* Defaults to the schema's [`writeConcern.w` option](/docs/guide.html#writeConcern)
*
- * ####Example:
+ * #### Example:
*
* // The 'majority' option means the `deleteOne()` promise won't resolve
* // until the `deleteOne()` has propagated to the majority of the replica set
@@ -1263,8 +1367,8 @@ Query.prototype.writeConcern = function writeConcern(val) {
* @method w
* @memberOf Query
* @instance
- * @param {String|number} val 0 for fire-and-forget, 1 for acknowledged by one server, 'majority' for majority of the replica set, or [any of the more advanced options](https://docs.mongodb.com/manual/reference/write-concern/#w-option).
- * @see mongodb https://docs.mongodb.com/manual/reference/write-concern/#w-option
+ * @param {String|number} val 0 for fire-and-forget, 1 for acknowledged by one server, 'majority' for majority of the replica set, or [any of the more advanced options](https://www.mongodb.com/docs/manual/reference/write-concern/#w-option).
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/write-concern/#w-option
* @return {Query} this
* @api public
*/
@@ -1298,7 +1402,7 @@ Query.prototype.w = function w(val) {
*
* Defaults to the schema's [`writeConcern.j` option](/docs/guide.html#writeConcern)
*
- * ####Example:
+ * #### Example:
*
* await mongoose.model('Person').deleteOne({ name: 'Ned Stark' }).j(true);
*
@@ -1306,7 +1410,7 @@ Query.prototype.w = function w(val) {
* @memberOf Query
* @instance
* @param {boolean} val
- * @see mongodb https://docs.mongodb.com/manual/reference/write-concern/#j-option
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/write-concern/#j-option
* @return {Query} this
* @api public
*/
@@ -1324,7 +1428,7 @@ Query.prototype.j = function j(val) {
};
/**
- * If [`w > 1`](/docs/api.html#query_Query-w), the maximum amount of time to
+ * If [`w > 1`](#query_Query-w), the maximum amount of time to
* wait for this write to propagate through the replica set before this
* operation fails. The default is `0`, which means no timeout.
*
@@ -1342,7 +1446,7 @@ Query.prototype.j = function j(val) {
*
* Defaults to the schema's [`writeConcern.wtimeout` option](/docs/guide.html#writeConcern)
*
- * ####Example:
+ * #### Example:
*
* // The `deleteOne()` promise won't resolve until this `deleteOne()` has
* // propagated to at least `w = 2` members of the replica set. If it takes
@@ -1356,7 +1460,7 @@ Query.prototype.j = function j(val) {
* @memberOf Query
* @instance
* @param {number} ms number of milliseconds to wait
- * @see mongodb https://docs.mongodb.com/manual/reference/write-concern/#wtimeout
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout
* @return {Query} this
* @api public
*/
@@ -1376,7 +1480,7 @@ Query.prototype.wtimeout = function wtimeout(ms) {
/**
* Sets the readConcern option for the query.
*
- * ####Example:
+ * #### Example:
*
* new Query().readConcern('local')
* new Query().readConcern('l') // same as local
@@ -1394,28 +1498,32 @@ Query.prototype.wtimeout = function wtimeout(ms) {
* new Query().readConcern('s') // same as snapshot
*
*
- * ####Read Concern Level:
+ * #### Read Concern Level:
*
- * local MongoDB 3.2+ The query returns from the instance with no guarantee guarantee that the data has been written to a majority of the replica set members (i.e. may be rolled back).
- * available MongoDB 3.6+ The query returns from the instance with no guarantee guarantee that the data has been written to a majority of the replica set members (i.e. may be rolled back).
- * majority MongoDB 3.2+ The query returns the data that has been acknowledged by a majority of the replica set members. The documents returned by the read operation are durable, even in the event of failure.
- * linearizable MongoDB 3.4+ The query returns data that reflects all successful majority-acknowledged writes that completed prior to the start of the read operation. The query may wait for concurrently executing writes to propagate to a majority of replica set members before returning results.
- * snapshot MongoDB 4.0+ Only available for operations within multi-document transactions. Upon transaction commit with write concern "majority", the transaction operations are guaranteed to have read from a snapshot of majority-committed data.
+ * ```
+ * local MongoDB 3.2+ The query returns from the instance with no guarantee guarantee that the data has been written to a majority of the replica set members (i.e. may be rolled back).
+ * available MongoDB 3.6+ The query returns from the instance with no guarantee guarantee that the data has been written to a majority of the replica set members (i.e. may be rolled back).
+ * majority MongoDB 3.2+ The query returns the data that has been acknowledged by a majority of the replica set members. The documents returned by the read operation are durable, even in the event of failure.
+ * linearizable MongoDB 3.4+ The query returns data that reflects all successful majority-acknowledged writes that completed prior to the start of the read operation. The query may wait for concurrently executing writes to propagate to a majority of replica set members before returning results.
+ * snapshot MongoDB 4.0+ Only available for operations within multi-document transactions. Upon transaction commit with write concern "majority", the transaction operations are guaranteed to have read from a snapshot of majority-committed data.
+ * ```
*
* Aliases
*
- * l local
- * a available
- * m majority
- * lz linearizable
- * s snapshot
+ * ```
+ * l local
+ * a available
+ * m majority
+ * lz linearizable
+ * s snapshot
+ * ```
*
- * Read more about how to use read concern [here](https://docs.mongodb.com/manual/reference/read-concern/).
+ * Read more about how to use read concern [here](https://www.mongodb.com/docs/manual/reference/read-concern/).
*
* @memberOf Query
* @method readConcern
* @param {String} level one of the listed read concern level or their aliases
- * @see mongodb https://docs.mongodb.com/manual/reference/read-concern/
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/read-concern/
* @return {Query} this
* @api public
*/
@@ -1423,11 +1531,11 @@ Query.prototype.wtimeout = function wtimeout(ms) {
/**
* Gets query options.
*
- * ####Example:
+ * #### Example:
*
* const query = new Query();
* query.limit(10);
- * query.setOptions({ maxTimeMS: 1000 })
+ * query.setOptions({ maxTimeMS: 1000 });
* query.getOptions(); // { limit: 10, maxTimeMS: 1000 }
*
* @return {Object} the options
@@ -1441,40 +1549,39 @@ Query.prototype.getOptions = function() {
/**
* Sets query options. Some options only make sense for certain operations.
*
- * ####Options:
+ * #### Options:
*
* The following options are only for `find()`:
*
- * - [tailable](http://www.mongodb.org/display/DOCS/Tailable+Cursors)
- * - [sort](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsort(\)%7D%7D)
- * - [limit](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Blimit%28%29%7D%7D)
- * - [skip](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bskip%28%29%7D%7D)
- * - [allowDiskUse](https://docs.mongodb.com/manual/reference/method/cursor.allowDiskUse/)
- * - [batchSize](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7BbatchSize%28%29%7D%7D)
- * - [readPreference](http://docs.mongodb.org/manual/applications/replication/#read-preference)
- * - [hint](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24hint)
- * - [comment](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24comment)
- * - [snapshot](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsnapshot%28%29%7D%7D)
- * - [maxscan](https://docs.mongodb.org/v3.2/reference/operator/meta/maxScan/#metaOp._S_maxScan)
+ * - [tailable](https://www.mongodb.com/docs/manual/core/tailable-cursors/)
+ * - [sort](https://www.mongodb.com/docs/manual/reference/method/cursor.sort/)
+ * - [limit](https://www.mongodb.com/docs/manual/reference/method/cursor.limit/)
+ * - [skip](https://www.mongodb.com/docs/manual/reference/method/cursor.skip/)
+ * - [allowDiskUse](https://www.mongodb.com/docs/manual/reference/method/cursor.allowDiskUse/)
+ * - [batchSize](https://www.mongodb.com/docs/manual/reference/method/cursor.batchSize/)
+ * - [readPreference](https://www.mongodb.com/docs/manual/applications/replication/#read-preference)
+ * - [hint](https://www.mongodb.com/docs/manual/reference/method/cursor.hint/)
+ * - [comment](https://www.mongodb.com/docs/manual/reference/method/cursor.comment/)
+ * - snapshot
+ * - [maxscan](https://www.mongodb.com/docs/v4.0/reference/method/cursor.maxScan/)
*
* The following options are only for write operations: `update()`, `updateOne()`, `updateMany()`, `replaceOne()`, `findOneAndUpdate()`, and `findByIdAndUpdate()`:
*
- * - [upsert](https://docs.mongodb.com/manual/reference/method/db.collection.update/)
- * - [writeConcern](https://docs.mongodb.com/manual/reference/method/db.collection.update/)
+ * - [upsert](https://www.mongodb.com/docs/manual/reference/method/db.collection.update/)
+ * - [writeConcern](https://www.mongodb.com/docs/manual/reference/method/db.collection.update/)
* - [timestamps](https://mongoosejs.com/docs/guide.html#timestamps): If `timestamps` is set in the schema, set this option to `false` to skip timestamps for that particular update. Has no effect if `timestamps` is not enabled in the schema options.
- * - omitUndefined: delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
* - overwriteDiscriminatorKey: allow setting the discriminator key in the update. Will use the correct discriminator schema if the update changes the discriminator key.
*
* The following options are only for `find()`, `findOne()`, `findById()`, `findOneAndUpdate()`, and `findByIdAndUpdate()`:
*
- * - [lean](./api.html#query_Query-lean)
+ * - [lean](#query_Query-lean)
* - [populate](/docs/populate.html)
- * - [projection](/docs/api/query.html#query_Query-projection)
+ * - [projection](#query_Query-projection)
* - sanitizeProjection
*
* The following options are only for all operations **except** `update()`, `updateOne()`, `updateMany()`, `remove()`, `deleteOne()`, and `deleteMany()`:
*
- * - [maxTimeMS](https://docs.mongodb.com/manual/reference/operator/meta/maxTimeMS/)
+ * - [maxTimeMS](https://www.mongodb.com/docs/manual/reference/operator/meta/maxTimeMS/)
*
* The following options are for `findOneAndUpdate()` and `findOneAndRemove()`
*
@@ -1483,9 +1590,9 @@ Query.prototype.getOptions = function() {
* The following options are for all operations:
*
* - [strict](/docs/guide.html#strict)
- * - [collation](https://docs.mongodb.com/manual/reference/collation/)
- * - [session](https://docs.mongodb.com/manual/reference/server-sessions/)
- * - [explain](https://docs.mongodb.com/manual/reference/method/cursor.explain/)
+ * - [collation](https://www.mongodb.com/docs/manual/reference/collation/)
+ * - [session](https://www.mongodb.com/docs/manual/reference/server-sessions/)
+ * - [explain](https://www.mongodb.com/docs/manual/reference/method/cursor.explain/)
*
* @param {Object} options
* @return {Query} this
@@ -1511,6 +1618,8 @@ Query.prototype.setOptions = function(options, overwrite) {
throw new Error('Options must be an object, got "' + options + '"');
}
+ options = Object.assign({}, options);
+
if (Array.isArray(options.populate)) {
const populate = options.populate;
delete options.populate;
@@ -1545,19 +1654,49 @@ Query.prototype.setOptions = function(options, overwrite) {
this._mongooseOptions.defaults = options.defaults;
// deleting options.defaults will cause 7287 to fail
}
+ if (options.lean == null && this.schema && 'lean' in this.schema.options) {
+ this._mongooseOptions.lean = this.schema.options.lean;
+ }
+
+ if (typeof options.limit === 'string') {
+ try {
+ options.limit = castNumber(options.limit);
+ } catch (err) {
+ throw new CastError('Number', options.limit, 'limit');
+ }
+ }
+ if (typeof options.skip === 'string') {
+ try {
+ options.skip = castNumber(options.skip);
+ } catch (err) {
+ throw new CastError('Number', options.skip, 'skip');
+ }
+ }
+
+ // set arbitrary options
+ for (const key of Object.keys(options)) {
+ if (queryOptionMethods.has(key)) {
+ const args = Array.isArray(options[key]) ?
+ options[key] :
+ [options[key]];
+ this[key].apply(this, args);
+ } else {
+ this.options[key] = options[key];
+ }
+ }
- return Query.base.setOptions.call(this, options);
+ return this;
};
/**
- * Sets the [`explain` option](https://docs.mongodb.com/manual/reference/method/cursor.explain/),
+ * Sets the [`explain` option](https://www.mongodb.com/docs/manual/reference/method/cursor.explain/),
* which makes this query return detailed execution stats instead of the actual
* query result. This method is useful for determining what index your queries
* use.
*
* Calling `query.explain(v)` is equivalent to `query.setOptions({ explain: v })`
*
- * ####Example:
+ * #### Example:
*
* const query = new Query();
* const res = await query.find({ a: 1 }).explain('queryPlanner');
@@ -1580,7 +1719,7 @@ Query.prototype.explain = function(verbose) {
};
/**
- * Sets the [`allowDiskUse` option](https://docs.mongodb.com/manual/reference/method/cursor.allowDiskUse/),
+ * Sets the [`allowDiskUse` option](https://www.mongodb.com/docs/manual/reference/method/cursor.allowDiskUse/),
* which allows the MongoDB server to use more than 100 MB for this query's `sort()`. This option can
* let you work around `QueryExceededMemoryLimitNoDiskUseAllowed` errors from the MongoDB server.
*
@@ -1589,7 +1728,7 @@ Query.prototype.explain = function(verbose) {
*
* Calling `query.allowDiskUse(v)` is equivalent to `query.setOptions({ allowDiskUse: v })`
*
- * ####Example:
+ * #### Example:
*
* await query.find().sort({ name: 1 }).allowDiskUse(true);
* // Equivalent:
@@ -1612,13 +1751,13 @@ Query.prototype.allowDiskUse = function(v) {
};
/**
- * Sets the [maxTimeMS](https://docs.mongodb.com/manual/reference/method/cursor.maxTimeMS/)
+ * Sets the [maxTimeMS](https://www.mongodb.com/docs/manual/reference/method/cursor.maxTimeMS/)
* option. This will tell the MongoDB server to abort if the query or write op
* has been running for more than `ms` milliseconds.
*
* Calling `query.maxTimeMS(v)` is equivalent to `query.setOptions({ maxTimeMS: v })`
*
- * ####Example:
+ * #### Example:
*
* const query = new Query();
* // Throws an error 'operation exceeded time limit' as long as there's
@@ -1638,7 +1777,7 @@ Query.prototype.maxTimeMS = function(ms) {
/**
* Returns the current query filter (also known as conditions) as a [POJO](https://masteringjs.io/tutorials/fundamentals/pojo).
*
- * ####Example:
+ * #### Example:
*
* const query = new Query();
* query.find({ a: 1 }).where('b').gt(2);
@@ -1658,7 +1797,7 @@ Query.prototype.getFilter = function() {
* You should use `getFilter()` instead of `getQuery()` where possible. `getQuery()`
* will likely be deprecated in a future release.
*
- * ####Example:
+ * #### Example:
*
* const query = new Query();
* query.find({ a: 1 }).where('b').gt(2);
@@ -1675,7 +1814,7 @@ Query.prototype.getQuery = function() {
/**
* Sets the query conditions to the provided JSON object.
*
- * ####Example:
+ * #### Example:
*
* const query = new Query();
* query.find({ a: 1 })
@@ -1694,7 +1833,7 @@ Query.prototype.setQuery = function(val) {
/**
* Returns the current update operations as a JSON object.
*
- * ####Example:
+ * #### Example:
*
* const query = new Query();
* query.update({}, { $set: { a: 5 } });
@@ -1711,7 +1850,7 @@ Query.prototype.getUpdate = function() {
/**
* Sets the current update operation to new value.
*
- * ####Example:
+ * #### Example:
*
* const query = new Query();
* query.update({}, { $set: { a: 5 } });
@@ -1733,10 +1872,16 @@ Query.prototype.setUpdate = function(val) {
* @method _fieldsForExec
* @return {Object}
* @api private
- * @receiver Query
+ * @memberOf Query
*/
Query.prototype._fieldsForExec = function() {
+ if (this._fields == null) {
+ return null;
+ }
+ if (Object.keys(this._fields).length === 0) {
+ return null;
+ }
return utils.clone(this._fields);
};
@@ -1745,8 +1890,9 @@ Query.prototype._fieldsForExec = function() {
* Return an update document with corrected `$set` operations.
*
* @method _updateForExec
+ * @return {Object}
* @api private
- * @receiver Query
+ * @memberOf Query
*/
Query.prototype._updateForExec = function() {
@@ -1793,10 +1939,12 @@ Query.prototype._updateForExec = function() {
/**
* Makes sure _path is set.
*
+ * This method is inherited by `mquery`
+ *
* @method _ensurePath
* @param {String} method
* @api private
- * @receiver Query
+ * @memberOf Query
*/
/**
@@ -1829,7 +1977,10 @@ Query.prototype.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment