Skip to content

Commit

Permalink
Merge pull request #14423 from Automattic/vkarpov15/gh-14400
Browse files Browse the repository at this point in the history
types(model): make `bulkWrite()` types more flexible to account for casting
  • Loading branch information
vkarpov15 committed Mar 11, 2024
2 parents cce65d1 + 7835396 commit 3ba9778
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 12 deletions.
14 changes: 12 additions & 2 deletions lib/model.js
Expand Up @@ -3375,6 +3375,7 @@ function _setIsNew(doc, val) {
* trip to MongoDB.
*
* Mongoose will perform casting on all operations you provide.
* The only exception is [setting the `update` operator for `updateOne` or `updateMany` to a pipeline](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#updateone-and-updatemany): Mongoose does **not** cast update pipelines.
*
* This function does **not** trigger any middleware, neither `save()`, nor `update()`.
* If you need to trigger
Expand Down Expand Up @@ -3410,6 +3411,15 @@ function _setIsNew(doc, val) {
* console.log(res.insertedCount, res.modifiedCount, res.deletedCount);
* });
*
* // Mongoose does **not** cast update pipelines, so no casting for the `update` option below.
* // Mongoose does still cast `filter`
* await Character.bulkWrite([{
* updateOne: {
* filter: { name: 'Annika Hansen' },
* update: [{ $set: { name: 7 } }] // Array means update pipeline, so Mongoose skips casting
* }
* }]);
*
* The [supported operations](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#db.collection.bulkWrite) are:
*
* - `insertOne`
Expand Down Expand Up @@ -3939,7 +3949,7 @@ Model.hydrate = function(obj, projection, options) {
* - `updateMany()`
*
* @param {Object} filter
* @param {Object|Array} update
* @param {Object|Array} update. If array, this update will be treated as an update pipeline and not casted.
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.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
Expand Down Expand Up @@ -3979,7 +3989,7 @@ Model.updateMany = function updateMany(conditions, doc, options) {
* - `updateOne()`
*
* @param {Object} filter
* @param {Object|Array} update
* @param {Object|Array} update. If array, this update will be treated as an update pipeline and not casted.
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.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
Expand Down
4 changes: 2 additions & 2 deletions lib/query.js
Expand Up @@ -3880,7 +3880,7 @@ Query.prototype._replaceOne = async function _replaceOne() {
* - `updateMany()`
*
* @param {Object} [filter]
* @param {Object|Array} [update] the update command
* @param {Object|Array} [update] the update command. If array, this update will be treated as an update pipeline and not casted.
* @param {Object} [options]
* @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
* @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
Expand Down Expand Up @@ -3950,7 +3950,7 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) {
* - `updateOne()`
*
* @param {Object} [filter]
* @param {Object|Array} [update] the update command
* @param {Object|Array} [update] the update command. If array, this update will be treated as an update pipeline and not casted.
* @param {Object} [options]
* @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
* @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
Expand Down
16 changes: 16 additions & 0 deletions test/types/models.test.ts
Expand Up @@ -897,6 +897,22 @@ function gh4727() {
const company = { _id: new mongoose.Types.ObjectId(), name: 'Booster', users: [users[0]] };

return Company.hydrate(company, {}, { hydratedPopulatedDocs: true });
}

async function gh14440() {
const testSchema = new Schema({
dateProperty: { type: Date }
});

const TestModel = model('Test', testSchema);

const doc = new TestModel();
await TestModel.bulkWrite([
{
updateOne: {
filter: { _id: doc._id },
update: { dateProperty: (new Date('2023-06-01')).toISOString() }
}
}
]);
}
89 changes: 81 additions & 8 deletions types/models.d.ts
Expand Up @@ -156,6 +156,85 @@ declare module 'mongoose' {

const Model: Model<any>;

export type AnyBulkWriteOperation<TSchema = AnyObject> = {
insertOne: InsertOneModel<TSchema>;
} | {
replaceOne: ReplaceOneModel<TSchema>;
} | {
updateOne: UpdateOneModel<TSchema>;
} | {
updateMany: UpdateManyModel<TSchema>;
} | {
deleteOne: DeleteOneModel<TSchema>;
} | {
deleteMany: DeleteManyModel<TSchema>;
};

export interface InsertOneModel<TSchema> {
document: mongodb.OptionalId<TSchema>
}

export interface ReplaceOneModel<TSchema = AnyObject> {
/** The filter to limit the replaced document. */
filter: FilterQuery<TSchema>;
/** The document with which to replace the matched document. */
replacement: mongodb.WithoutId<TSchema>;
/** Specifies a collation. */
collation?: mongodb.CollationOptions;
/** The index to use. If specified, then the query system will only consider plans using the hinted index. */
hint?: mongodb.Hint;
/** When true, creates a new document if no document matches the query. */
upsert?: boolean;
}

export interface UpdateOneModel<TSchema = AnyObject> {
/** The filter to limit the updated documents. */
filter: FilterQuery<TSchema>;
/** A document or pipeline containing update operators. */
update: UpdateQuery<TSchema>;
/** A set of filters specifying to which array elements an update should apply. */
arrayFilters?: AnyObject[];
/** Specifies a collation. */
collation?: mongodb.CollationOptions;
/** The index to use. If specified, then the query system will only consider plans using the hinted index. */
hint?: mongodb.Hint;
/** When true, creates a new document if no document matches the query. */
upsert?: boolean;
}

export interface UpdateManyModel<TSchema = AnyObject> {
/** The filter to limit the updated documents. */
filter: FilterQuery<TSchema>;
/** A document or pipeline containing update operators. */
update: UpdateQuery<TSchema>;
/** A set of filters specifying to which array elements an update should apply. */
arrayFilters?: AnyObject[];
/** Specifies a collation. */
collation?: mongodb.CollationOptions;
/** The index to use. If specified, then the query system will only consider plans using the hinted index. */
hint?: mongodb.Hint;
/** When true, creates a new document if no document matches the query. */
upsert?: boolean;
}

export interface DeleteOneModel<TSchema = AnyObject> {
/** The filter to limit the deleted documents. */
filter: FilterQuery<TSchema>;
/** Specifies a collation. */
collation?: mongodb.CollationOptions;
/** The index to use. If specified, then the query system will only consider plans using the hinted index. */
hint?: mongodb.Hint;
}

export interface DeleteManyModel<TSchema = AnyObject> {
/** The filter to limit the deleted documents. */
filter: FilterQuery<TSchema>;
/** Specifies a collation. */
collation?: mongodb.CollationOptions;
/** The index to use. If specified, then the query system will only consider plans using the hinted index. */
hint?: mongodb.Hint;
}

/**
* Models are fancy constructors compiled from `Schema` definitions.
* An instance of a model is called a document.
Expand Down Expand Up @@ -201,17 +280,11 @@ declare module 'mongoose' {
* round trip to the MongoDB server.
*/
bulkWrite<DocContents = TRawDocType>(
writes: Array<
mongodb.AnyBulkWriteOperation<
DocContents extends mongodb.Document ? DocContents : any
> & MongooseBulkWritePerWriteOptions>,
writes: Array<AnyBulkWriteOperation<DocContents extends Document ? any : (DocContents extends {} ? DocContents : any)>>,
options: mongodb.BulkWriteOptions & MongooseBulkWriteOptions & { ordered: false }
): Promise<mongodb.BulkWriteResult & { mongoose?: { validationErrors: Error[] } }>;
bulkWrite<DocContents = TRawDocType>(
writes: Array<
mongodb.AnyBulkWriteOperation<
DocContents extends mongodb.Document ? DocContents : any
> & MongooseBulkWritePerWriteOptions>,
writes: Array<AnyBulkWriteOperation<DocContents extends Document ? any : (DocContents extends {} ? DocContents : any)>>,
options?: mongodb.BulkWriteOptions & MongooseBulkWriteOptions
): Promise<mongodb.BulkWriteResult>;

Expand Down

0 comments on commit 3ba9778

Please sign in to comment.