Skip to content

Commit 032aa1c

Browse files
authored
Merge pull request #15020 from Automattic/vkarpov15/gh-14696-2
BREAKING CHANGE: change `this` to HydratedDocument for default() and required(), HydratedDocument | Query for validate()
2 parents d36ac59 + 3773c06 commit 032aa1c

File tree

4 files changed

+79
-46
lines changed

4 files changed

+79
-46
lines changed

test/types/schema.test.ts

+37-13
Original file line numberDiff line numberDiff line change
@@ -1232,12 +1232,24 @@ async function gh13797() {
12321232
interface IUser {
12331233
name: string;
12341234
}
1235-
new Schema<IUser>({ name: { type: String, required: function() {
1236-
expectType<IUser>(this); return true;
1237-
} } });
1238-
new Schema<IUser>({ name: { type: String, default: function() {
1239-
expectType<IUser>(this); return '';
1240-
} } });
1235+
new Schema<IUser>({
1236+
name: {
1237+
type: String,
1238+
required: function() {
1239+
expectType<HydratedDocument<IUser>>(this);
1240+
return true;
1241+
}
1242+
}
1243+
});
1244+
new Schema<IUser>({
1245+
name: {
1246+
type: String,
1247+
default: function() {
1248+
expectType<HydratedDocument<IUser>>(this);
1249+
return '';
1250+
}
1251+
}
1252+
});
12411253
}
12421254

12431255
declare const brand: unique symbol;
@@ -1529,12 +1541,17 @@ function gh14696() {
15291541

15301542
const x: ValidateOpts<unknown, User> = {
15311543
validator(v: any) {
1532-
expectAssignable<User>(this);
1533-
return !v || this.name === 'super admin';
1544+
expectAssignable<User | Query<unknown, User>>(this);
1545+
return !v || this instanceof Query || this.name === 'super admin';
15341546
}
15351547
};
15361548

1537-
const userSchema = new Schema<User>({
1549+
interface IUserMethods {
1550+
isSuperAdmin(): boolean;
1551+
}
1552+
1553+
type UserModelType = Model<User, {}, IUserMethods>;
1554+
const userSchema = new Schema<User, UserModelType, IUserMethods>({
15381555
name: {
15391556
type: String,
15401557
required: [true, 'Name on card is required']
@@ -1544,8 +1561,11 @@ function gh14696() {
15441561
default: false,
15451562
validate: {
15461563
validator(v: any) {
1547-
expectAssignable<User>(this);
1548-
return !v || this.name === 'super admin';
1564+
expectAssignable<User | Query<unknown, User>>(this);
1565+
if (!v) {
1566+
return true;
1567+
}
1568+
return this.get('name') === 'super admin' || (!(this instanceof Query) && this.isSuperAdmin());
15491569
}
15501570
}
15511571
},
@@ -1554,8 +1574,12 @@ function gh14696() {
15541574
default: false,
15551575
validate: {
15561576
async validator(v: any) {
1557-
expectAssignable<User>(this);
1558-
return !v || this.name === 'super admin';
1577+
expectAssignable<User | Query<unknown, User>>(this);
1578+
if (this instanceof Query) {
1579+
const doc = await this.clone().findOne().orFail();
1580+
return doc.isSuperAdmin();
1581+
}
1582+
return !v || this.get('name') === 'super admin';
15591583
}
15601584
}
15611585
}

types/index.d.ts

+13-13
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ declare module 'mongoose' {
273273
/**
274274
* Create a new schema
275275
*/
276-
constructor(definition?: SchemaDefinition<SchemaDefinitionType<RawDocType>, RawDocType> | DocType, options?: SchemaOptions<FlatRecord<DocType>, TInstanceMethods, TQueryHelpers, TStaticMethods, TVirtuals, THydratedDocumentType> | ResolveSchemaOptions<TSchemaOptions>);
276+
constructor(definition?: SchemaDefinition<SchemaDefinitionType<RawDocType>, RawDocType, THydratedDocumentType> | DocType, options?: SchemaOptions<FlatRecord<DocType>, TInstanceMethods, TQueryHelpers, TStaticMethods, TVirtuals, THydratedDocumentType> | ResolveSchemaOptions<TSchemaOptions>);
277277

278278
/** Adds key path / schema type pairs to this schema. */
279279
add(obj: SchemaDefinition<SchemaDefinitionType<RawDocType>> | Schema, prefix?: string): this;
@@ -539,26 +539,26 @@ declare module 'mongoose' {
539539
? DateSchemaDefinition
540540
: (Function | string);
541541

542-
export type SchemaDefinitionProperty<T = undefined, EnforcedDocType = any> = SchemaDefinitionWithBuiltInClass<T> |
543-
SchemaTypeOptions<T extends undefined ? any : T, EnforcedDocType> |
542+
export type SchemaDefinitionProperty<T = undefined, EnforcedDocType = any, THydratedDocumentType = any> = SchemaDefinitionWithBuiltInClass<T> |
543+
SchemaTypeOptions<T extends undefined ? any : T, EnforcedDocType, THydratedDocumentType> |
544544
typeof SchemaType |
545-
Schema<any, any, any> |
546-
Schema<any, any, any>[] |
547-
SchemaTypeOptions<T extends undefined ? any : Unpacked<T>, EnforcedDocType>[] |
548-
Function[] |
549-
SchemaDefinition<T, EnforcedDocType> |
550-
SchemaDefinition<Unpacked<T>, EnforcedDocType>[] |
545+
Schema<any, any, any> |
546+
Schema<any, any, any>[] |
547+
SchemaTypeOptions<T extends undefined ? any : Unpacked<T>, EnforcedDocType, THydratedDocumentType>[] |
548+
Function[] |
549+
SchemaDefinition<T, EnforcedDocType, THydratedDocumentType> |
550+
SchemaDefinition<Unpacked<T>, EnforcedDocType, THydratedDocumentType>[] |
551551
typeof Schema.Types.Mixed |
552-
MixedSchemaTypeOptions<EnforcedDocType>;
552+
MixedSchemaTypeOptions<EnforcedDocType, THydratedDocumentType>;
553553

554-
export type SchemaDefinition<T = undefined, EnforcedDocType = any> = T extends undefined
554+
export type SchemaDefinition<T = undefined, EnforcedDocType = any, THydratedDocumentType = any> = T extends undefined
555555
? { [path: string]: SchemaDefinitionProperty; }
556-
: { [path in keyof T]?: SchemaDefinitionProperty<T[path], EnforcedDocType>; };
556+
: { [path in keyof T]?: SchemaDefinitionProperty<T[path], EnforcedDocType, THydratedDocumentType>; };
557557

558558
export type AnyArray<T> = T[] | ReadonlyArray<T>;
559559
export type ExtractMongooseArray<T> = T extends Types.Array<any> ? AnyArray<Unpacked<T>> : T;
560560

561-
export interface MixedSchemaTypeOptions<EnforcedDocType> extends SchemaTypeOptions<Schema.Types.Mixed, EnforcedDocType> {
561+
export interface MixedSchemaTypeOptions<EnforcedDocType, THydratedDocumentType> extends SchemaTypeOptions<Schema.Types.Mixed, EnforcedDocType, THydratedDocumentType> {
562562
type: typeof Schema.Types.Mixed;
563563
}
564564

types/schematypes.d.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ declare module 'mongoose' {
3939

4040
type DefaultType<T> = T extends Schema.Types.Mixed ? any : Partial<ExtractMongooseArray<T>>;
4141

42-
class SchemaTypeOptions<T, EnforcedDocType = any> {
42+
class SchemaTypeOptions<T, EnforcedDocType = any, THydratedDocumentType = any> {
4343
type?:
4444
T extends string ? StringSchemaDefinition :
4545
T extends number ? NumberSchemaDefinition :
@@ -48,19 +48,19 @@ declare module 'mongoose' {
4848
T extends Map<any, any> ? SchemaDefinition<typeof Map> :
4949
T extends Buffer ? SchemaDefinition<typeof Buffer> :
5050
T extends Types.ObjectId ? ObjectIdSchemaDefinition :
51-
T extends Types.ObjectId[] ? AnyArray<ObjectIdSchemaDefinition> | AnyArray<SchemaTypeOptions<ObjectId, EnforcedDocType>> :
52-
T extends object[] ? (AnyArray<Schema<any, any, any>> | AnyArray<SchemaDefinition<Unpacked<T>>> | AnyArray<SchemaTypeOptions<Unpacked<T>, EnforcedDocType>>) :
53-
T extends string[] ? AnyArray<StringSchemaDefinition> | AnyArray<SchemaTypeOptions<string, EnforcedDocType>> :
54-
T extends number[] ? AnyArray<NumberSchemaDefinition> | AnyArray<SchemaTypeOptions<number, EnforcedDocType>> :
55-
T extends boolean[] ? AnyArray<BooleanSchemaDefinition> | AnyArray<SchemaTypeOptions<boolean, EnforcedDocType>> :
56-
T extends Function[] ? AnyArray<Function | string> | AnyArray<SchemaTypeOptions<Unpacked<T>, EnforcedDocType>> :
57-
T | typeof SchemaType | Schema<any, any, any> | SchemaDefinition<T> | Function | AnyArray<Function>;
51+
T extends Types.ObjectId[] ? AnyArray<ObjectIdSchemaDefinition> | AnyArray<SchemaTypeOptions<ObjectId, EnforcedDocType, THydratedDocumentType>> :
52+
T extends object[] ? (AnyArray<Schema<any, any, any>> | AnyArray<SchemaDefinition<Unpacked<T>, EnforcedDocType, THydratedDocumentType>> | AnyArray<SchemaTypeOptions<Unpacked<T>, EnforcedDocType, THydratedDocumentType>>) :
53+
T extends string[] ? AnyArray<StringSchemaDefinition> | AnyArray<SchemaTypeOptions<string, EnforcedDocType, THydratedDocumentType>> :
54+
T extends number[] ? AnyArray<NumberSchemaDefinition> | AnyArray<SchemaTypeOptions<number, EnforcedDocType, THydratedDocumentType>> :
55+
T extends boolean[] ? AnyArray<BooleanSchemaDefinition> | AnyArray<SchemaTypeOptions<boolean, EnforcedDocType, THydratedDocumentType>> :
56+
T extends Function[] ? AnyArray<Function | string> | AnyArray<SchemaTypeOptions<Unpacked<T>, EnforcedDocType, THydratedDocumentType>> :
57+
T | typeof SchemaType | Schema<any, any, any> | SchemaDefinition<T, EnforcedDocType, THydratedDocumentType> | Function | AnyArray<Function>;
5858

5959
/** Defines a virtual with the given name that gets/sets this path. */
6060
alias?: string | string[];
6161

6262
/** Function or object describing how to validate this schematype. See [validation docs](https://mongoosejs.com/docs/validation.html). */
63-
validate?: SchemaValidator<T, EnforcedDocType> | AnyArray<SchemaValidator<T, EnforcedDocType>>;
63+
validate?: SchemaValidator<T, THydratedDocumentType> | AnyArray<SchemaValidator<T, THydratedDocumentType>>;
6464

6565
/** Allows overriding casting logic for this individual path. If a string, the given string overwrites Mongoose's default cast error message. */
6666
cast?: string |
@@ -74,13 +74,13 @@ declare module 'mongoose' {
7474
* path cannot be set to a nullish value. If a function, Mongoose calls the
7575
* function and only checks for nullish values if the function returns a truthy value.
7676
*/
77-
required?: boolean | ((this: EnforcedDocType) => boolean) | [boolean, string] | [(this: EnforcedDocType) => boolean, string];
77+
required?: boolean | ((this: THydratedDocumentType) => boolean) | [boolean, string] | [(this: THydratedDocumentType) => boolean, string];
7878

7979
/**
8080
* The default value for this path. If a function, Mongoose executes the function
8181
* and uses the return value as the default.
8282
*/
83-
default?: DefaultType<T> | ((this: EnforcedDocType, doc: any) => DefaultType<T>) | null;
83+
default?: DefaultType<T> | ((this: THydratedDocumentType, doc: THydratedDocumentType) => DefaultType<T>) | null;
8484

8585
/**
8686
* The model that `populate()` should use if populating this path.

types/validation.d.ts

+18-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
declare module 'mongoose' {
2-
3-
type SchemaValidator<T, EnforcedDocType> = RegExp | [RegExp, string] | Function | [Function, string] | ValidateOpts<T, EnforcedDocType> | ValidateOpts<T, EnforcedDocType>[];
2+
type SchemaValidator<T, THydratedDocumentType> = RegExp |
3+
[RegExp, string] |
4+
Function |
5+
[Function, string] |
6+
ValidateOpts<T, THydratedDocumentType> |
7+
ValidateOpts<T, THydratedDocumentType>[];
48

59
interface ValidatorProps {
610
path: string;
@@ -13,18 +17,23 @@ declare module 'mongoose' {
1317
(props: ValidatorProps): string;
1418
}
1519

16-
type ValidateFn<T, EnforcedDocType> =
17-
(this: EnforcedDocType, value: any, props?: ValidatorProps & Record<string, any>) => boolean;
20+
type ValidateFn<T, THydratedDocumentType> = (
21+
this: THydratedDocumentType | Query<unknown, THydratedDocumentType>,
22+
value: any,
23+
props?: ValidatorProps & Record<string, any>
24+
) => boolean;
1825

19-
type AsyncValidateFn<T, EnforcedDocType> =
20-
(this: EnforcedDocType, value: any, props?: ValidatorProps & Record<string, any>) => Promise<boolean>;
26+
type AsyncValidateFn<T, THydratedDocumentType> = (
27+
this: THydratedDocumentType | Query<unknown, THydratedDocumentType>,
28+
value: any,
29+
props?: ValidatorProps & Record<string, any>
30+
) => Promise<boolean>;
2131

22-
interface ValidateOpts<T, EnforcedDocType> {
32+
interface ValidateOpts<T, THydratedDocumentType> {
2333
msg?: string;
2434
message?: string | ValidatorMessageFn;
2535
type?: string;
26-
validator: ValidateFn<T, EnforcedDocType>
27-
| AsyncValidateFn<T, EnforcedDocType>;
36+
validator: ValidateFn<T, THydratedDocumentType> | AsyncValidateFn<T, THydratedDocumentType>;
2837
propsParameter?: boolean;
2938
}
3039
}

0 commit comments

Comments
 (0)