Skip to content
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

Improve Auto typed schema. #12042

Merged
merged 8 commits into from Jul 15, 2022
Merged
76 changes: 74 additions & 2 deletions test/types/schema.test.ts
Expand Up @@ -429,7 +429,7 @@ export function autoTypedSchema() {
mixed3: Schema.Types.Mixed,
objectId1: Schema.Types.ObjectId,
objectId2: 'ObjectId',
objectId3: 'objectId',
objectId3: 'ObjectID',
customSchema: Int8,
map1: { type: Map, of: String },
map2: { type: Map, of: Number },
Expand Down Expand Up @@ -540,7 +540,7 @@ export type AutoTypedSchemaType = {
favoritDrink?: 'Tea' | 'Coffee',
favoritColorMode: 'dark' | 'light'
friendID?: Types.ObjectId;
nestedArray: Array<{
nestedArray: Types.DocumentArray<{
date: Date;
messages?: number;
}>
Expand Down Expand Up @@ -630,3 +630,75 @@ function gh11987() {
expectError(userSchema.path<'foo'>('name'));
expectType<SchemaTypeOptions<string>>(userSchema.path<'name'>('name').OptionsConstructor);
}

function gh12030() {
const Schema1 = new Schema({
users: [
{
username: { type: String }
}
]
});

expectType<{
users: {
username?: string
}[];
}>({} as InferSchemaType<typeof Schema1>);

const Schema2 = new Schema({
createdAt: { type: Date, default: Date.now }
});

expectType<{ createdAt: Date }>({} as InferSchemaType<typeof Schema2>);

const Schema3 = new Schema({
users: [
new Schema({
username: { type: String },
credit: { type: Number, default: 0 }
})
]
});

expectType<{
users: Types.DocumentArray<{
credit: number;
username?: string;
}>;
}>({} as InferSchemaType<typeof Schema3>);


const Schema4 = new Schema({
data: { type: { role: String }, default: {} }
});

expectType<{ data: { role?: string } }>({} as InferSchemaType<typeof Schema4>);

const Schema5 = new Schema({
data: { type: { role: Object }, default: {} }
});

expectType<{ data: { role?: any } }>({} as InferSchemaType<typeof Schema5>);

const Schema6 = new Schema({
track: {
backupCount: {
type: Number,
default: 0
},
count: {
type: Number,
default: 0
}
}
});

expectType<{
track?: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per discussion in #12030, this should ideally be track: { backupCount: number; count: number; }. But I don't think that's significant enough to block merging and shipping this PR, because it's a meaningful improvement. Feel free to put in a separate PR.

backupCount: number;
count: number;
};
}>({} as InferSchemaType<typeof Schema6>);

}
36 changes: 26 additions & 10 deletions types/inferschematype.d.ts
@@ -1,4 +1,18 @@
import { Schema, InferSchemaType, SchemaType, SchemaTypeOptions, TypeKeyBaseType, Types, NumberSchemaDefinition, StringSchemaDefinition, BooleanSchemaDefinition, DateSchemaDefinition } from 'mongoose';
import {
Schema,
InferSchemaType,
SchemaType,
SchemaTypeOptions,
TypeKeyBaseType,
Types,
NumberSchemaDefinition,
StringSchemaDefinition,
BooleanSchemaDefinition,
DateSchemaDefinition,
ObtainDocumentType,
DefaultTypeKey,
ObjectIdSchemaDefinition
} from 'mongoose';

declare module 'mongoose' {
/**
Expand Down Expand Up @@ -75,9 +89,9 @@ type IsPathRequired<P, TypeKey extends TypeKeyBaseType> =
? P extends { default: undefined }
? false
: true
: P extends (Record<TypeKey, NumberSchemaDefinition | StringSchemaDefinition | BooleanSchemaDefinition | DateSchemaDefinition>)
? P extends { default: ResolvePathType<P[TypeKey]> }
? true
: P extends (Record<TypeKey, any>)
? P extends { default: any }
? IfEquals<P['default'], undefined, false, true>
: false
: false;

Expand Down Expand Up @@ -138,7 +152,8 @@ type ObtainDocumentPathType<PathValueType, TypeKey extends TypeKeyBaseType> = Pa
? InferSchemaType<PathValueType>
: ResolvePathType<
PathValueType extends PathWithTypePropertyBaseType<TypeKey> ? PathValueType[TypeKey] : PathValueType,
PathValueType extends PathWithTypePropertyBaseType<TypeKey> ? Omit<PathValueType, TypeKey> : {}
PathValueType extends PathWithTypePropertyBaseType<TypeKey> ? Omit<PathValueType, TypeKey> : {},
TypeKey
>;

/**
Expand All @@ -151,23 +166,24 @@ type PathEnumOrString<T extends SchemaTypeOptions<string>['enum']> = T extends (
* @summary Resolve path type by returning the corresponding type.
* @param {PathValueType} PathValueType Document definition path type.
* @param {Options} Options Document definition path options except path type.
* @returns Number, "Number" or "number" will be resolved to string type.
* @param {TypeKey} TypeKey A generic of literal string type."Refers to the property used for path type definition".
* @returns Number, "Number" or "number" will be resolved to number type.
*/
type ResolvePathType<PathValueType, Options extends SchemaTypeOptions<PathValueType> = {}> =
type ResolvePathType<PathValueType, Options extends SchemaTypeOptions<PathValueType> = {}, TypeKey extends TypeKeyBaseType = DefaultTypeKey> =
PathValueType extends Schema ? InferSchemaType<PathValueType> :
PathValueType extends (infer Item)[] ? IfEquals<Item, never, any, ResolvePathType<Item>>[] :
PathValueType extends (infer Item)[] ? IfEquals<Item, never, any[], Item extends Schema ? Types.DocumentArray<ResolvePathType<Item>> : ResolvePathType<Item>[]> :
PathValueType extends StringSchemaDefinition ? PathEnumOrString<Options['enum']> :
PathValueType extends NumberSchemaDefinition ? number :
PathValueType extends DateSchemaDefinition ? Date :
PathValueType extends typeof Buffer | 'buffer' | 'Buffer' | typeof Schema.Types.Buffer ? Buffer :
PathValueType extends BooleanSchemaDefinition ? boolean :
PathValueType extends 'objectId' | 'ObjectId' | typeof Schema.Types.ObjectId ? Types.ObjectId :
PathValueType extends ObjectIdSchemaDefinition ? Types.ObjectId :
PathValueType extends 'decimal128' | 'Decimal128' | typeof Schema.Types.Decimal128 ? Types.Decimal128 :
PathValueType extends MapConstructor ? Map<string, ResolvePathType<Options['of']>> :
PathValueType extends ArrayConstructor ? any[] :
PathValueType extends typeof Schema.Types.Mixed ? any:
IfEquals<PathValueType, ObjectConstructor> extends true ? any:
IfEquals<PathValueType, {}> extends true ? any:
PathValueType extends typeof SchemaType ? PathValueType['prototype'] :
PathValueType extends {} ? PathValueType :
PathValueType extends Record<string, any> ? ObtainDocumentType<PathValueType, any, TypeKey> :
unknown;