feat: add support for defining hasOne with FK (#10441)
This commit is contained in:
@@ -2887,6 +2887,690 @@ describe("Entity builder", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("Entity builder | hasOneWithFK", () => {
|
||||
test("define hasOne relationship with FK enabled", () => {
|
||||
const email = model.define("email", {
|
||||
email: model.text(),
|
||||
isVerified: model.boolean(),
|
||||
})
|
||||
|
||||
const user = model.define("user", {
|
||||
id: model.number(),
|
||||
username: model.text(),
|
||||
email: model.hasOne(() => email, {
|
||||
foreignKey: true,
|
||||
}),
|
||||
})
|
||||
|
||||
const User = toMikroORMEntity(user)
|
||||
|
||||
expectTypeOf(new User()).toEqualTypeOf<{
|
||||
id: number
|
||||
username: string
|
||||
email_id: string
|
||||
created_at: Date
|
||||
updated_at: Date
|
||||
deleted_at: Date | null
|
||||
email: {
|
||||
email: string
|
||||
isVerified: boolean
|
||||
created_at: Date
|
||||
updated_at: Date
|
||||
deleted_at: Date | null
|
||||
}
|
||||
}>()
|
||||
|
||||
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||
expect(metaData.className).toEqual("User")
|
||||
expect(metaData.path).toEqual("User")
|
||||
expect(metaData.properties).toEqual({
|
||||
id: {
|
||||
reference: "scalar",
|
||||
type: "number",
|
||||
columnType: "integer",
|
||||
name: "id",
|
||||
fieldName: "id",
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
username: {
|
||||
reference: "scalar",
|
||||
type: "string",
|
||||
columnType: "text",
|
||||
name: "username",
|
||||
fieldName: "username",
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
email: {
|
||||
reference: "1:1",
|
||||
name: "email",
|
||||
entity: "Email",
|
||||
nullable: false,
|
||||
},
|
||||
email_id: {
|
||||
columnType: "text",
|
||||
type: "string",
|
||||
reference: "scalar",
|
||||
name: "email_id",
|
||||
nullable: false,
|
||||
persist: true,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
created_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "created_at",
|
||||
fieldName: "created_at",
|
||||
defaultRaw: "now()",
|
||||
onCreate: expect.any(Function),
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
updated_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "updated_at",
|
||||
fieldName: "updated_at",
|
||||
defaultRaw: "now()",
|
||||
onCreate: expect.any(Function),
|
||||
onUpdate: expect.any(Function),
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
deleted_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "deleted_at",
|
||||
fieldName: "deleted_at",
|
||||
nullable: true,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("mark hasOne with FK enabled relationship as nullable", () => {
|
||||
const email = model.define("email", {
|
||||
email: model.text(),
|
||||
isVerified: model.boolean(),
|
||||
})
|
||||
|
||||
const user = model.define("user", {
|
||||
id: model.number(),
|
||||
username: model.text(),
|
||||
emails: model
|
||||
.hasOne(() => email, {
|
||||
foreignKey: true,
|
||||
})
|
||||
.nullable(),
|
||||
})
|
||||
|
||||
const User = toMikroORMEntity(user)
|
||||
|
||||
expectTypeOf(new User().emails_id).toEqualTypeOf<string | null>()
|
||||
expectTypeOf(new User()).toMatchTypeOf<{
|
||||
id: number
|
||||
username: string
|
||||
deleted_at: Date | null
|
||||
emails_id: string | null
|
||||
emails: {
|
||||
email: string
|
||||
isVerified: boolean
|
||||
deleted_at: Date | null
|
||||
} | null
|
||||
}>()
|
||||
|
||||
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||
expect(metaData.className).toEqual("User")
|
||||
expect(metaData.path).toEqual("User")
|
||||
expect(metaData.properties).toEqual({
|
||||
id: {
|
||||
reference: "scalar",
|
||||
type: "number",
|
||||
columnType: "integer",
|
||||
name: "id",
|
||||
fieldName: "id",
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
username: {
|
||||
reference: "scalar",
|
||||
type: "string",
|
||||
columnType: "text",
|
||||
name: "username",
|
||||
fieldName: "username",
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
emails: {
|
||||
reference: "1:1",
|
||||
name: "emails",
|
||||
entity: "Email",
|
||||
nullable: true,
|
||||
},
|
||||
emails_id: {
|
||||
columnType: "text",
|
||||
type: "string",
|
||||
reference: "scalar",
|
||||
name: "emails_id",
|
||||
nullable: true,
|
||||
persist: true,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
created_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "created_at",
|
||||
fieldName: "created_at",
|
||||
defaultRaw: "now()",
|
||||
onCreate: expect.any(Function),
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
updated_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "updated_at",
|
||||
fieldName: "updated_at",
|
||||
defaultRaw: "now()",
|
||||
onCreate: expect.any(Function),
|
||||
onUpdate: expect.any(Function),
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
deleted_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "deleted_at",
|
||||
fieldName: "deleted_at",
|
||||
nullable: true,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("define custom mappedBy key for relationship", () => {
|
||||
const email = model.define("email", {
|
||||
email: model.text(),
|
||||
isVerified: model.boolean(),
|
||||
})
|
||||
|
||||
const user = model.define("user", {
|
||||
id: model.number(),
|
||||
username: model.text(),
|
||||
email: model.hasOne(() => email, {
|
||||
mappedBy: "owner",
|
||||
foreignKey: true,
|
||||
}),
|
||||
})
|
||||
|
||||
const User = toMikroORMEntity(user)
|
||||
expectTypeOf(new User().email_id).toEqualTypeOf<string>()
|
||||
expectTypeOf(new User()).toMatchTypeOf<{
|
||||
id: number
|
||||
username: string
|
||||
email: { email: string; isVerified: boolean }
|
||||
}>()
|
||||
|
||||
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||
expect(metaData.className).toEqual("User")
|
||||
expect(metaData.path).toEqual("User")
|
||||
expect(metaData.properties).toEqual({
|
||||
id: {
|
||||
reference: "scalar",
|
||||
type: "number",
|
||||
columnType: "integer",
|
||||
name: "id",
|
||||
fieldName: "id",
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
username: {
|
||||
reference: "scalar",
|
||||
type: "string",
|
||||
columnType: "text",
|
||||
name: "username",
|
||||
fieldName: "username",
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
email: {
|
||||
reference: "1:1",
|
||||
name: "email",
|
||||
entity: "Email",
|
||||
nullable: false,
|
||||
mappedBy: "owner",
|
||||
},
|
||||
email_id: {
|
||||
columnType: "text",
|
||||
type: "string",
|
||||
reference: "scalar",
|
||||
name: "email_id",
|
||||
nullable: false,
|
||||
persist: true,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
created_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "created_at",
|
||||
fieldName: "created_at",
|
||||
defaultRaw: "now()",
|
||||
onCreate: expect.any(Function),
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
updated_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "updated_at",
|
||||
fieldName: "updated_at",
|
||||
defaultRaw: "now()",
|
||||
onCreate: expect.any(Function),
|
||||
onUpdate: expect.any(Function),
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
deleted_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "deleted_at",
|
||||
fieldName: "deleted_at",
|
||||
nullable: true,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("define delete cascades for the entity", () => {
|
||||
const email = model.define("email", {
|
||||
email: model.text(),
|
||||
isVerified: model.boolean(),
|
||||
})
|
||||
|
||||
const user = model
|
||||
.define("user", {
|
||||
id: model.number(),
|
||||
username: model.text(),
|
||||
email: model.hasOne(() => email, {
|
||||
foreignKey: true,
|
||||
}),
|
||||
})
|
||||
.cascades({
|
||||
delete: ["email"],
|
||||
})
|
||||
|
||||
const User = toMikroORMEntity(user)
|
||||
expectTypeOf(new User().email_id).toEqualTypeOf<string>()
|
||||
expectTypeOf(new User()).toMatchTypeOf<{
|
||||
id: number
|
||||
username: string
|
||||
email: { email: string; isVerified: boolean }
|
||||
}>()
|
||||
|
||||
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||
expect(metaData.className).toEqual("User")
|
||||
expect(metaData.path).toEqual("User")
|
||||
expect(metaData.properties).toEqual({
|
||||
id: {
|
||||
reference: "scalar",
|
||||
type: "number",
|
||||
columnType: "integer",
|
||||
name: "id",
|
||||
fieldName: "id",
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
username: {
|
||||
reference: "scalar",
|
||||
type: "string",
|
||||
columnType: "text",
|
||||
name: "username",
|
||||
fieldName: "username",
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
email: {
|
||||
reference: "1:1",
|
||||
name: "email",
|
||||
entity: "Email",
|
||||
nullable: false,
|
||||
cascade: ["persist", "soft-remove"],
|
||||
},
|
||||
email_id: {
|
||||
columnType: "text",
|
||||
type: "string",
|
||||
reference: "scalar",
|
||||
name: "email_id",
|
||||
nullable: false,
|
||||
persist: true,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
created_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "created_at",
|
||||
fieldName: "created_at",
|
||||
defaultRaw: "now()",
|
||||
onCreate: expect.any(Function),
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
updated_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "updated_at",
|
||||
fieldName: "updated_at",
|
||||
defaultRaw: "now()",
|
||||
onCreate: expect.any(Function),
|
||||
onUpdate: expect.any(Function),
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
deleted_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "deleted_at",
|
||||
fieldName: "deleted_at",
|
||||
nullable: true,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
})
|
||||
|
||||
const Email = toMikroORMEntity(email)
|
||||
const emailMetaData = MetadataStorage.getMetadataFromDecorator(Email)
|
||||
expect(emailMetaData.className).toEqual("Email")
|
||||
expect(emailMetaData.path).toEqual("Email")
|
||||
expect(emailMetaData.properties).toEqual({
|
||||
email: {
|
||||
reference: "scalar",
|
||||
type: "string",
|
||||
columnType: "text",
|
||||
name: "email",
|
||||
fieldName: "email",
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
isVerified: {
|
||||
reference: "scalar",
|
||||
type: "boolean",
|
||||
columnType: "boolean",
|
||||
name: "isVerified",
|
||||
fieldName: "isVerified",
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
created_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "created_at",
|
||||
fieldName: "created_at",
|
||||
defaultRaw: "now()",
|
||||
onCreate: expect.any(Function),
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
updated_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "updated_at",
|
||||
fieldName: "updated_at",
|
||||
defaultRaw: "now()",
|
||||
onCreate: expect.any(Function),
|
||||
onUpdate: expect.any(Function),
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
deleted_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "deleted_at",
|
||||
fieldName: "deleted_at",
|
||||
nullable: true,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("define delete cascades with belongsTo on the other end", () => {
|
||||
const email = model.define("email", {
|
||||
email: model.text(),
|
||||
isVerified: model.boolean(),
|
||||
user: model.belongsTo(() => user),
|
||||
})
|
||||
|
||||
const user = model
|
||||
.define("user", {
|
||||
id: model.number(),
|
||||
username: model.text(),
|
||||
email: model.hasOne(() => email, {
|
||||
foreignKey: true,
|
||||
}),
|
||||
})
|
||||
.cascades({
|
||||
delete: ["email"],
|
||||
})
|
||||
|
||||
const User = toMikroORMEntity(user)
|
||||
expectTypeOf(new User().email_id).toEqualTypeOf<string>()
|
||||
expectTypeOf(new User()).toMatchTypeOf<{
|
||||
id: number
|
||||
username: string
|
||||
email: {
|
||||
email: string
|
||||
isVerified: boolean
|
||||
user: {
|
||||
id: number
|
||||
username: string
|
||||
}
|
||||
}
|
||||
}>()
|
||||
|
||||
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||
expect(metaData.className).toEqual("User")
|
||||
expect(metaData.path).toEqual("User")
|
||||
expect(metaData.properties).toEqual({
|
||||
id: {
|
||||
reference: "scalar",
|
||||
type: "number",
|
||||
columnType: "integer",
|
||||
name: "id",
|
||||
fieldName: "id",
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
username: {
|
||||
reference: "scalar",
|
||||
type: "string",
|
||||
columnType: "text",
|
||||
name: "username",
|
||||
fieldName: "username",
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
email: {
|
||||
reference: "1:1",
|
||||
name: "email",
|
||||
entity: "Email",
|
||||
nullable: false,
|
||||
cascade: ["persist", "soft-remove"],
|
||||
},
|
||||
email_id: {
|
||||
columnType: "text",
|
||||
type: "string",
|
||||
reference: "scalar",
|
||||
name: "email_id",
|
||||
nullable: false,
|
||||
persist: true,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
created_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "created_at",
|
||||
fieldName: "created_at",
|
||||
defaultRaw: "now()",
|
||||
onCreate: expect.any(Function),
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
updated_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "updated_at",
|
||||
fieldName: "updated_at",
|
||||
defaultRaw: "now()",
|
||||
onCreate: expect.any(Function),
|
||||
onUpdate: expect.any(Function),
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
deleted_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "deleted_at",
|
||||
fieldName: "deleted_at",
|
||||
nullable: true,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
})
|
||||
|
||||
const Email = toMikroORMEntity(email)
|
||||
const emailMetaData = MetadataStorage.getMetadataFromDecorator(Email)
|
||||
expect(emailMetaData.className).toEqual("Email")
|
||||
expect(emailMetaData.path).toEqual("Email")
|
||||
expect(emailMetaData.properties).toEqual({
|
||||
email: {
|
||||
reference: "scalar",
|
||||
type: "string",
|
||||
columnType: "text",
|
||||
name: "email",
|
||||
fieldName: "email",
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
isVerified: {
|
||||
reference: "scalar",
|
||||
type: "boolean",
|
||||
columnType: "boolean",
|
||||
name: "isVerified",
|
||||
fieldName: "isVerified",
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
user: {
|
||||
entity: "User",
|
||||
mappedBy: "email",
|
||||
name: "user",
|
||||
nullable: false,
|
||||
onDelete: "cascade",
|
||||
owner: true,
|
||||
reference: "1:1",
|
||||
},
|
||||
user_id: {
|
||||
columnType: "text",
|
||||
getter: false,
|
||||
name: "user_id",
|
||||
nullable: false,
|
||||
reference: "scalar",
|
||||
setter: false,
|
||||
type: "string",
|
||||
persist: false,
|
||||
},
|
||||
created_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "created_at",
|
||||
fieldName: "created_at",
|
||||
defaultRaw: "now()",
|
||||
onCreate: expect.any(Function),
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
updated_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "updated_at",
|
||||
fieldName: "updated_at",
|
||||
defaultRaw: "now()",
|
||||
onCreate: expect.any(Function),
|
||||
onUpdate: expect.any(Function),
|
||||
nullable: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
deleted_at: {
|
||||
reference: "scalar",
|
||||
type: "date",
|
||||
columnType: "timestamptz",
|
||||
name: "deleted_at",
|
||||
fieldName: "deleted_at",
|
||||
nullable: true,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("Entity builder | indexes", () => {
|
||||
test("should define indexes for an entity", () => {
|
||||
const group = model.define("group", {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { expectTypeOf } from "expect-type"
|
||||
import { TextProperty } from "../properties/text"
|
||||
import { HasOne } from "../relations/has-one"
|
||||
import { HasOneWithForeignKey } from "../relations/has-one-fk"
|
||||
|
||||
describe("HasOne relationship", () => {
|
||||
test("define hasOne relationship", () => {
|
||||
@@ -57,4 +58,23 @@ describe("HasOne relationship", () => {
|
||||
|
||||
expect(HasOne.isHasOne(relationship)).toEqual(false)
|
||||
})
|
||||
|
||||
test("enable foreign keys for has one relationship", () => {
|
||||
const user = {
|
||||
username: new TextProperty(),
|
||||
}
|
||||
|
||||
const entityRef = () => user
|
||||
const relationship = new HasOneWithForeignKey(entityRef, {})
|
||||
|
||||
expectTypeOf(relationship["$dataType"]).toEqualTypeOf<() => typeof user>()
|
||||
expect(relationship.parse("user")).toEqual({
|
||||
name: "user",
|
||||
type: "hasOneWithFK",
|
||||
nullable: false,
|
||||
options: {},
|
||||
searchable: false,
|
||||
entity: entityRef,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -25,6 +25,7 @@ import { BelongsTo } from "./relations/belongs-to"
|
||||
import { HasMany } from "./relations/has-many"
|
||||
import { HasOne } from "./relations/has-one"
|
||||
import { ManyToMany } from "./relations/many-to-many"
|
||||
import { HasOneWithForeignKey } from "./relations/has-one-fk"
|
||||
|
||||
/**
|
||||
* The implicit properties added by EntityBuilder in every schema
|
||||
@@ -345,7 +346,27 @@ export class EntityBuilder {
|
||||
*
|
||||
* @customNamespace Relationship Methods
|
||||
*/
|
||||
hasOne<T>(entityBuilder: T, options?: RelationshipOptions) {
|
||||
hasOne<T>(
|
||||
entityBuilder: T,
|
||||
options: RelationshipOptions & {
|
||||
foreignKey: true
|
||||
}
|
||||
): HasOneWithForeignKey<T>
|
||||
hasOne<T>(
|
||||
entityBuilder: T,
|
||||
options?: RelationshipOptions & {
|
||||
foreignKey?: false
|
||||
}
|
||||
): HasOne<T>
|
||||
hasOne<T>(
|
||||
entityBuilder: T,
|
||||
options?: RelationshipOptions & {
|
||||
foreignKey?: boolean
|
||||
}
|
||||
): HasOneWithForeignKey<T> | HasOne<T> {
|
||||
if (options?.foreignKey) {
|
||||
return new HasOneWithForeignKey<T>(entityBuilder, options || {})
|
||||
}
|
||||
return new HasOne<T>(entityBuilder, options || {})
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ export class DmlEntity<
|
||||
*/
|
||||
cascades(
|
||||
options: EntityCascades<
|
||||
ExtractEntityRelations<Schema, "hasOne" | "hasMany">
|
||||
ExtractEntityRelations<Schema, "hasOne" | "hasOneWithFK" | "hasMany">
|
||||
>
|
||||
) {
|
||||
const childToParentCascades = options.delete?.filter((relationship) => {
|
||||
|
||||
@@ -22,6 +22,7 @@ import { parseEntityName } from "./parse-entity-name"
|
||||
import { camelToSnakeCase, pluralize } from "../../../common"
|
||||
import { applyEntityIndexes } from "../mikro-orm/apply-indexes"
|
||||
import { ManyToMany as DmlManyToMany } from "../../relations/many-to-many"
|
||||
import { HasOneWithForeignKey } from "../../relations/has-one-fk"
|
||||
|
||||
type Context = {
|
||||
MANY_TO_MANY_TRACKED_RELATIONS: Record<string, boolean>
|
||||
@@ -150,6 +151,43 @@ export function defineHasOneRelationship(
|
||||
})(MikroORMEntity.prototype, relationship.name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines has one relationship with Foreign key on the MikroORM
|
||||
* entity
|
||||
*/
|
||||
export function defineHasOneWithFKRelationship(
|
||||
MikroORMEntity: EntityConstructor<any>,
|
||||
relationship: RelationshipMetadata,
|
||||
{ relatedModelName }: { relatedModelName: string },
|
||||
cascades: EntityCascades<string[]>
|
||||
) {
|
||||
const foreignKeyName = camelToSnakeCase(`${relationship.name}Id`)
|
||||
const shouldRemoveRelated = !!cascades.delete?.includes(relationship.name)
|
||||
let mappedBy: string | undefined
|
||||
|
||||
if ("mappedBy" in relationship) {
|
||||
mappedBy = relationship.mappedBy
|
||||
} else {
|
||||
mappedBy = camelToSnakeCase(MikroORMEntity.name)
|
||||
}
|
||||
|
||||
OneToOne({
|
||||
entity: relatedModelName,
|
||||
nullable: relationship.nullable,
|
||||
...(mappedBy ? { mappedBy } : {}),
|
||||
cascade: shouldRemoveRelated
|
||||
? (["persist", "soft-remove"] as any)
|
||||
: undefined,
|
||||
} as any)(MikroORMEntity.prototype, relationship.name)
|
||||
|
||||
Property({
|
||||
type: "string",
|
||||
columnType: "text",
|
||||
nullable: relationship.nullable,
|
||||
persist: true,
|
||||
})(MikroORMEntity.prototype, foreignKeyName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines has many relationship on the Mikro ORM entity
|
||||
*/
|
||||
@@ -225,7 +263,10 @@ export function defineBelongsToRelationship(
|
||||
* to associate a relation (through the relation or the foreign key) we need to handle it
|
||||
* specifically
|
||||
*/
|
||||
if (HasOne.isHasOne(otherSideRelation)) {
|
||||
if (
|
||||
HasOne.isHasOne(otherSideRelation) ||
|
||||
HasOneWithForeignKey.isHasOneWithForeignKey(otherSideRelation)
|
||||
) {
|
||||
const relationMeta = this.__meta.relations.find(
|
||||
(relation) => relation.name === relationship.name
|
||||
).targetMeta
|
||||
@@ -317,7 +358,10 @@ export function defineBelongsToRelationship(
|
||||
/**
|
||||
* Otherside is a has one. Hence we should defined a OneToOne
|
||||
*/
|
||||
if (HasOne.isHasOne(otherSideRelation)) {
|
||||
if (
|
||||
HasOne.isHasOne(otherSideRelation) ||
|
||||
HasOneWithForeignKey.isHasOneWithForeignKey(otherSideRelation)
|
||||
) {
|
||||
const foreignKeyName = camelToSnakeCase(`${relationship.name}Id`)
|
||||
|
||||
OneToOne({
|
||||
@@ -600,6 +644,14 @@ export function defineRelationship(
|
||||
cascades
|
||||
)
|
||||
break
|
||||
case "hasOneWithFK":
|
||||
defineHasOneWithFKRelationship(
|
||||
MikroORMEntity,
|
||||
relationship,
|
||||
relatedEntityInfo,
|
||||
cascades
|
||||
)
|
||||
break
|
||||
case "hasMany":
|
||||
defineHasManyRelationship(
|
||||
MikroORMEntity,
|
||||
|
||||
@@ -56,8 +56,6 @@ export abstract class BaseRelationship<T> implements RelationshipType<T> {
|
||||
* })
|
||||
*
|
||||
* export default Product
|
||||
*
|
||||
* @customNamespace Property Configuration Methods
|
||||
*/
|
||||
searchable() {
|
||||
this.#searchable = true
|
||||
|
||||
@@ -3,6 +3,7 @@ import { RelationNullableModifier } from "./nullable"
|
||||
|
||||
export class BelongsTo<T> extends BaseRelationship<T> {
|
||||
type = "belongsTo" as const
|
||||
declare $foreignKey: true
|
||||
|
||||
static isBelongsTo<T>(relationship: any): relationship is BelongsTo<T> {
|
||||
return relationship?.type === "belongsTo"
|
||||
@@ -12,6 +13,6 @@ export class BelongsTo<T> extends BaseRelationship<T> {
|
||||
* Apply nullable modifier on the schema
|
||||
*/
|
||||
nullable() {
|
||||
return new RelationNullableModifier<T, BelongsTo<T>>(this)
|
||||
return new RelationNullableModifier<T, BelongsTo<T>, true>(this)
|
||||
}
|
||||
}
|
||||
|
||||
30
packages/core/utils/src/dml/relations/has-one-fk.ts
Normal file
30
packages/core/utils/src/dml/relations/has-one-fk.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { BaseRelationship } from "./base"
|
||||
import { RelationNullableModifier } from "./nullable"
|
||||
|
||||
/**
|
||||
* HasOne relationship defines a relationship between two entities
|
||||
* where the owner of the relationship has exactly one instance
|
||||
* of the related entity.
|
||||
*
|
||||
* For example: A user HasOne profile
|
||||
*
|
||||
* You may use the "BelongsTo" relationship to define the inverse
|
||||
* of the "HasOne" relationship
|
||||
*/
|
||||
export class HasOneWithForeignKey<T> extends BaseRelationship<T> {
|
||||
type = "hasOneWithFK" as const
|
||||
declare $foreignKey: true
|
||||
|
||||
static isHasOneWithForeignKey<T>(
|
||||
relationship: any
|
||||
): relationship is HasOneWithForeignKey<T> {
|
||||
return relationship?.type === "hasOneWithFK"
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply nullable modifier on the schema
|
||||
*/
|
||||
nullable() {
|
||||
return new RelationNullableModifier<T, HasOneWithForeignKey<T>, true>(this)
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,6 @@ export class HasOne<T> extends BaseRelationship<T> {
|
||||
* Apply nullable modifier on the schema
|
||||
*/
|
||||
nullable() {
|
||||
return new RelationNullableModifier<T, HasOne<T>>(this)
|
||||
return new RelationNullableModifier<T, HasOne<T>, false>(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,18 @@ const IsNullableModifier = Symbol.for("isNullableModifier")
|
||||
/**
|
||||
* Nullable modifier marks a schema node as nullable
|
||||
*/
|
||||
export class RelationNullableModifier<T, Relation extends RelationshipType<T>>
|
||||
implements RelationshipType<T | null>
|
||||
export class RelationNullableModifier<
|
||||
T,
|
||||
Relation extends RelationshipType<T>,
|
||||
ForeignKey extends boolean
|
||||
> implements RelationshipType<T | null>
|
||||
{
|
||||
[IsNullableModifier]: true = true;
|
||||
[IsRelationship]: true = true
|
||||
|
||||
static isNullableModifier<T>(
|
||||
modifier: any
|
||||
): modifier is RelationNullableModifier<T, any> {
|
||||
): modifier is RelationNullableModifier<T, any, any> {
|
||||
return !!modifier?.[IsNullableModifier]
|
||||
}
|
||||
|
||||
@@ -25,6 +28,7 @@ export class RelationNullableModifier<T, Relation extends RelationshipType<T>>
|
||||
* of the schema property
|
||||
*/
|
||||
declare $dataType: T | null
|
||||
declare $foreignKey: ForeignKey
|
||||
|
||||
/**
|
||||
* The parent schema on which the nullable modifier is
|
||||
|
||||
Reference in New Issue
Block a user