Skip to content

Commit

Permalink
Merge pull request #12007 from iammola/inferschematype
Browse files Browse the repository at this point in the history
馃悰fix: improve inferred Schema Type
  • Loading branch information
vkarpov15 committed Jul 1, 2022
2 parents 2eb9314 + ca6f97b commit 87a6edc
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 45 deletions.
51 changes: 39 additions & 12 deletions test/types/schema.test.ts
Expand Up @@ -2,7 +2,6 @@ import {
Schema,
Document,
SchemaDefinition,
SchemaDefinitionProperty,
SchemaTypeOptions,
Model,
Types,
Expand Down Expand Up @@ -359,14 +358,17 @@ export function autoTypedSchema() {
string2?: string;
string3?: string;
string4?: string;
string5: string;
number1?: number;
number2?: number;
number3?: number;
number4?: number;
number5: number;
date1?: Date;
date2?: Date;
date3?: Date;
date4?: Date;
date5: Date;
buffer1?: Buffer;
buffer2?: Buffer;
buffer3?: Buffer;
Expand All @@ -375,38 +377,44 @@ export function autoTypedSchema() {
boolean2?: boolean;
boolean3?: boolean;
boolean4?: boolean;
boolean5: boolean;
mixed1?: any;
mixed2?: any;
mixed3?: any;
objectId1?: Schema.Types.ObjectId;
objectId2?: Schema.Types.ObjectId;
objectId3?: Schema.Types.ObjectId;
objectId1?: Types.ObjectId;
objectId2?: Types.ObjectId;
objectId3?: Types.ObjectId;
customSchema?: Int8;
map1?: Map<string, string>;
map2?: Map<string, number>;
array1?: string[];
array2?: any[];
array3?: any[];
array4?: any[];
array5?: any[];
decimal1?: Schema.Types.Decimal128;
decimal2?: Schema.Types.Decimal128;
decimal3?: Schema.Types.Decimal128;
array1: string[];
array2: any[];
array3: any[];
array4: any[];
array5: any[];
array6: string[];
array7?: string[];
decimal1?: Types.Decimal128;
decimal2?: Types.Decimal128;
decimal3?: Types.Decimal128;
};

const TestSchema = new Schema({
string1: String,
string2: 'String',
string3: 'string',
string4: Schema.Types.String,
string5: { type: String, default: 'ABCD' },
number1: Number,
number2: 'Number',
number3: 'number',
number4: Schema.Types.Number,
number5: { type: Number, default: 10 },
date1: Date,
date2: 'Date',
date3: 'date',
date4: Schema.Types.Date,
date5: { type: Date, default: new Date() },
buffer1: Buffer,
buffer2: 'Buffer',
buffer3: 'buffer',
Expand All @@ -415,6 +423,7 @@ export function autoTypedSchema() {
boolean2: 'Boolean',
boolean3: 'boolean',
boolean4: Schema.Types.Boolean,
boolean5: { type: Boolean, default: true },
mixed1: Object,
mixed2: {},
mixed3: Schema.Types.Mixed,
Expand All @@ -429,6 +438,8 @@ export function autoTypedSchema() {
array3: [Schema.Types.Mixed],
array4: [{}],
array5: [],
array6: { type: [String] },
array7: { type: [String], default: undefined },
decimal1: Schema.Types.Decimal128,
decimal2: 'Decimal128',
decimal3: 'decimal128'
Expand Down Expand Up @@ -476,6 +487,17 @@ export function autoTypedSchema() {
message: '{VALUE} is not supported'
},
required: true
},
friendID: {
type: Schema.Types.ObjectId
},
nestedArray: {
type: [
new Schema({
date: { type: Date, required: true },
messages: Number
})
]
}
}, {
statics: {
Expand Down Expand Up @@ -517,6 +539,11 @@ export type AutoTypedSchemaType = {
},
favoritDrink?: 'Tea' | 'Coffee',
favoritColorMode: 'dark' | 'light'
friendID?: Types.ObjectId;
nestedArray: Array<{
date: Date;
messages?: number;
}>
}
, statics: {
staticFn: () => 'Returned from staticFn'
Expand Down
84 changes: 51 additions & 33 deletions types/inferschematype.d.ts
@@ -1,18 +1,18 @@
import { Schema, InferSchemaType, SchemaType, SchemaTypeOptions, TypeKeyBaseType } from 'mongoose';
import { Schema, InferSchemaType, SchemaType, SchemaTypeOptions, TypeKeyBaseType, Types, NumberSchemaDefinition, StringSchemaDefinition, BooleanSchemaDefinition, DateSchemaDefinition } from 'mongoose';

declare module 'mongoose' {
/**
* @summary Obtains document schema type.
* @description Obtains document schema type from document Definition OR returns enforced schema type if it's provided.
* @param {DocDefinition} DocDefinition A generic equals to the type of document definition "provided in as first parameter in Schema constructor".
* @param {EnforcedDocType} EnforcedDocType A generic type enforced by user "provided before schema constructor".
* @param {TypeKey} TypeKey A generic of literal string type.
* @param {TypeKey} TypeKey A generic of literal string type."Refers to the property used for path type definition".
*/
type ObtainDocumentType<DocDefinition, EnforcedDocType = any, TypeKey extends TypeKeyBaseType = DefaultTypeKey> =
IsItRecordAndNotAny<EnforcedDocType> extends true ? EnforcedDocType : {
[K in keyof (RequiredPaths<DocDefinition> &
OptionalPaths<DocDefinition>)]: ObtainDocumentPathType<DocDefinition[K], TypeKey>;
};
type ObtainDocumentType<DocDefinition, EnforcedDocType = any, TypeKey extends TypeKeyBaseType = DefaultTypeKey> =
IsItRecordAndNotAny<EnforcedDocType> extends true ? EnforcedDocType : {
[K in keyof (RequiredPaths<DocDefinition, TypeKey> &
OptionalPaths<DocDefinition, TypeKey>)]: ObtainDocumentPathType<DocDefinition[K], TypeKey>;
};

/**
* @summary Obtains document schema type from Schema instance.
Expand Down Expand Up @@ -64,10 +64,22 @@ type IfEquals<T, U, Y = true, N = false> =
(<G>() => G extends U ? 1 : 0) ? Y : N;

/**
* @summary Required path base type.
* @description It helps to check whereas if a path is required OR optional.
* @summary Checks if a document path is required or optional.
* @param {P} P Document path.
* @param {TypeKey} TypeKey A generic of literal string type."Refers to the property used for path type definition".
*/
type RequiredPathBaseType = { required: true | [true, string | undefined] };
type IsPathRequired<P, TypeKey extends TypeKeyBaseType> =
P extends { required: true | [true, string | undefined] } | ArrayConstructor | any[]
? true
: P extends (Record<TypeKey, ArrayConstructor | any[]>)
? P extends { default: undefined }
? false
: true
: P extends (Record<TypeKey, NumberSchemaDefinition | StringSchemaDefinition | BooleanSchemaDefinition | DateSchemaDefinition>)
? P extends { default: ResolvePathType<P[TypeKey]> }
? true
: false
: false;

/**
* @summary Path base type defined by using TypeKey
Expand All @@ -79,37 +91,41 @@ type PathWithTypePropertyBaseType<TypeKey extends TypeKeyBaseType> = { [k in Typ
/**
* @summary A Utility to obtain schema's required path keys.
* @param {T} T A generic refers to document definition.
* @param {TypeKey} TypeKey A generic of literal string type."Refers to the property used for path type definition".
* @returns required paths keys of document definition.
*/
type RequiredPathKeys<T> = {
[K in keyof T]: T[K] extends RequiredPathBaseType ? IfEquals<T[K], any, never, K> : never;
type RequiredPathKeys<T, TypeKey extends TypeKeyBaseType> = {
[K in keyof T]: IsPathRequired<T[K], TypeKey> extends true ? IfEquals<T[K], any, never, K> : never;
}[keyof T];

/**
* @summary A Utility to obtain schema's required paths.
* @param {T} T A generic refers to document definition.
* @param {TypeKey} TypeKey A generic of literal string type."Refers to the property used for path type definition".
* @returns a record contains required paths with the corresponding type.
*/
type RequiredPaths<T> = {
[K in RequiredPathKeys<T>]: T[K];
type RequiredPaths<T, TypeKey extends TypeKeyBaseType> = {
[K in RequiredPathKeys<T, TypeKey>]: T[K];
};

/**
* @summary A Utility to obtain schema's optional path keys.
* @param {T} T A generic refers to document definition.
* @param {TypeKey} TypeKey A generic of literal string type."Refers to the property used for path type definition".
* @returns optional paths keys of document definition.
*/
type OptionalPathKeys<T> = {
[K in keyof T]: T[K] extends RequiredPathBaseType ? never : K;
type OptionalPathKeys<T, TypeKey extends TypeKeyBaseType> = {
[K in keyof T]: IsPathRequired<T[K], TypeKey> extends true ? never : K;
}[keyof T];

/**
* @summary A Utility to obtain schema's optional paths.
* @param {T} T A generic refers to document definition.
* @param {TypeKey} TypeKey A generic of literal string type."Refers to the property used for path type definition".
* @returns a record contains optional paths with the corresponding type.
*/
type OptionalPaths<T> = {
[K in OptionalPathKeys<T>]?: T[K];
type OptionalPaths<T, TypeKey extends TypeKeyBaseType> = {
[K in OptionalPathKeys<T, TypeKey>]?: T[K];
};

/**
Expand Down Expand Up @@ -138,18 +154,20 @@ type PathEnumOrString<T extends SchemaTypeOptions<string>['enum']> = T extends (
* @returns Number, "Number" or "number" will be resolved to string type.
*/
type ResolvePathType<PathValueType, Options extends SchemaTypeOptions<PathValueType> = {}> =
PathValueType extends (infer Item)[] ? IfEquals<Item, never, any, ResolvePathType<Item>>[] :
PathValueType extends StringConstructor | 'string' | 'String' | typeof Schema.Types.String ? PathEnumOrString<Options['enum']> :
PathValueType extends NumberConstructor | 'number' | 'Number' | typeof Schema.Types.Number ? number :
PathValueType extends DateConstructor | 'date' | 'Date' | typeof Schema.Types.Date ? Date :
PathValueType extends typeof Buffer | 'buffer' | 'Buffer' | typeof Schema.Types.Buffer ? Buffer :
PathValueType extends BooleanConstructor | 'boolean' | 'Boolean' | typeof Schema.Types.Boolean ? boolean :
PathValueType extends 'objectId' | 'ObjectId' | typeof Schema.Types.ObjectId ? Schema.Types.ObjectId :
PathValueType extends 'decimal128' | 'Decimal128' | typeof Schema.Types.Decimal128 ? Schema.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'] :
unknown;
PathValueType extends Schema ? InferSchemaType<PathValueType> :
PathValueType extends (infer Item)[] ? IfEquals<Item, never, any, 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 '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 :
unknown;

0 comments on commit 87a6edc

Please sign in to comment.