Skip to content

Commit

Permalink
types: WIP removing FlattenMaps and inferring hydrated doc type from …
Browse files Browse the repository at this point in the history
…schema definition re: #13523
  • Loading branch information
vkarpov15 committed Sep 1, 2023
1 parent 68790ee commit 2e7ad9a
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 32 deletions.
2 changes: 1 addition & 1 deletion test/types/lean.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ async function gh13345_2() {
const PlaceModel = model('Place', placeSchema);

const place = await PlaceModel.findOne().lean().orFail().exec();
expectAssignable<FlattenMaps<Place>>(place);
expectAssignable<Place>(place);
expectType<Record<string, string>>(place.images[0].description);
}

Expand Down
2 changes: 1 addition & 1 deletion test/types/models.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ async function gh13705() {
const schema = new Schema({ name: String });
const TestModel = model('Test', schema);

type ExpectedLeanDoc = (mongoose.FlattenMaps<{ name?: string }> & { _id: mongoose.Types.ObjectId });
type ExpectedLeanDoc = ({ name?: string } & { _id: mongoose.Types.ObjectId });

const findByIdRes = await TestModel.findById('0'.repeat(24), undefined, { lean: true });
expectType<ExpectedLeanDoc | null>(findByIdRes);
Expand Down
12 changes: 9 additions & 3 deletions test/types/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
SchemaType,
Types,
Query,
model
model,
InferHydratedDocumentType
} from 'mongoose';
import { expectType, expectError, expectAssignable } from 'tsd';
import { ObtainDocumentPathType, ResolvePathType } from '../../types/inferschematype';
Expand Down Expand Up @@ -403,8 +404,8 @@ export function autoTypedSchema() {
objectId2?: Types.ObjectId;
objectId3?: Types.ObjectId;
customSchema?: Int8;
map1?: Map<string, string>;
map2?: Map<string, number>;
map1?: Record<string, string>;
map2?: Record<string, number>;
array1: string[];
array2: any[];
array3: any[];
Expand Down Expand Up @@ -1133,6 +1134,11 @@ function maps() {
});
const Test = model('Test', schema);

type Test1 = ObtainSchemaGeneric<typeof schema, 'DocType'>;
expectType<Record<string, number>>({} as Test1['myMap']);
type THydratedDocumentType = InferHydratedDocumentType<typeof schema>;
expectType<Map<string, number>>({} as THydratedDocumentType['myMap']);

const doc = new Test({ myMap: { answer: 42 } });
expectType<Map<string, number>>(doc.myMap);
expectType<number | undefined>(doc.myMap!.get('answer'));
Expand Down
28 changes: 16 additions & 12 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,16 @@ declare module 'mongoose' {
collection?: string,
options?: CompileModelOptions
): Model<
InferSchemaType<TSchema>,
ObtainSchemaGeneric<TSchema, 'TQueryHelpers'>,
ObtainSchemaGeneric<TSchema, 'TInstanceMethods'>,
ObtainSchemaGeneric<TSchema, 'TVirtuals'>,
HydratedDocument<
InferSchemaType<TSchema>,
ObtainSchemaGeneric<TSchema, 'TVirtuals'> & ObtainSchemaGeneric<TSchema, 'TInstanceMethods'>,
ObtainSchemaGeneric<TSchema, 'TQueryHelpers'>
>,
TSchema
InferSchemaType<TSchema>,
ObtainSchemaGeneric<TSchema, 'TQueryHelpers'>,
ObtainSchemaGeneric<TSchema, 'TInstanceMethods'>,
ObtainSchemaGeneric<TSchema, 'TVirtuals'>,
HydratedDocument<
InferSchemaType<TSchema>,
ObtainSchemaGeneric<TSchema, 'TVirtuals'> & ObtainSchemaGeneric<TSchema, 'TInstanceMethods'>,
ObtainSchemaGeneric<TSchema, 'TQueryHelpers'>
>,
TSchema
> & ObtainSchemaGeneric<TSchema, 'TStaticMethods'>;

export function model<T>(name: string, schema?: Schema<T, any, any> | Schema<T & Document, any, any>, collection?: string, options?: CompileModelOptions): Model<T>;
Expand Down Expand Up @@ -230,7 +230,11 @@ declare module 'mongoose' {
ObtainDocumentType<any, EnforcedDocType, ResolveSchemaOptions<TSchemaOptions>>,
ResolveSchemaOptions<TSchemaOptions>
>,
THydratedDocumentType = HydratedDocument<DocType, TVirtuals & TInstanceMethods>
THydratedDocumentType = InferHydratedDocumentType<
DocType,
EnforcedDocType,
ResolveSchemaOptions<TSchemaOptions>
> //HydratedDocument<DocType, TVirtuals & TInstanceMethods>
>
extends events.EventEmitter {
/**
Expand Down Expand Up @@ -658,7 +662,7 @@ declare module 'mongoose' {
type FlattenProperty<T> = T extends Map<any, infer V>
? Record<string, V> : T extends TreatAsPrimitives
? T : T extends Types.DocumentArray<infer ItemType>
? Types.DocumentArray<FlattenMaps<ItemType>> : FlattenMaps<T>;
? Types.DocumentArray<ItemType> : T;

export type actualPrimitives = string | boolean | number | bigint | symbol | null | undefined;
export type TreatAsPrimitives = actualPrimitives | NativeDate | RegExp | symbol | Error | BigInt | Types.ObjectId | Buffer | Function;
Expand Down
66 changes: 52 additions & 14 deletions types/inferschematype.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,35 @@ declare module 'mongoose' {
* @param {EnforcedDocType} EnforcedDocType A generic type enforced by user "provided before schema constructor".
* @param {TypeKey} TypeKey A generic of literal string type."Refers to the property used for path type definition".
*/
type ObtainDocumentType<DocDefinition, EnforcedDocType = any, TSchemaOptions extends Record<any, any> = DefaultSchemaOptions> =
IsItRecordAndNotAny<EnforcedDocType> extends true ? EnforcedDocType : {
[K in keyof (RequiredPaths<DocDefinition, TSchemaOptions['typeKey']> &
OptionalPaths<DocDefinition, TSchemaOptions['typeKey']>)]: ObtainDocumentPathType<DocDefinition[K], TSchemaOptions['typeKey']>;
};
type ObtainDocumentType<
DocDefinition,
EnforcedDocType = any,
TSchemaOptions extends Record<any, any> = DefaultSchemaOptions,
UseHydratedType = false
> =
IsItRecordAndNotAny<EnforcedDocType> extends true ? EnforcedDocType : {
[
K in keyof (RequiredPaths<DocDefinition, TSchemaOptions['typeKey']> &
OptionalPaths<DocDefinition, TSchemaOptions['typeKey']>)
]: ObtainDocumentPathType<
DocDefinition[K],
TSchemaOptions['typeKey'],
UseHydratedType
>;
};

/**
/**
* @summary Obtains document schema type from Schema instance.
* @param {Schema} TSchema `typeof` a schema instance.
* @example
* const userSchema = new Schema({userName:String});
* type UserType = InferSchemaType<typeof userSchema>;
* // result
* type UserType = {userName?: string}
*/
export type InferSchemaType<TSchema> = IfAny<TSchema, any, ObtainSchemaGeneric<TSchema, 'DocType'>>;

/**
* @summary Obtains document schema type from Schema instance.
* @param {Schema} TSchema `typeof` a schema instance.
* @example
Expand All @@ -39,7 +61,13 @@ declare module 'mongoose' {
* // result
* type UserType = {userName?: string}
*/
export type InferSchemaType<TSchema> = IfAny<TSchema, any, ObtainSchemaGeneric<TSchema, 'DocType'>>;
export type InferHydratedDocumentType<
DocDefinition,
EnforcedDocType = any,
TSchemaOptions extends Record<any, any> = DefaultSchemaOptions
> = HydratedDocument<
ObtainDocumentType<DocDefinition, EnforcedDocType, TSchemaOptions, true>
>;

/**
* @summary Obtains schema Generic type by using generic alias.
Expand Down Expand Up @@ -155,10 +183,15 @@ type OptionalPaths<T, TypeKey extends string = DefaultTypeKey> = {
* @param {PathValueType} PathValueType Document definition path type.
* @param {TypeKey} TypeKey A generic refers to document definition.
*/
type ObtainDocumentPathType<PathValueType, TypeKey extends string = DefaultTypeKey> = ResolvePathType<
PathValueType extends PathWithTypePropertyBaseType<TypeKey> ? PathValueType[TypeKey] : PathValueType,
PathValueType extends PathWithTypePropertyBaseType<TypeKey> ? Omit<PathValueType, TypeKey> : {},
TypeKey
type ObtainDocumentPathType<
PathValueType,
TypeKey extends string = DefaultTypeKey,
UseHydratedType = false
> = ResolvePathType<
PathValueType extends PathWithTypePropertyBaseType<TypeKey> ? PathValueType[TypeKey] : PathValueType,
PathValueType extends PathWithTypePropertyBaseType<TypeKey> ? Omit<PathValueType, TypeKey> : {},
TypeKey,
UseHydratedType
>;

/**
Expand All @@ -174,7 +207,12 @@ type PathEnumOrString<T extends SchemaTypeOptions<string>['enum']> = T extends R
* @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']> =
type ResolvePathType<
PathValueType,
Options extends SchemaTypeOptions<PathValueType> = {},
TypeKey extends string = DefaultSchemaOptions['typeKey'],
UseHydratedType = false
> =
PathValueType extends Schema ? InferSchemaType<PathValueType> :
PathValueType extends (infer Item)[] ?
IfEquals<Item, never, any[], Item extends Schema ?
Expand Down Expand Up @@ -219,8 +257,8 @@ type ResolvePathType<PathValueType, Options extends SchemaTypeOptions<PathValueT
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<string, ResolvePathType<Options['of']>> :
IfEquals<PathValueType, typeof Schema.Types.Map> extends true ? Map<string, ResolvePathType<Options['of']>> :
PathValueType extends MapConstructor ? (UseHydratedType extends true ? Map<string, ResolvePathType<Options['of'], {}, DefaultSchemaOptions['typeKey'], UseHydratedType>> : Record<string, ResolvePathType<Options['of'], {}, DefaultSchemaOptions['typeKey'], UseHydratedType>>) :
IfEquals<PathValueType, typeof Schema.Types.Map> extends true ? (UseHydratedType extends true ? Map<string, ResolvePathType<Options['of'], {}, DefaultSchemaOptions['typeKey'], UseHydratedType>> : Record<string, ResolvePathType<Options['of'], {}, DefaultSchemaOptions['typeKey'], UseHydratedType>>) :
PathValueType extends ArrayConstructor ? any[] :
PathValueType extends typeof Schema.Types.Mixed ? any:
IfEquals<PathValueType, ObjectConstructor> extends true ? any:
Expand Down
2 changes: 1 addition & 1 deletion types/query.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ declare module 'mongoose' {
type QueryOpThatReturnsDocument = 'find' | 'findOne' | 'findOneAndUpdate' | 'findOneAndReplace' | 'findOneAndDelete';

type GetLeanResultType<RawDocType, ResultType, QueryOp> = QueryOp extends QueryOpThatReturnsDocument
? (ResultType extends any[] ? Require_id<FlattenMaps<RawDocType>>[] : Require_id<FlattenMaps<RawDocType>>)
? (ResultType extends any[] ? Require_id<RawDocType>[] : Require_id<RawDocType>)
: ResultType;

class Query<ResultType, DocType, THelpers = {}, RawDocType = DocType, QueryOp = 'find'> implements SessionOperation {
Expand Down

0 comments on commit 2e7ad9a

Please sign in to comment.