Skip to content

Commit

Permalink
Merge pull request #15020 from Automattic/vkarpov15/gh-14696-2
Browse files Browse the repository at this point in the history
BREAKING CHANGE: change `this` to HydratedDocument for default() and required(), HydratedDocument | Query for validate()
  • Loading branch information
vkarpov15 authored Nov 8, 2024
2 parents d36ac59 + 3773c06 commit 032aa1c
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 46 deletions.
50 changes: 37 additions & 13 deletions test/types/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1232,12 +1232,24 @@ async function gh13797() {
interface IUser {
name: string;
}
new Schema<IUser>({ name: { type: String, required: function() {
expectType<IUser>(this); return true;
} } });
new Schema<IUser>({ name: { type: String, default: function() {
expectType<IUser>(this); return '';
} } });
new Schema<IUser>({
name: {
type: String,
required: function() {
expectType<HydratedDocument<IUser>>(this);
return true;
}
}
});
new Schema<IUser>({
name: {
type: String,
default: function() {
expectType<HydratedDocument<IUser>>(this);
return '';
}
}
});
}

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

const x: ValidateOpts<unknown, User> = {
validator(v: any) {
expectAssignable<User>(this);
return !v || this.name === 'super admin';
expectAssignable<User | Query<unknown, User>>(this);
return !v || this instanceof Query || this.name === 'super admin';
}
};

const userSchema = new Schema<User>({
interface IUserMethods {
isSuperAdmin(): boolean;
}

type UserModelType = Model<User, {}, IUserMethods>;
const userSchema = new Schema<User, UserModelType, IUserMethods>({
name: {
type: String,
required: [true, 'Name on card is required']
Expand All @@ -1544,8 +1561,11 @@ function gh14696() {
default: false,
validate: {
validator(v: any) {
expectAssignable<User>(this);
return !v || this.name === 'super admin';
expectAssignable<User | Query<unknown, User>>(this);
if (!v) {
return true;
}
return this.get('name') === 'super admin' || (!(this instanceof Query) && this.isSuperAdmin());
}
}
},
Expand All @@ -1554,8 +1574,12 @@ function gh14696() {
default: false,
validate: {
async validator(v: any) {
expectAssignable<User>(this);
return !v || this.name === 'super admin';
expectAssignable<User | Query<unknown, User>>(this);
if (this instanceof Query) {
const doc = await this.clone().findOne().orFail();
return doc.isSuperAdmin();
}
return !v || this.get('name') === 'super admin';
}
}
}
Expand Down
26 changes: 13 additions & 13 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ declare module 'mongoose' {
/**
* Create a new schema
*/
constructor(definition?: SchemaDefinition<SchemaDefinitionType<RawDocType>, RawDocType> | DocType, options?: SchemaOptions<FlatRecord<DocType>, TInstanceMethods, TQueryHelpers, TStaticMethods, TVirtuals, THydratedDocumentType> | ResolveSchemaOptions<TSchemaOptions>);
constructor(definition?: SchemaDefinition<SchemaDefinitionType<RawDocType>, RawDocType, THydratedDocumentType> | DocType, options?: SchemaOptions<FlatRecord<DocType>, TInstanceMethods, TQueryHelpers, TStaticMethods, TVirtuals, THydratedDocumentType> | ResolveSchemaOptions<TSchemaOptions>);

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

export type SchemaDefinitionProperty<T = undefined, EnforcedDocType = any> = SchemaDefinitionWithBuiltInClass<T> |
SchemaTypeOptions<T extends undefined ? any : T, EnforcedDocType> |
export type SchemaDefinitionProperty<T = undefined, EnforcedDocType = any, THydratedDocumentType = any> = SchemaDefinitionWithBuiltInClass<T> |
SchemaTypeOptions<T extends undefined ? any : T, EnforcedDocType, THydratedDocumentType> |
typeof SchemaType |
Schema<any, any, any> |
Schema<any, any, any>[] |
SchemaTypeOptions<T extends undefined ? any : Unpacked<T>, EnforcedDocType>[] |
Function[] |
SchemaDefinition<T, EnforcedDocType> |
SchemaDefinition<Unpacked<T>, EnforcedDocType>[] |
Schema<any, any, any> |
Schema<any, any, any>[] |
SchemaTypeOptions<T extends undefined ? any : Unpacked<T>, EnforcedDocType, THydratedDocumentType>[] |
Function[] |
SchemaDefinition<T, EnforcedDocType, THydratedDocumentType> |
SchemaDefinition<Unpacked<T>, EnforcedDocType, THydratedDocumentType>[] |
typeof Schema.Types.Mixed |
MixedSchemaTypeOptions<EnforcedDocType>;
MixedSchemaTypeOptions<EnforcedDocType, THydratedDocumentType>;

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

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

export interface MixedSchemaTypeOptions<EnforcedDocType> extends SchemaTypeOptions<Schema.Types.Mixed, EnforcedDocType> {
export interface MixedSchemaTypeOptions<EnforcedDocType, THydratedDocumentType> extends SchemaTypeOptions<Schema.Types.Mixed, EnforcedDocType, THydratedDocumentType> {
type: typeof Schema.Types.Mixed;
}

Expand Down
22 changes: 11 additions & 11 deletions types/schematypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ declare module 'mongoose' {

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

class SchemaTypeOptions<T, EnforcedDocType = any> {
class SchemaTypeOptions<T, EnforcedDocType = any, THydratedDocumentType = any> {
type?:
T extends string ? StringSchemaDefinition :
T extends number ? NumberSchemaDefinition :
Expand All @@ -48,19 +48,19 @@ declare module 'mongoose' {
T extends Map<any, any> ? SchemaDefinition<typeof Map> :
T extends Buffer ? SchemaDefinition<typeof Buffer> :
T extends Types.ObjectId ? ObjectIdSchemaDefinition :
T extends Types.ObjectId[] ? AnyArray<ObjectIdSchemaDefinition> | AnyArray<SchemaTypeOptions<ObjectId, EnforcedDocType>> :
T extends object[] ? (AnyArray<Schema<any, any, any>> | AnyArray<SchemaDefinition<Unpacked<T>>> | AnyArray<SchemaTypeOptions<Unpacked<T>, EnforcedDocType>>) :
T extends string[] ? AnyArray<StringSchemaDefinition> | AnyArray<SchemaTypeOptions<string, EnforcedDocType>> :
T extends number[] ? AnyArray<NumberSchemaDefinition> | AnyArray<SchemaTypeOptions<number, EnforcedDocType>> :
T extends boolean[] ? AnyArray<BooleanSchemaDefinition> | AnyArray<SchemaTypeOptions<boolean, EnforcedDocType>> :
T extends Function[] ? AnyArray<Function | string> | AnyArray<SchemaTypeOptions<Unpacked<T>, EnforcedDocType>> :
T | typeof SchemaType | Schema<any, any, any> | SchemaDefinition<T> | Function | AnyArray<Function>;
T extends Types.ObjectId[] ? AnyArray<ObjectIdSchemaDefinition> | AnyArray<SchemaTypeOptions<ObjectId, EnforcedDocType, THydratedDocumentType>> :
T extends object[] ? (AnyArray<Schema<any, any, any>> | AnyArray<SchemaDefinition<Unpacked<T>, EnforcedDocType, THydratedDocumentType>> | AnyArray<SchemaTypeOptions<Unpacked<T>, EnforcedDocType, THydratedDocumentType>>) :
T extends string[] ? AnyArray<StringSchemaDefinition> | AnyArray<SchemaTypeOptions<string, EnforcedDocType, THydratedDocumentType>> :
T extends number[] ? AnyArray<NumberSchemaDefinition> | AnyArray<SchemaTypeOptions<number, EnforcedDocType, THydratedDocumentType>> :
T extends boolean[] ? AnyArray<BooleanSchemaDefinition> | AnyArray<SchemaTypeOptions<boolean, EnforcedDocType, THydratedDocumentType>> :
T extends Function[] ? AnyArray<Function | string> | AnyArray<SchemaTypeOptions<Unpacked<T>, EnforcedDocType, THydratedDocumentType>> :
T | typeof SchemaType | Schema<any, any, any> | SchemaDefinition<T, EnforcedDocType, THydratedDocumentType> | Function | AnyArray<Function>;

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

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

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

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

/**
* The model that `populate()` should use if populating this path.
Expand Down
27 changes: 18 additions & 9 deletions types/validation.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
declare module 'mongoose' {

type SchemaValidator<T, EnforcedDocType> = RegExp | [RegExp, string] | Function | [Function, string] | ValidateOpts<T, EnforcedDocType> | ValidateOpts<T, EnforcedDocType>[];
type SchemaValidator<T, THydratedDocumentType> = RegExp |
[RegExp, string] |
Function |
[Function, string] |
ValidateOpts<T, THydratedDocumentType> |
ValidateOpts<T, THydratedDocumentType>[];

interface ValidatorProps {
path: string;
Expand All @@ -13,18 +17,23 @@ declare module 'mongoose' {
(props: ValidatorProps): string;
}

type ValidateFn<T, EnforcedDocType> =
(this: EnforcedDocType, value: any, props?: ValidatorProps & Record<string, any>) => boolean;
type ValidateFn<T, THydratedDocumentType> = (
this: THydratedDocumentType | Query<unknown, THydratedDocumentType>,
value: any,
props?: ValidatorProps & Record<string, any>
) => boolean;

type AsyncValidateFn<T, EnforcedDocType> =
(this: EnforcedDocType, value: any, props?: ValidatorProps & Record<string, any>) => Promise<boolean>;
type AsyncValidateFn<T, THydratedDocumentType> = (
this: THydratedDocumentType | Query<unknown, THydratedDocumentType>,
value: any,
props?: ValidatorProps & Record<string, any>
) => Promise<boolean>;

interface ValidateOpts<T, EnforcedDocType> {
interface ValidateOpts<T, THydratedDocumentType> {
msg?: string;
message?: string | ValidatorMessageFn;
type?: string;
validator: ValidateFn<T, EnforcedDocType>
| AsyncValidateFn<T, EnforcedDocType>;
validator: ValidateFn<T, THydratedDocumentType> | AsyncValidateFn<T, THydratedDocumentType>;
propsParameter?: boolean;
}
}

0 comments on commit 032aa1c

Please sign in to comment.