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

Support type hints in InferSchemaType #14008

Merged
merged 3 commits into from Dec 3, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 16 additions & 0 deletions test/types/schema.test.ts
Expand Up @@ -1231,6 +1231,22 @@ async function gh13797() {
} } });
}

declare const brand: unique symbol;
function gh14002() {
type Brand<T, U extends string> = T & { [brand]: U };
type UserId = Brand<string, 'UserId'>;

interface IUser {
userId: UserId;
}

const userIdTypeHint = 'placeholder' as UserId;
const schema = new Schema({
userId: { type: String, required: true, __typehint: userIdTypeHint }
});
expectType<IUser>({} as InferSchemaType<typeof schema>);
}

function gh14028_methods() {
// Methods that have access to `this` should have access to typing of other methods on the schema
interface IUser {
Expand Down
117 changes: 63 additions & 54 deletions types/inferschematype.d.ts
Expand Up @@ -159,6 +159,12 @@ type OptionalPaths<T, TypeKey extends string = DefaultTypeKey> = {
[K in OptionalPathKeys<T, TypeKey>]?: T[K];
};

/**
* @summary Allows users to optionally choose their own type for a schema field for stronger typing.
*/
type TypeHint<T> = T extends { __typehint: infer U } ? U: never;


/**
* @summary Obtains schema Path type.
* @description Obtains Path type by separating path type from other options and calling {@link ResolvePathType}
Expand All @@ -168,7 +174,8 @@ type OptionalPaths<T, TypeKey extends string = DefaultTypeKey> = {
type ObtainDocumentPathType<PathValueType, TypeKey extends string = DefaultTypeKey> = ResolvePathType<
PathValueType extends PathWithTypePropertyBaseType<TypeKey> ? PathValueType[TypeKey] : PathValueType,
PathValueType extends PathWithTypePropertyBaseType<TypeKey> ? Omit<PathValueType, TypeKey> : {},
TypeKey
TypeKey,
TypeHint<PathValueType>
>;

/**
Expand Down Expand Up @@ -208,35 +215,21 @@ type IsSchemaTypeFromBuiltinClass<T> = T extends (typeof String)
* @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> = {}, TypeKey extends string = DefaultSchemaOptions['typeKey']> =
PathValueType extends Schema ? InferSchemaType<PathValueType> :
PathValueType extends (infer Item)[] ?
IfEquals<Item, never, any[], Item extends Schema ?
// If Item is a schema, infer its type.
Types.DocumentArray<InferSchemaType<Item>> :
Item extends Record<TypeKey, any> ?
Item[TypeKey] extends Function | String ?
// If Item has a type key that's a string or a callable, it must be a scalar,
// so we can directly obtain its path type.
ObtainDocumentPathType<Item, TypeKey>[] :
// If the type key isn't callable, then this is an array of objects, in which case
// we need to call ObtainDocumentType to correctly infer its type.
ObtainDocumentType<Item, any, { typeKey: TypeKey }>[] :
IsSchemaTypeFromBuiltinClass<Item> extends true ?
ObtainDocumentPathType<Item, TypeKey>[] :
IsItRecordAndNotAny<Item> extends true ?
Item extends Record<string, never> ?
ObtainDocumentPathType<Item, TypeKey>[] :
Types.DocumentArray<ObtainDocumentType<Item, any, { typeKey: TypeKey }>> :
ObtainDocumentPathType<Item, TypeKey>[]
>:
PathValueType extends ReadonlyArray<infer Item> ?
type ResolvePathType<PathValueType, Options extends SchemaTypeOptions<PathValueType> = {}, TypeKey extends string = DefaultSchemaOptions['typeKey'], TypeHint = never> =
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change looks large, but what it boils down to is:

"If TypeHint is not never, use the TypeHint, otherwise infer it as we did before"

IfEquals<TypeHint, never,
PathValueType extends Schema ? InferSchemaType<PathValueType> :
PathValueType extends (infer Item)[] ?
IfEquals<Item, never, any[], Item extends Schema ?
// If Item is a schema, infer its type.
Types.DocumentArray<InferSchemaType<Item>> :
Item extends Record<TypeKey, any> ?
Item[TypeKey] extends Function | String ?
// If Item has a type key that's a string or a callable, it must be a scalar,
// so we can directly obtain its path type.
ObtainDocumentPathType<Item, TypeKey>[] :
ObtainDocumentType<Item, any, { typeKey: TypeKey }>[]:
// If the type key isn't callable, then this is an array of objects, in which case
// we need to call ObtainDocumentType to correctly infer its type.
ObtainDocumentType<Item, any, { typeKey: TypeKey }>[] :
IsSchemaTypeFromBuiltinClass<Item> extends true ?
ObtainDocumentPathType<Item, TypeKey>[] :
IsItRecordAndNotAny<Item> extends true ?
Expand All @@ -245,32 +238,48 @@ type ResolvePathType<PathValueType, Options extends SchemaTypeOptions<PathValueT
Types.DocumentArray<ObtainDocumentType<Item, any, { typeKey: TypeKey }>> :
ObtainDocumentPathType<Item, TypeKey>[]
>:
PathValueType extends StringSchemaDefinition ? PathEnumOrString<Options['enum']> :
IfEquals<PathValueType, Schema.Types.String> extends true ? PathEnumOrString<Options['enum']> :
IfEquals<PathValueType, String> extends true ? PathEnumOrString<Options['enum']> :
PathValueType extends NumberSchemaDefinition ? Options['enum'] extends ReadonlyArray<any> ? Options['enum'][number] : number :
IfEquals<PathValueType, Schema.Types.Number> extends true ? number :
PathValueType extends DateSchemaDefinition ? Date :
IfEquals<PathValueType, Schema.Types.Date> extends true ? Date :
PathValueType extends typeof Buffer | 'buffer' | 'Buffer' | typeof Schema.Types.Buffer ? Buffer :
PathValueType extends BooleanSchemaDefinition ? boolean :
IfEquals<PathValueType, Schema.Types.Boolean> extends true ? boolean :
PathValueType extends ObjectIdSchemaDefinition ? Types.ObjectId :
IfEquals<PathValueType, Types.ObjectId> extends true ? Types.ObjectId :
IfEquals<PathValueType, Schema.Types.ObjectId> extends true ? Types.ObjectId :
PathValueType extends 'decimal128' | 'Decimal128' | typeof Schema.Types.Decimal128 ? Types.Decimal128 :
IfEquals<PathValueType, Schema.Types.Decimal128> extends true ? Types.Decimal128 :
IfEquals<PathValueType, Types.Decimal128> extends true ? Types.Decimal128 :
IfEquals<PathValueType, Schema.Types.BigInt> extends true ? bigint :
PathValueType extends 'bigint' | 'BigInt' | typeof Schema.Types.BigInt ? bigint :
PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? Buffer :
IfEquals<PathValueType, Schema.Types.UUID> extends true ? Buffer :
PathValueType extends MapConstructor | 'Map' ? Map<string, ResolvePathType<Options['of']>> :
IfEquals<PathValueType, typeof Schema.Types.Map> extends true ? 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 Record<string, any> ? ObtainDocumentType<PathValueType, any, { typeKey: TypeKey }> :
unknown;
PathValueType extends ReadonlyArray<infer Item> ?
IfEquals<Item, never, any[], Item extends Schema ?
Types.DocumentArray<InferSchemaType<Item>> :
Item extends Record<TypeKey, any> ?
Item[TypeKey] extends Function | String ?
ObtainDocumentPathType<Item, TypeKey>[] :
ObtainDocumentType<Item, any, { typeKey: TypeKey }>[]:
IsSchemaTypeFromBuiltinClass<Item> extends true ?
ObtainDocumentPathType<Item, TypeKey>[] :
IsItRecordAndNotAny<Item> extends true ?
Item extends Record<string, never> ?
ObtainDocumentPathType<Item, TypeKey>[] :
Types.DocumentArray<ObtainDocumentType<Item, any, { typeKey: TypeKey }>> :
ObtainDocumentPathType<Item, TypeKey>[]
>:
PathValueType extends StringSchemaDefinition ? PathEnumOrString<Options['enum']> :
IfEquals<PathValueType, Schema.Types.String> extends true ? PathEnumOrString<Options['enum']> :
IfEquals<PathValueType, String> extends true ? PathEnumOrString<Options['enum']> :
PathValueType extends NumberSchemaDefinition ? Options['enum'] extends ReadonlyArray<any> ? Options['enum'][number] : number :
IfEquals<PathValueType, Schema.Types.Number> extends true ? number :
PathValueType extends DateSchemaDefinition ? Date :
IfEquals<PathValueType, Schema.Types.Date> extends true ? Date :
PathValueType extends typeof Buffer | 'buffer' | 'Buffer' | typeof Schema.Types.Buffer ? Buffer :
PathValueType extends BooleanSchemaDefinition ? boolean :
IfEquals<PathValueType, Schema.Types.Boolean> extends true ? boolean :
PathValueType extends ObjectIdSchemaDefinition ? Types.ObjectId :
IfEquals<PathValueType, Types.ObjectId> extends true ? Types.ObjectId :
IfEquals<PathValueType, Schema.Types.ObjectId> extends true ? Types.ObjectId :
PathValueType extends 'decimal128' | 'Decimal128' | typeof Schema.Types.Decimal128 ? Types.Decimal128 :
IfEquals<PathValueType, Schema.Types.Decimal128> extends true ? Types.Decimal128 :
IfEquals<PathValueType, Types.Decimal128> extends true ? Types.Decimal128 :
IfEquals<PathValueType, Schema.Types.BigInt> extends true ? bigint :
PathValueType extends 'bigint' | 'BigInt' | typeof Schema.Types.BigInt ? bigint :
PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? Buffer :
IfEquals<PathValueType, Schema.Types.UUID> extends true ? Buffer :
PathValueType extends MapConstructor | 'Map' ? Map<string, ResolvePathType<Options['of']>> :
IfEquals<PathValueType, typeof Schema.Types.Map> extends true ? 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 Record<string, any> ? ObtainDocumentType<PathValueType, any, { typeKey: TypeKey }> :
unknown,
TypeHint>;