fix: inferring of relationship types (#7913)

FIXES: CORE-2448
This commit is contained in:
Harminder Virk
2024-07-03 13:29:27 +05:30
committed by GitHub
parent 837ed093e3
commit 46f15b4909
4 changed files with 201 additions and 162 deletions

View File

@@ -121,33 +121,71 @@ export interface EntityConstructor<Props> extends Function {
* From a IDmlEntity, infer the foreign keys name and type for
* "belongsTo" relation meaning "hasOne" and "ManyToOne"
*/
export type InferForeignKeys<T> = T extends IDmlEntity<infer Schema>
? {
[K in keyof Schema as Schema[K] extends { type: infer Type }
? Type extends RelationshipTypes
? `${K & string}_id`
: K
: K]: Schema[K] extends { type: infer Type }
? Type extends RelationshipTypes
? string
: Schema[K]
: Schema[K]
}
export type InferForeignKeys<Schema extends DMLSchema> = {
[K in keyof Schema as Schema[K] extends { type: infer Type }
? Type extends RelationshipTypes
? `${K & string}_id`
: K
: K]: Schema[K] extends { type: infer Type }
? Type extends RelationshipTypes
? string
: Schema[K]
: Schema[K]
}
/**
* Infer fields for a belongsTo relationship
*/
export type InferBelongsToFields<Relation> = Relation extends () => IDmlEntity<
infer R
>
? InferSchemaFields<R>
: Relation extends () => IDmlEntity<infer R> | null
? InferSchemaFields<R> | null
: never
/**
* Infer fields for a hasOne relationship
*/
export type InferHasOneFields<Relation> = InferBelongsToFields<Relation>
/**
* Infer fields for hasMany relationship
*/
export type InferHasManyFields<Relation> = Relation extends () => IDmlEntity<
infer R
>
? InferSchemaFields<R>[]
: never
/**
* Infer fields for manyToMany relationship
*/
export type InferManyToManyFields<Relation> = InferHasManyFields<Relation>
/**
* Inferring the types of the schema fields from the DML
* entity
*/
export type InferSchemaFields<Schema extends DMLSchema> = {
[K in keyof Schema]: Schema[K] extends RelationshipType<any>
? Schema[K]["type"] extends "belongsTo"
? InferBelongsToFields<Schema[K]["$dataType"]>
: Schema[K]["type"] extends "hasOne"
? InferHasOneFields<Schema[K]["$dataType"]>
: Schema[K]["type"] extends "hasMany"
? InferHasManyFields<Schema[K]["$dataType"]>
: Schema[K]["type"] extends "manyToMany"
? InferManyToManyFields<Schema[K]["$dataType"]>
: never
: Schema[K]["$dataType"]
}
/**
* Helper to infer the schema type of a DmlEntity
*/
export type Infer<T> = T extends IDmlEntity<infer Schema>
? EntityConstructor<
{
[K in keyof Schema]: Schema[K]["$dataType"] extends () => infer R
? Infer<R>
: Schema[K]["$dataType"] extends (() => infer R) | null
? Infer<R> | null
: Schema[K]["$dataType"]
} & InferForeignKeys<T>
>
? EntityConstructor<InferSchemaFields<Schema>>
: never
/**
@@ -200,7 +238,7 @@ export type InferIndexableProperties<T> = keyof (T extends IDmlEntity<
? never
: K
: K]: string
} & InferForeignKeys<T>
} & InferForeignKeys<Schema>
: never)
export type EntityIndex<

View File

@@ -1,4 +1,3 @@
import { EntityConstructor } from "@medusajs/types"
import { ArrayType, MetadataStorage } from "@mikro-orm/core"
import { expectTypeOf } from "expect-type"
import { DmlEntity } from "../entity"
@@ -77,7 +76,6 @@ describe("Entity builder", () => {
expect(user.parse().tableName).toEqual("user")
const User = toMikroORMEntity(user)
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
@@ -1808,15 +1806,20 @@ describe("Entity builder", () => {
})
const User = toMikroORMEntity(user)
expectTypeOf(new User()).toMatchTypeOf<{
expectTypeOf(new User()).toEqualTypeOf<{
id: number
username: string
created_at: Date
updated_at: Date
deleted_at: Date | null
email: EntityConstructor<{
email: {
email: string
isVerified: boolean
created_at: Date
updated_at: Date
deleted_at: Date | null
}>
}
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -1901,11 +1904,11 @@ describe("Entity builder", () => {
id: number
username: string
deleted_at: Date | null
emails: EntityConstructor<{
emails: {
email: string
isVerified: boolean
deleted_at: Date | null
}> | null
} | null
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -1988,7 +1991,7 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
email: EntityConstructor<{ email: string; isVerified: boolean }>
email: { email: string; isVerified: boolean }
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -2075,7 +2078,7 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
email: EntityConstructor<{ email: string; isVerified: boolean }>
email: { email: string; isVerified: boolean }
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -2221,14 +2224,14 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
email: EntityConstructor<{
email: {
email: string
isVerified: boolean
user: EntityConstructor<{
user: {
id: number
username: string
}>
}>
}
}
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -2661,7 +2664,7 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
emails: EntityConstructor<{ email: string; isVerified: boolean }>
emails: { email: string; isVerified: boolean }[]
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -2747,7 +2750,7 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
emails: EntityConstructor<{ email: string; isVerified: boolean }> | null
emails: { email: string; isVerified: boolean }[]
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -2834,7 +2837,7 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
emails: EntityConstructor<{ email: string; isVerified: boolean }>
emails: { email: string; isVerified: boolean }[]
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -2924,7 +2927,7 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
emails: EntityConstructor<{ email: string; isVerified: boolean }>
emails: { email: string; isVerified: boolean }[]
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -3087,32 +3090,32 @@ describe("Entity builder", () => {
id: number
username: string
deleted_at: Date | null
email: EntityConstructor<{
email: {
email: string
isVerified: boolean
deleted_at: Date | null
user: EntityConstructor<{
user: {
id: number
username: string
deleted_at: Date | null
}>
}>
}
}
}>()
expectTypeOf(new Email()).toMatchTypeOf<{
email: string
isVerified: boolean
deleted_at: Date | null
user: EntityConstructor<{
user: {
id: number
username: string
deleted_at: Date | null
email: EntityConstructor<{
email: {
email: string
isVerified: boolean
deleted_at: Date | null
}>
}>
}
}
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -3272,27 +3275,27 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
email: EntityConstructor<{
email: {
email: string
isVerified: boolean
user: EntityConstructor<{
user: {
id: number
username: string
}> | null
}>
} | null
}
}>()
expectTypeOf(new Email()).toMatchTypeOf<{
email: string
isVerified: boolean
user: EntityConstructor<{
user: {
id: number
username: string
email: EntityConstructor<{
email: {
email: string
isVerified: boolean
}>
}> | null
}
} | null
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -3452,27 +3455,27 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
emails: EntityConstructor<{
emails: {
email: string
isVerified: boolean
user: EntityConstructor<{
user: {
id: number
username: string
}>
}>
}
}[]
}>()
expectTypeOf(new Email()).toMatchTypeOf<{
email: string
isVerified: boolean
user: EntityConstructor<{
user: {
id: number
username: string
emails: EntityConstructor<{
emails: {
email: string
isVerified: boolean
}>
}>
}[]
}
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -3631,27 +3634,27 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
emails: EntityConstructor<{
emails: {
email: string
isVerified: boolean
user: EntityConstructor<{
user: {
id: number
username: string
}> | null
}>
} | null
}[]
}>()
expectTypeOf(new Email()).toMatchTypeOf<{
email: string
isVerified: boolean
user: EntityConstructor<{
user: {
id: number
username: string
emails: EntityConstructor<{
emails: {
email: string
isVerified: boolean
}>
}> | null
}[]
} | null
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -3869,32 +3872,32 @@ describe("Entity builder", () => {
id: number
username: string
deleted_at: Date | null
email: EntityConstructor<{
email: {
email: string
isVerified: boolean
deleted_at: Date | null
user: EntityConstructor<{
user: {
id: number
username: string
deleted_at: Date | null
}>
}>
}
}
}>()
expectTypeOf(new Email()).toMatchTypeOf<{
email: string
isVerified: boolean
deleted_at: Date | null
user: EntityConstructor<{
user: {
id: number
username: string
deleted_at: Date | null
email: EntityConstructor<{
email: {
email: string
isVerified: boolean
deleted_at: Date | null
}>
}>
}
}
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -4057,32 +4060,32 @@ describe("Entity builder", () => {
id: number
username: string
deleted_at: Date | null
email: EntityConstructor<{
email: {
email: string
isVerified: boolean
deleted_at: Date | null
user: EntityConstructor<{
user: {
id: number
username: string
deleted_at: Date | null
}>
}>
}
}
}>()
expectTypeOf(new Email()).toMatchTypeOf<{
email: string
isVerified: boolean
deleted_at: Date | null
user: EntityConstructor<{
user: {
id: number
username: string
deleted_at: Date | null
email: EntityConstructor<{
email: {
email: string
isVerified: boolean
deleted_at: Date | null
}>
}>
}
}
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -4246,27 +4249,27 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
teams: EntityConstructor<{
teams: {
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
}>
}>
}[]
}[]
}>()
expectTypeOf(new Team()).toMatchTypeOf<{
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
teams: EntityConstructor<{
teams: {
id: number
name: string
}>
}>
}[]
}[]
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -4413,27 +4416,27 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
teams: EntityConstructor<{
teams: {
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
}>
}>
}[]
}[]
}>()
expectTypeOf(new Team()).toMatchTypeOf<{
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
teams: EntityConstructor<{
teams: {
id: number
name: string
}>
}>
}[]
}[]
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -4615,27 +4618,27 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
teams: EntityConstructor<{
teams: {
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
}>
}>
}[]
}[]
}>()
expectTypeOf(new Team()).toMatchTypeOf<{
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
teams: EntityConstructor<{
teams: {
id: number
name: string
}>
}>
}[]
}[]
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -4789,27 +4792,27 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
teams: EntityConstructor<{
teams: {
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
}>
}>
}[]
}[]
}>()
expectTypeOf(new Team()).toMatchTypeOf<{
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
teams: EntityConstructor<{
teams: {
id: number
name: string
}>
}>
}[]
}[]
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -4969,39 +4972,39 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
teams: EntityConstructor<{
teams: {
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
}>
}>
activeTeams: EntityConstructor<{
}[]
}[]
activeTeams: {
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
}>
}>
}[]
}[]
}>()
expectTypeOf(new Team()).toMatchTypeOf<{
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
teams: EntityConstructor<{
teams: {
id: number
name: string
}>
activeTeams: EntityConstructor<{
}[]
activeTeams: {
id: number
name: string
}>
}>
}[]
}[]
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -5168,27 +5171,27 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
teams: EntityConstructor<{
teams: {
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
}>
}>
}[]
}[]
}>()
expectTypeOf(new Team()).toMatchTypeOf<{
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
teams: EntityConstructor<{
teams: {
id: number
name: string
}>
}>
}[]
}[]
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -5339,27 +5342,27 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
teams: EntityConstructor<{
teams: {
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
}>
}>
}[]
}[]
}>()
expectTypeOf(new Team()).toMatchTypeOf<{
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
teams: EntityConstructor<{
teams: {
id: number
name: string
}>
}>
}[]
}[]
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
@@ -5513,27 +5516,27 @@ describe("Entity builder", () => {
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
teams: EntityConstructor<{
teams: {
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
}>
}>
}[]
}[]
}>()
expectTypeOf(new Team()).toMatchTypeOf<{
id: number
name: string
users: EntityConstructor<{
users: {
id: number
username: string
teams: EntityConstructor<{
teams: {
id: number
name: string
}>
}>
}[]
}[]
}>()
const squadMetaData = MetadataStorage.getMetadataFromDecorator(Squad)

View File

@@ -85,16 +85,14 @@ export function createMikrORMEntity() {
* return the input idempotently
* @param entity
*/
export const toMikroORMEntity = <T>(
entity: T
): T extends DmlEntity<any> ? Infer<T> : T => {
export const toMikroORMEntity = <T>(entity: T): Infer<T> => {
let mikroOrmEntity: T | EntityConstructor<any> = entity
if (DmlEntity.isDmlEntity(entity)) {
mikroOrmEntity = createMikrORMEntity()(entity)
}
return mikroOrmEntity as T extends DmlEntity<any> ? Infer<T> : T
return mikroOrmEntity as Infer<T>
}
/**

View File

@@ -16,7 +16,7 @@ export class NullableModifier<T, Relation extends RelationshipType<T>>
return !!modifier?.[IsNullableModifier]
}
declare type: RelationshipType<T>["type"]
declare type: Relation["type"]
/**
* A type-only property to infer the JavScript data-type