From 2f800d4c1ab6827d8ab157eeabb0f5c5c01f83ed Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 28 Mar 2025 12:09:44 -0400 Subject: [PATCH] Revert "Revert "types: make `init` hooks types accurately reflect runtime behavior"" --- test/types/middleware.preposttypes.test.ts | 146 +++++---------------- test/types/schema.test.ts | 31 +++++ types/index.d.ts | 1 + types/middlewares.d.ts | 2 +- 4 files changed, 63 insertions(+), 117 deletions(-) diff --git a/test/types/middleware.preposttypes.test.ts b/test/types/middleware.preposttypes.test.ts index 052df875c0f..238059ee372 100644 --- a/test/types/middleware.preposttypes.test.ts +++ b/test/types/middleware.preposttypes.test.ts @@ -67,47 +67,6 @@ schema.pre('init', function() { expectType>(this); }); -schema.post('init', function(res) { - expectType>(this); - expectNotType>(res); -}); - -schema.pre('init', { document: true, query: false }, function() { - expectType>(this); -}); - -schema.post('init', { document: true, query: false }, function(res) { - expectType>(this); - expectNotType>(res); -}); - -schema.pre('init', { document: true, query: true }, function() { - expectType>(this); -}); - -schema.post('init', { document: true, query: true }, function(res) { - expectType>(this); - expectNotType>(res); -}); - -schema.pre('init', { document: false, query: true }, function() { - expectType(this); -}); - -schema.post('init', { document: false, query: true }, function(res) { - expectType(this); - expectNotType>(res); -}); - -schema.pre('init', { document: false, query: false }, function() { - expectType(this); -}); - -schema.post('init', { document: false, query: false }, function(res) { - expectType(this); - expectNotType>(res); -}); - schema.pre('estimatedDocumentCount', function() { expectType>(this); }); @@ -693,51 +652,6 @@ schema.post('deleteOne', { document: false, query: false }, function(res) { expectNotType>(res); }); -schema.pre(['save', 'init'], function() { - expectType>(this); -}); - -schema.post(['save', 'init'], function(res) { - expectType>(this); - expectNotType>(res); -}); - -schema.pre(['save', 'init'], { document: true, query: false }, function() { - expectType>(this); -}); - -schema.post(['save', 'init'], { document: true, query: false }, function(res) { - expectType>(this); - expectNotType>(res); -}); - -schema.pre(['save', 'init'], { document: true, query: true }, function() { - expectType>(this); -}); - -schema.post(['save', 'init'], { document: true, query: true }, function(res) { - expectType>(this); - expectNotType>(res); -}); - -schema.pre(['save', 'init'], { document: false, query: true }, function() { - expectType(this); -}); - -schema.post(['save', 'init'], { document: false, query: true }, function(res) { - expectType(this); - expectNotType>(res); -}); - -schema.pre(['save', 'init'], { document: false, query: false }, function() { - expectType(this); -}); - -schema.post(['save', 'init'], { document: false, query: false }, function(res) { - expectType(this); - expectNotType>(res); -}); - schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], function() { expectType>(this); }); @@ -828,137 +742,137 @@ schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct expectNotType>(res); }); -schema.pre(['save', 'init', 'updateOne', 'deleteOne', 'validate'], function() { +schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], function() { expectType|HydratedDocument>(this); }); -schema.post(['save', 'init', 'updateOne', 'deleteOne', 'validate'], function(res) { +schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], function(res) { expectType|HydratedDocument>(this); expectNotType>(res); }); -schema.pre(['save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function() { +schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function() { expectType>(this); }); -schema.post(['save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function(res) { +schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function(res) { expectType>(this); expectNotType>(res); }); -schema.pre(['save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function() { +schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function() { expectType>(this); }); -schema.post(['save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function(res) { +schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function(res) { expectType>(this); expectNotType>(res); }); -schema.pre(['save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function() { +schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function() { expectType|HydratedDocument>(this); }); -schema.post(['save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function(res) { +schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function(res) { expectType|HydratedDocument>(this); expectNotType>(res); }); -schema.pre(['save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function() { +schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function() { expectType(this); }); -schema.post(['save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function(res) { +schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function(res) { expectType(this); expectNotType>(res); }); -schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], function() { +schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'updateOne', 'deleteOne', 'validate'], function() { expectType|HydratedDocument>(this); }); -schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], function(res) { +schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'updateOne', 'deleteOne', 'validate'], function(res) { expectType|HydratedDocument>(this); expectNotType>(res); }); -schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function() { +schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function() { expectType>(this); }); -schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function(res) { +schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function(res) { expectType>(this); expectNotType>(res); }); -schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function() { +schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function() { expectType>(this); }); -schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function(res) { +schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function(res) { expectType>(this); expectNotType>(res); }); -schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function() { +schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function() { expectType|HydratedDocument>(this); }); -schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function(res) { +schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function(res) { expectType|HydratedDocument>(this); expectNotType>(res); }); -schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function() { +schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function() { expectType(this); }); -schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function(res) { +schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function(res) { expectType(this); expectNotType>(res); }); -schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], function() { +schema.pre(['save', 'updateOne', 'deleteOne', 'validate'], function() { expectType|HydratedDocument>(this); }); -schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], function(res) { +schema.post(['save', 'updateOne', 'deleteOne', 'validate'], function(res) { expectType|HydratedDocument>(this); expectNotType>(res); }); -schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function() { +schema.pre(['save', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function() { expectType>(this); }); -schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function(res) { +schema.post(['save', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function(res) { expectType>(this); expectNotType>(res); }); -schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function() { +schema.pre(['save', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function() { expectType>(this); }); -schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function(res) { +schema.post(['save', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function(res) { expectType>(this); expectNotType>(res); }); -schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function() { +schema.pre(['save', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function() { expectType|HydratedDocument>(this); }); -schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function(res) { +schema.post(['save', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function(res) { expectType|HydratedDocument>(this); expectNotType>(res); }); -schema.pre(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function() { +schema.pre(['save', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function() { expectType(this); }); -schema.post(['estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function(res) { +schema.post(['save', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function(res) { expectType(this); expectNotType>(res); }); diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 20ecc9c0584..ee11e1f4e6f 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1747,6 +1747,37 @@ async function schemaDouble() { expectType(doc.balance); } +function gh15301() { + interface IUser { + time: { hours: number, minutes: number } + } + const userSchema = new Schema({ + time: { + type: new Schema( + { + hours: { type: Number, required: true }, + minutes: { type: Number, required: true } + }, + { _id: false } + ), + required: true + } + }); + + const timeStringToObject = (time) => { + if (typeof time !== 'string') return time; + const [hours, minutes] = time.split(':'); + return { hours: parseInt(hours), minutes: parseInt(minutes) }; + }; + + userSchema.pre('init', function(rawDoc) { + expectType(rawDoc); + if (typeof rawDoc.time === 'string') { + rawDoc.time = timeStringToObject(rawDoc.time); + } + }); +} + function defaultReturnsUndefined() { const schema = new Schema<{ arr: number[] }>({ arr: { diff --git a/types/index.d.ts b/types/index.d.ts index 1c3879d476f..ca951b4cf2d 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -436,6 +436,7 @@ declare module 'mongoose' { ): this; // this = Document pre(method: 'save', fn: PreSaveMiddlewareFunction): this; + pre(method: 'init', fn: (this: T, doc: U) => void): this; pre(method: MongooseDistinctDocumentMiddleware|MongooseDistinctDocumentMiddleware[], fn: PreMiddlewareFunction): this; pre(method: MongooseDistinctDocumentMiddleware|MongooseDistinctDocumentMiddleware[], options: SchemaPreOptions, fn: PreMiddlewareFunction): this; pre( diff --git a/types/middlewares.d.ts b/types/middlewares.d.ts index 8d380ed69d1..64d8ca620bb 100644 --- a/types/middlewares.d.ts +++ b/types/middlewares.d.ts @@ -3,7 +3,7 @@ declare module 'mongoose' { type MongooseQueryAndDocumentMiddleware = 'updateOne' | 'deleteOne'; - type MongooseDistinctDocumentMiddleware = 'save' | 'init' | 'validate'; + type MongooseDistinctDocumentMiddleware = 'save' | 'validate'; type MongooseDocumentMiddleware = MongooseDistinctDocumentMiddleware | MongooseQueryAndDocumentMiddleware; type MongooseRawResultQueryMiddleware = 'findOneAndUpdate' | 'findOneAndReplace' | 'findOneAndDelete';