Automattic / Automattic/mongoose
9.10.0: Model with typed virtuals is no longer assignable to `Model<any>`
- Dominant language
- JavaScript
- Stars
- 27.5k
- Forks
- 4k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 35
Description
### Prerequisites
- [x] I have written a descriptive issue title
- [x] I have searched existing issues to ensure the bug has not already been reported
### Mongoose version
`9.10.0` (regression vs `9.9.5`)
### Node.js version
`24.21.0`
### MongoDB server version
n/a — compile-time TypeScript error
### Typescript version (if applicable)
`7.0.2`
### Description
In 9.10.0, a `Model` with a non-empty `TVirtuals` generic is no longer assignable to `Model` (non-empty `TInstanceMethods`/`TQueryHelpers` alone still work). This breaks every API typed as `string | Model`: [`PopulateOptions.model`](https://github.com/Automattic/mongoose/blob/9.10.0/types/populate.d.ts#L63), [`SchemaTypeOptions.ref`](https://github.com/Automattic/mongoose/blob/9.10.0/types/schematypes.d.ts#L123), [`Document#populate()`](https://github.com/Automattic/mongoose/blob/9.10.0/types/document.d.ts#L255), `Model.populate()`, and `as Model` casts.
Likely introduced by the `Schema.prototype.queryHelper()` / `static()`/`statics` typing changes (#16464, #16350).
Notably, this release already relaxed `doc.$model()`/`doc.model()` to `Model` for the same reason — see [document.d.ts#L99](https://github.com/Automattic/mongoose/blob/9.10.0/types/document.d.ts#L99) and [document.d.ts#L235](https://github.com/Automattic/mongoose/blob/9.10.0/types/document.d.ts#L235). `PopulateOptions.model` etc. were presumably missed.
This affects every `@typegoose/typegoose` project: its `ModelType` always sets `TVirtuals = { id: string }`, so all typegoose models now fail `populate({ model })`.
### Steps to Reproduce
```ts
import type { Model, PopulateOptions } from 'mongoose';
// any Model with non-empty TVirtuals
declare const m: Model<{ a: string }, {}, {}, { id: string }>;
const a: Model = m; // TS2322 on 9.10.0, OK on 9.9.5
const p: PopulateOptions = { path: 'x', model: m }; // TS2322 on 9.10.0
```
`tsc --noEmit --strict --skipLibCheck` prints:
```
error TS2322: Type 'Model<{ a: string; }, {}, {}, { id: string; }, ...>' is not assignable to type 'Model'.
The types returned by 'discriminator(...).schema.queryHelper(...).statics' are incompatible between these types.
...
The 'this' types of each signature are incompatible.
Type 'Model' is not assignable to type 'Model'.
The types of 'schema.virtuals' are incompatible between these types.
Property 'id' is missing in type '{}' but required in type '{ id: string; }'.
```
### Expected Behavior
`Model` should remain assignable to `Model` as in 9.9.5, so typed models can be passed to `populate({ model })`, `ref`, etc. Suggested fix: type these positions as `Model` — the same relaxation already applied to `doc.$model()`/`doc.model()` in this release.
Contributor guide
Research direction
Inspect types/populate.d.ts, types/schematypes.d.ts, and types/document.d.ts, comparing their Model generic positions with the already-relaxed doc.$model() and doc.model() signatures. Reproduce the strict tsc snippet; done means a Model with non-empty TVirtuals is assignable to Model and accepted by PopulateOptions.model, ref, and populate APIs.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- mongodb, node.js, typescript
- Domain
- backend-api-design, database
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100