Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

types(model): make bulkWrite() types more flexible to account for casting #14423

Merged
merged 6 commits into from Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 if you pass [set 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.
vkarpov15 marked this conversation as resolved.
Show resolved Hide resolved
*
* 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 type InsertOneModel<TSchema> = {
vkarpov15 marked this conversation as resolved.
Show resolved Hide resolved
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