Automattic / Automattic/mongoose

9.10.0: Model with typed virtuals is no longer assignable to `Model<any>`

Closed
#16,503 2 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.