Skip to content

docs(typescript): clarify that setting THydratedDocumentType on schemas is necessary for correct method context #14575

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

Merged
merged 4 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions docs/typescript/schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ This is because Mongoose has numerous features that add paths to your schema tha
## Arrays

When you define an array in a document interface, we recommend using vanilla JavaScript arrays, **not** Mongoose's `Types.Array` type or `Types.DocumentArray` type.
Instead, use the `THydratedDocumentType` generic to define that the hydrated document type has paths of type `Types.Array` and `Types.DocumentArray`.
Instead, use the `THydratedDocumentType` generic for models and schemas to define that the hydrated document type has paths of type `Types.Array` and `Types.DocumentArray`.

```typescript
import mongoose from 'mongoose'
Expand All @@ -178,17 +178,27 @@ interface IOrder {
// for fully hydrated docs returned from `findOne()`, etc.
type OrderHydratedDocument = mongoose.HydratedDocument<
IOrder,
{ tags: mongoose.Types.DocumentArray<{ name: string }> }
{ tags: mongoose.HydratedArraySubdocument<{ name: string }> }
>;
type OrderModelType = mongoose.Model<
IOrder,
{},
{},
{},
OrderHydratedDocument
OrderHydratedDocument // THydratedDocumentType
>;

const orderSchema = new mongoose.Schema<IOrder, OrderModelType>({
const orderSchema = new mongoose.Schema<
IOrder,
OrderModelType,
{}, // methods
{}, // query helpers
{}, // virtuals
{}, // statics
mongoose.DefaultSchemaOptions, // schema options
IOrder, // doctype
OrderHydratedDocument // THydratedDocumentType
>({
tags: [{ name: { type: String, required: true } }]
});
const OrderModel = mongoose.model<IOrder, OrderModelType>('Order', orderSchema);
Expand All @@ -207,3 +217,8 @@ async function run() {
leanDoc.tags; // Array<{ name: string }>
};
```

Use `HydratedArraySubdocument<RawDocType>` for the type of array subdocuments, and `HydratedSingleSubdocument<RawDocType>` for single subdocuments.

If you are not using [schema methods](../guide.html#methods), middleware, or [virtuals](../tutorials/virtuals.html), you can omit the last 7 generic parameters to `Schema()` and just define your schema using `new mongoose.Schema<IOrder, OrderModelType>(...)`.
The THydratedDocumentType parameter for schemas is primarily for setting the value of `this` on methods and virtuals.
52 changes: 52 additions & 0 deletions test/types/schema.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {
DefaultSchemaOptions,
HydratedSingleSubdocument,
Schema,
Document,
HydratedDocument,
Expand Down Expand Up @@ -1442,3 +1444,53 @@ function gh14367() {
flags: [true]
};
}

function gh14573() {
interface Names {
_id: Types.ObjectId;
firstName: string;
}

// Document definition
interface User {
names: Names;
}

// Define property overrides for hydrated documents
type THydratedUserDocument = {
names?: HydratedSingleSubdocument<Names>;
};

type UserMethods = {
getName(): Names | undefined;
};

type UserModelType = Model<User, {}, UserMethods, {}, THydratedUserDocument>;

const userSchema = new Schema<
User,
UserModelType,
UserMethods,
{},
{},
{},
DefaultSchemaOptions,
User,
THydratedUserDocument
>(
{
names: new Schema<Names>({ firstName: String })
},
{
methods: {
getName() {
const str: string | undefined = this.names?.firstName;
return this.names?.toObject();
}
}
}
);
const UserModel = model<User, UserModelType>('User', userSchema);
const doc = new UserModel({ names: { _id: '0'.repeat(24), firstName: 'foo' } });
doc.names?.ownerDocument();
}