Skip to content

Commit

Permalink
Merge pull request #12884 from JavaScriptBach/array-type-fix
Browse files Browse the repository at this point in the history
Correctly infer types on document arrays
  • Loading branch information
vkarpov15 authored Jan 17, 2023
2 parents 256a11e + 054b704 commit ed8eacf
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 10 deletions.
102 changes: 102 additions & 0 deletions test/types/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1018,3 +1018,105 @@ function gh12869() {
type Example = InferSchemaType<typeof dbExample>;
expectType<'foo' | 'bar'>({} as Example['active']);
}

function gh12882() {
// Array of strings
const arrString = new Schema({
fooArray: {
type: [{
type: String,
required: true
}],
required: true
}
});
type tArrString = InferSchemaType<typeof arrString>;
// Array of numbers using string definition
const arrNum = new Schema({
fooArray: {
type: [{
type: 'Number',
required: true
}],
required: true
}
});
type tArrNum = InferSchemaType<typeof arrNum>;
expectType<{
fooArray: number[]
}>({} as tArrNum);
// Array of object with key named "type"
const arrType = new Schema({
fooArray: {
type: [{
type: {
type: String,
required: true
},
foo: {
type: Number,
required: true
}
}],
required: true
}
});
type tArrType = InferSchemaType<typeof arrType>;
expectType<{
fooArray: {
type: string;
foo: number;
}[]
}>({} as tArrType);
// Readonly array of strings
const rArrString = new Schema({
fooArray: {
type: [{
type: String,
required: true
}] as const,
required: true
}
});
type rTArrString = InferSchemaType<typeof rArrString>;
expectType<{
fooArray: string[]
}>({} as rTArrString);
// Readonly array of numbers using string definition
const rArrNum = new Schema({
fooArray: {
type: [{
type: 'Number',
required: true
}] as const,
required: true
}
});
type rTArrNum = InferSchemaType<typeof rArrNum>;
expectType<{
fooArray: number[]
}>({} as rTArrNum);
// Readonly array of object with key named "type"
const rArrType = new Schema({
fooArray: {
type: [{
type: {
type: String,
required: true
},
foo: {
type: Number,
required: true
}
}] as const,
required: true
}
});
type rTArrType = InferSchemaType<typeof rArrType>;
expectType<{
fooArray: {
type: string;
foo: number;
}[]
}>({} as rTArrType);
}
39 changes: 29 additions & 10 deletions types/inferschematype.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,17 +152,15 @@ type OptionalPaths<T, TypeKey extends string = DefaultTypeKey> = {

/**
* @summary Obtains schema Path type.
* @description Obtains Path type by calling {@link ResolvePathType} OR by calling {@link InferSchemaType} if path of schema type.
* @description Obtains Path type by separating path type from other options and calling {@link ResolvePathType}
* @param {PathValueType} PathValueType Document definition path type.
* @param {TypeKey} TypeKey A generic refers to document definition.
*/
type ObtainDocumentPathType<PathValueType, TypeKey extends string = DefaultTypeKey> = PathValueType extends Schema<any>
? InferSchemaType<PathValueType>
: ResolvePathType<
PathValueType extends PathWithTypePropertyBaseType<TypeKey> ? PathValueType[TypeKey] : PathValueType,
PathValueType extends PathWithTypePropertyBaseType<TypeKey> ? Omit<PathValueType, TypeKey> : {},
TypeKey
>;
type ObtainDocumentPathType<PathValueType, TypeKey extends string = DefaultTypeKey> = ResolvePathType<
PathValueType extends PathWithTypePropertyBaseType<TypeKey> ? PathValueType[TypeKey] : PathValueType,
PathValueType extends PathWithTypePropertyBaseType<TypeKey> ? Omit<PathValueType, TypeKey> : {},
TypeKey
>;

/**
* @param {T} T A generic refers to string path enums.
Expand All @@ -179,8 +177,29 @@ type PathEnumOrString<T extends SchemaTypeOptions<string>['enum']> = T extends R
*/
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 ? Types.DocumentArray<ObtainDocumentPathType<Item, TypeKey>> : ObtainDocumentPathType<Item, TypeKey>[]> :
PathValueType extends ReadonlyArray<infer Item> ? IfEquals<Item, never, any[], Item extends Schema ? Types.DocumentArray<ObtainDocumentPathType<Item, TypeKey>> : ObtainDocumentPathType<Item, TypeKey>[]> :
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 }>[]:
ObtainDocumentPathType<Item, TypeKey>[]
>:
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 }>[]:
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']> :
Expand Down

0 comments on commit ed8eacf

Please sign in to comment.