chore(fulfillment, utils): Migrate module to DML (#10617)
**What**
- Allow to provide `foreignKeyName` option for hasOne and belongsTo relationships
- `model.hasOne(() => OtherEntity, { foreignKey: true, foreignKeyName: 'other_entity_something_id' })`
- The above will also output a generated type that takes into consideration the custom fk name 🔽
- Update types to account for defined custom foreign key name
- Fix joiner config linkable generation to account for custom linkable keys that provide a public API for their model but are not part of the list of the models included in the MedusaService
- This was supposed to be handled correctly but the implementation was not considering that custom linkable keys could reference models not part of the one provided to medusa service
- Migrate fulfillment module to DML
- Fix has one with fk behaviour and hooks (the relation should be assigned but not the fk)
- Fix has one belongsTo hooks (the relation should be assigned but not the fk)
- Fix hasOneWithFk and belongsTo non persisted fk to be selectable
- Allow to define `belongsTo` without other side definition for `ManyToOne` with no counter part defined
- Meaning that if a user defined `belongsTo` on one side andnot mapped by and no counter part on the other entity it will be considered as a `ManyToOne`
- `orphanRemoval` on `OneToOne` have been removed, this means that when assigning a new object relation to an entity, the previous one gets deconected but not deleted automatically. This prevent removing data un volountarely
**NOTE**
As per our convention here are some information to keep in mind
**HasOne <> BelongsTo**
Define `OneToOne`, The foreign key is owned by the belongs to and the relation needs to be provided to cascade if wanted
**HasMany <> BelongsTo**
Define `OneToMane` <> `ManyToOne`, the foreign key is owned by the many to one and for those relation no cascade will be performed, the foreign key must be provided. For the `HasMany` the cascade is available
**HasOne (with FK)**
Will act similarly to belongs to with **HasOne <> BelongsTo**
Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
This commit is contained in:
co-authored by
Carlos R. L. Rodrigues
parent
65007c49f6
commit
100da64242
@@ -3018,6 +3018,7 @@ describe("Entity builder", () => {
|
||||
persist: false,
|
||||
name: "user_id",
|
||||
nullable: false,
|
||||
formula: expect.any(Function),
|
||||
reference: "scalar",
|
||||
setter: false,
|
||||
type: "string",
|
||||
@@ -3123,14 +3124,16 @@ describe("Entity builder", () => {
|
||||
reference: "1:1",
|
||||
name: "email",
|
||||
entity: "Email",
|
||||
fieldName: "email_id",
|
||||
},
|
||||
email_id: {
|
||||
columnType: "text",
|
||||
type: "string",
|
||||
reference: "scalar",
|
||||
name: "email_id",
|
||||
formula: expect.any(Function),
|
||||
nullable: false,
|
||||
persist: true,
|
||||
persist: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
@@ -3233,14 +3236,16 @@ describe("Entity builder", () => {
|
||||
name: "emails",
|
||||
entity: "Email",
|
||||
nullable: true,
|
||||
fieldName: "emails_id",
|
||||
},
|
||||
emails_id: {
|
||||
columnType: "text",
|
||||
type: "string",
|
||||
reference: "scalar",
|
||||
name: "emails_id",
|
||||
formula: expect.any(Function),
|
||||
nullable: true,
|
||||
persist: true,
|
||||
persist: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
@@ -3334,14 +3339,16 @@ describe("Entity builder", () => {
|
||||
name: "email",
|
||||
entity: "Email",
|
||||
mappedBy: "owner",
|
||||
fieldName: "email_id",
|
||||
},
|
||||
email_id: {
|
||||
columnType: "text",
|
||||
type: "string",
|
||||
reference: "scalar",
|
||||
name: "email_id",
|
||||
formula: expect.any(Function),
|
||||
nullable: false,
|
||||
persist: true,
|
||||
persist: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
@@ -3439,14 +3446,16 @@ describe("Entity builder", () => {
|
||||
entity: "Email",
|
||||
cascade: ["persist", "soft-remove"],
|
||||
mappedBy: "user",
|
||||
fieldName: "email_id",
|
||||
},
|
||||
email_id: {
|
||||
columnType: "text",
|
||||
type: "string",
|
||||
reference: "scalar",
|
||||
name: "email_id",
|
||||
formula: expect.any(Function),
|
||||
nullable: false,
|
||||
persist: true,
|
||||
persist: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
@@ -3614,14 +3623,16 @@ describe("Entity builder", () => {
|
||||
entity: "Email",
|
||||
cascade: ["persist", "soft-remove"],
|
||||
mappedBy: "user",
|
||||
fieldName: "email_id",
|
||||
},
|
||||
email_id: {
|
||||
columnType: "text",
|
||||
type: "string",
|
||||
reference: "scalar",
|
||||
formula: expect.any(Function),
|
||||
name: "email_id",
|
||||
nullable: false,
|
||||
persist: true,
|
||||
persist: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
},
|
||||
@@ -3704,6 +3715,7 @@ describe("Entity builder", () => {
|
||||
name: "user_id",
|
||||
nullable: false,
|
||||
reference: "scalar",
|
||||
formula: expect.any(Function),
|
||||
setter: false,
|
||||
type: "string",
|
||||
persist: false,
|
||||
@@ -4649,6 +4661,7 @@ describe("Entity builder", () => {
|
||||
reference: "scalar",
|
||||
persist: false,
|
||||
type: "string",
|
||||
formula: expect.any(Function),
|
||||
columnType: "text",
|
||||
nullable: false,
|
||||
name: "user_id",
|
||||
@@ -4848,6 +4861,7 @@ describe("Entity builder", () => {
|
||||
type: "string",
|
||||
columnType: "text",
|
||||
nullable: true,
|
||||
formula: expect.any(Function),
|
||||
name: "user_id",
|
||||
getter: false,
|
||||
setter: false,
|
||||
@@ -5268,23 +5282,6 @@ describe("Entity builder", () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("throw error when other side relationship is missing", () => {
|
||||
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(),
|
||||
})
|
||||
|
||||
expect(() => toMikroORMEntity(email)).toThrow(
|
||||
'Missing property "email" on "User" entity. Make sure to define it as a relationship'
|
||||
)
|
||||
})
|
||||
|
||||
test("throw error when other side relationship is invalid", () => {
|
||||
const email = model.define("email", {
|
||||
email: model.text(),
|
||||
@@ -5482,6 +5479,7 @@ describe("Entity builder", () => {
|
||||
type: "string",
|
||||
persist: false,
|
||||
columnType: "text",
|
||||
formula: expect.any(Function),
|
||||
nullable: false,
|
||||
name: "user_id",
|
||||
getter: false,
|
||||
@@ -5682,6 +5680,7 @@ describe("Entity builder", () => {
|
||||
type: "string",
|
||||
columnType: "text",
|
||||
nullable: false,
|
||||
formula: expect.any(Function),
|
||||
name: "user_id",
|
||||
getter: false,
|
||||
setter: false,
|
||||
@@ -5903,6 +5902,7 @@ describe("Entity builder", () => {
|
||||
type: "string",
|
||||
columnType: "text",
|
||||
reference: "scalar",
|
||||
formula: expect.any(Function),
|
||||
persist: false,
|
||||
getter: false,
|
||||
setter: false,
|
||||
|
||||
@@ -3,15 +3,9 @@ import {
|
||||
IDmlEntityConfig,
|
||||
RelationshipOptions,
|
||||
} from "@medusajs/types"
|
||||
import { DmlEntity } from "./entity"
|
||||
import {
|
||||
createBigNumberProperties,
|
||||
DMLSchemaWithBigNumber,
|
||||
} from "./helpers/entity-builder/create-big-number-properties"
|
||||
import {
|
||||
createDefaultProperties,
|
||||
DMLSchemaDefaults,
|
||||
} from "./helpers/entity-builder/create-default-properties"
|
||||
import { DmlEntity, DMLEntitySchemaBuilder } from "./entity"
|
||||
import { createBigNumberProperties } from "./helpers/entity-builder/create-big-number-properties"
|
||||
import { createDefaultProperties } from "./helpers/entity-builder/create-default-properties"
|
||||
import { ArrayProperty } from "./properties/array"
|
||||
import { AutoIncrementProperty } from "./properties/autoincrement"
|
||||
import { BigNumberProperty } from "./properties/big-number"
|
||||
@@ -131,20 +125,14 @@ export class EntityBuilder {
|
||||
define<Schema extends DMLSchema, const TConfig extends IDmlEntityConfig>(
|
||||
nameOrConfig: TConfig,
|
||||
schema: Schema
|
||||
): DmlEntity<
|
||||
Schema & DMLSchemaWithBigNumber<Schema> & DMLSchemaDefaults,
|
||||
TConfig
|
||||
> {
|
||||
): DmlEntity<DMLEntitySchemaBuilder<Schema>, TConfig> {
|
||||
this.#disallowImplicitProperties(schema)
|
||||
|
||||
return new DmlEntity<Schema, TConfig>(nameOrConfig, {
|
||||
...schema,
|
||||
...createBigNumberProperties(schema),
|
||||
...createDefaultProperties(),
|
||||
}) as unknown as DmlEntity<
|
||||
Schema & DMLSchemaWithBigNumber<Schema> & DMLSchemaDefaults,
|
||||
TConfig
|
||||
>
|
||||
}) as unknown as DmlEntity<DMLEntitySchemaBuilder<Schema>, TConfig>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,7 +241,7 @@ export class EntityBuilder {
|
||||
/**
|
||||
* This method defines a float property that allows for
|
||||
* values with decimal places
|
||||
*
|
||||
*
|
||||
* @version 2.1.2
|
||||
*
|
||||
* @example
|
||||
@@ -398,26 +386,31 @@ export class EntityBuilder {
|
||||
*
|
||||
* @customNamespace Relationship Methods
|
||||
*/
|
||||
hasOne<T>(
|
||||
hasOne<T, const ForeignKeyName extends string | undefined = undefined>(
|
||||
entityBuilder: T,
|
||||
options: RelationshipOptions & {
|
||||
foreignKey: true
|
||||
foreignKeyName?: ForeignKeyName
|
||||
}
|
||||
): HasOneWithForeignKey<T>
|
||||
): HasOneWithForeignKey<T, ForeignKeyName>
|
||||
hasOne<T>(
|
||||
entityBuilder: T,
|
||||
options?: RelationshipOptions & {
|
||||
foreignKey?: false
|
||||
}
|
||||
): HasOne<T>
|
||||
hasOne<T>(
|
||||
hasOne<T, const ForeignKeyName extends string | undefined = undefined>(
|
||||
entityBuilder: T,
|
||||
options?: RelationshipOptions & {
|
||||
foreignKey?: boolean
|
||||
foreignKeyName?: ForeignKeyName
|
||||
}
|
||||
): HasOneWithForeignKey<T> | HasOne<T> {
|
||||
): HasOneWithForeignKey<T, ForeignKeyName> | HasOne<T> {
|
||||
if (options?.foreignKey) {
|
||||
return new HasOneWithForeignKey<T>(entityBuilder, options || {})
|
||||
return new HasOneWithForeignKey<T, ForeignKeyName>(
|
||||
entityBuilder,
|
||||
options || {}
|
||||
)
|
||||
}
|
||||
return new HasOne<T>(entityBuilder, options || {})
|
||||
}
|
||||
@@ -445,8 +438,13 @@ export class EntityBuilder {
|
||||
*
|
||||
* @customNamespace Relationship Methods
|
||||
*/
|
||||
belongsTo<T>(entityBuilder: T, options?: RelationshipOptions) {
|
||||
return new BelongsTo<T>(entityBuilder, options || {})
|
||||
belongsTo<T, const ForeignKeyName extends string | undefined = undefined>(
|
||||
entityBuilder: T,
|
||||
options?: RelationshipOptions & {
|
||||
foreignKeyName?: ForeignKeyName
|
||||
}
|
||||
) {
|
||||
return new BelongsTo<T, ForeignKeyName>(entityBuilder, options || {})
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,9 +12,16 @@ import {
|
||||
import { isObject, isString, toCamelCase, upperCaseFirst } from "../common"
|
||||
import { transformIndexWhere } from "./helpers/entity-builder/build-indexes"
|
||||
import { BelongsTo } from "./relations/belongs-to"
|
||||
import {
|
||||
DMLSchemaDefaults,
|
||||
DMLSchemaWithBigNumber,
|
||||
} from "./helpers/entity-builder"
|
||||
|
||||
const IsDmlEntity = Symbol.for("isDmlEntity")
|
||||
|
||||
export type DMLEntitySchemaBuilder<Schema extends DMLSchema> =
|
||||
DMLSchemaWithBigNumber<Schema> & DMLSchemaDefaults & Schema
|
||||
|
||||
function extractNameAndTableName<const Config extends IDmlEntityConfig>(
|
||||
nameOrConfig: Config
|
||||
) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
Cascade,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
@@ -19,13 +20,13 @@ import {
|
||||
} from "@mikro-orm/core"
|
||||
import { camelToSnakeCase, pluralize } from "../../../common"
|
||||
import { DmlEntity } from "../../entity"
|
||||
import { BelongsTo } from "../../relations"
|
||||
import { HasMany } from "../../relations/has-many"
|
||||
import { HasOne } from "../../relations/has-one"
|
||||
import { HasOneWithForeignKey } from "../../relations/has-one-fk"
|
||||
import { ManyToMany as DmlManyToMany } from "../../relations/many-to-many"
|
||||
import { applyEntityIndexes } from "../mikro-orm/apply-indexes"
|
||||
import { parseEntityName } from "./parse-entity-name"
|
||||
import { BelongsTo } from "../../relations"
|
||||
|
||||
type Context = {
|
||||
MANY_TO_MANY_TRACKED_RELATIONS: Record<string, boolean>
|
||||
@@ -181,7 +182,10 @@ export function defineHasOneWithFKRelationship(
|
||||
{ relatedModelName }: { relatedModelName: string },
|
||||
cascades: EntityCascades<string[], string[]>
|
||||
) {
|
||||
const foreignKeyName = camelToSnakeCase(`${relationship.name}Id`)
|
||||
const foreignKeyName =
|
||||
relationship.options.foreignKeyName ??
|
||||
camelToSnakeCase(`${relationship.name}Id`)
|
||||
|
||||
const shouldRemoveRelated = !!cascades.delete?.includes(relationship.name)
|
||||
|
||||
let mappedBy: string | undefined = camelToSnakeCase(MikroORMEntity.name)
|
||||
@@ -189,21 +193,83 @@ export function defineHasOneWithFKRelationship(
|
||||
mappedBy = relationship.mappedBy
|
||||
}
|
||||
|
||||
OneToOne({
|
||||
const oneToOneOptions = {
|
||||
entity: relatedModelName,
|
||||
fieldName: foreignKeyName,
|
||||
...(relationship.nullable ? { nullable: relationship.nullable } : {}),
|
||||
...(mappedBy ? { mappedBy } : {}),
|
||||
cascade: shouldRemoveRelated
|
||||
? (["persist", "soft-remove"] as any)
|
||||
: undefined,
|
||||
} as OneToOneOptions<any, any>)(MikroORMEntity.prototype, relationship.name)
|
||||
//orphanRemoval: true,
|
||||
} as OneToOneOptions<any, any>
|
||||
|
||||
if (shouldRemoveRelated) {
|
||||
oneToOneOptions.cascade = ["persist", "soft-remove"] as any
|
||||
}
|
||||
|
||||
OneToOne(oneToOneOptions)(MikroORMEntity.prototype, relationship.name)
|
||||
|
||||
Property({
|
||||
type: "string",
|
||||
columnType: "text",
|
||||
nullable: relationship.nullable,
|
||||
persist: true,
|
||||
persist: false,
|
||||
formula(alias) {
|
||||
return alias + "." + foreignKeyName
|
||||
},
|
||||
})(MikroORMEntity.prototype, foreignKeyName)
|
||||
|
||||
const hookFactory = function (
|
||||
name: string,
|
||||
type: "init" | "create" | "update",
|
||||
hookFn: Function
|
||||
) {
|
||||
MikroORMEntity.prototype[name] = function (
|
||||
this: typeof MikroORMEntity.prototype
|
||||
) {
|
||||
if (type !== "update") {
|
||||
// During creation
|
||||
const relationMeta = this.__meta.relations.find(
|
||||
(relation) => relation.name === relationship.name
|
||||
).targetMeta
|
||||
this[relationship.name] ??= rel(
|
||||
relationMeta.class,
|
||||
this[foreignKeyName]
|
||||
)
|
||||
this[foreignKeyName] ??= this[relationship.name]?.id
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (this[relationship.name]) {
|
||||
this[foreignKeyName] = this[relationship.name].id
|
||||
}
|
||||
|
||||
if (this[relationship.name] === null) {
|
||||
this[foreignKeyName] = null
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
hookFn()(MikroORMEntity.prototype, name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to handle foreign key assignation
|
||||
*/
|
||||
hookFactory(
|
||||
`assignRelationFromForeignKeyValue${foreignKeyName}_init`,
|
||||
"init",
|
||||
OnInit
|
||||
)
|
||||
hookFactory(
|
||||
`assignRelationFromForeignKeyValue${foreignKeyName}_create`,
|
||||
"create",
|
||||
BeforeCreate
|
||||
)
|
||||
hookFactory(
|
||||
`assignRelationFromForeignKeyValue${foreignKeyName}_update`,
|
||||
"update",
|
||||
BeforeUpdate
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,82 +327,108 @@ export function defineBelongsToRelationship(
|
||||
*/
|
||||
const shouldCascade = !!relationCascades.delete?.includes(mappedBy)
|
||||
|
||||
/**
|
||||
* Ensure the mapped by is defined as relationship on the other side
|
||||
*/
|
||||
if (!otherSideRelation) {
|
||||
throw new Error(
|
||||
`Missing property "${mappedBy}" on "${relatedModelName}" entity. Make sure to define it as a relationship`
|
||||
)
|
||||
}
|
||||
|
||||
function applyForeignKeyAssignationHooks(foreignKeyName: string) {
|
||||
const hookName = `assignRelationFromForeignKeyValue${foreignKeyName}`
|
||||
/**
|
||||
* Hook to handle foreign key assignation
|
||||
*/
|
||||
MikroORMEntity.prototype[hookName] = function () {
|
||||
/**
|
||||
* In case of has one relation, in order to be able to have both ways
|
||||
* to associate a relation (through the relation or the foreign key) we need to handle it
|
||||
* specifically
|
||||
*/
|
||||
if (
|
||||
HasOne.isHasOne(otherSideRelation) ||
|
||||
HasOneWithForeignKey.isHasOneWithForeignKey(otherSideRelation)
|
||||
const hookFactory = function (
|
||||
name: string,
|
||||
type: "init" | "create" | "update",
|
||||
hookFn: Function
|
||||
) {
|
||||
MikroORMEntity.prototype[name] = function (
|
||||
this: typeof MikroORMEntity.prototype
|
||||
) {
|
||||
const relationMeta = this.__meta.relations.find(
|
||||
(relation) => relation.name === relationship.name
|
||||
).targetMeta
|
||||
this[relationship.name] ??= rel(
|
||||
relationMeta.class,
|
||||
this[foreignKeyName]
|
||||
)
|
||||
this[relationship.name] ??= this[relationship.name]?.id
|
||||
return
|
||||
}
|
||||
/**
|
||||
* In case of has one relation, in order to be able to have both ways
|
||||
* to associate a relation (through the relation or the foreign key) we need to handle it
|
||||
* specifically
|
||||
*/
|
||||
if (
|
||||
HasOne.isHasOne(otherSideRelation) ||
|
||||
HasOneWithForeignKey.isHasOneWithForeignKey(otherSideRelation)
|
||||
) {
|
||||
if (type !== "update") {
|
||||
// During creation
|
||||
const relationMeta = this.__meta.relations.find(
|
||||
(relation) => relation.name === relationship.name
|
||||
).targetMeta
|
||||
this[relationship.name] ??= rel(
|
||||
relationMeta.class,
|
||||
this[foreignKeyName]
|
||||
)
|
||||
this[foreignKeyName] ??= this[relationship.name]?.id
|
||||
|
||||
/**
|
||||
* Do not override the existing foreign key value if
|
||||
* exists
|
||||
*/
|
||||
if (this[foreignKeyName] !== undefined) {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the foreign key when the relationship is initialized
|
||||
* as null
|
||||
*/
|
||||
if (this[relationship.name] === null) {
|
||||
this[foreignKeyName] = null
|
||||
return
|
||||
}
|
||||
if (this[relationship.name]) {
|
||||
this[foreignKeyName] = this[relationship.name].id
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the foreign key when the relationship is initialized
|
||||
* and as the id
|
||||
*/
|
||||
if (this[relationship.name] && "id" in this[relationship.name]) {
|
||||
this[foreignKeyName] = this[relationship.name].id
|
||||
if (this[relationship.name] === null) {
|
||||
this[foreignKeyName] = null
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not override the existing foreign key value if
|
||||
* exists
|
||||
*/
|
||||
if (this[foreignKeyName] !== undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the foreign key when the relationship is initialized
|
||||
* as null
|
||||
*/
|
||||
if (this[relationship.name] === null) {
|
||||
this[foreignKeyName] = null
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the foreign key when the relationship is initialized
|
||||
* and as the id
|
||||
*/
|
||||
if (this[relationship.name] && "id" in this[relationship.name]) {
|
||||
this[foreignKeyName] = this[relationship.name].id
|
||||
}
|
||||
}
|
||||
hookFn()(MikroORMEntity.prototype, name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute hook via lifecycle decorators
|
||||
* Hook to handle foreign key assignation
|
||||
*/
|
||||
BeforeCreate()(MikroORMEntity.prototype, hookName)
|
||||
OnInit()(MikroORMEntity.prototype, hookName)
|
||||
hookFactory(
|
||||
`assignRelationFromForeignKeyValue${foreignKeyName}_init`,
|
||||
"init",
|
||||
OnInit
|
||||
)
|
||||
hookFactory(
|
||||
`assignRelationFromForeignKeyValue${foreignKeyName}_create`,
|
||||
"create",
|
||||
BeforeCreate
|
||||
)
|
||||
hookFactory(
|
||||
`assignRelationFromForeignKeyValue${foreignKeyName}_update`,
|
||||
"update",
|
||||
BeforeUpdate
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Otherside is a has many. Hence we should defined a ManyToOne
|
||||
*/
|
||||
if (
|
||||
!otherSideRelation ||
|
||||
HasMany.isHasMany(otherSideRelation) ||
|
||||
DmlManyToMany.isManyToMany(otherSideRelation)
|
||||
) {
|
||||
const foreignKeyName = camelToSnakeCase(`${relationship.name}Id`)
|
||||
const foreignKeyName =
|
||||
relationship.options.foreignKeyName ??
|
||||
camelToSnakeCase(`${relationship.name}Id`)
|
||||
const detachCascade =
|
||||
!!relationship.mappedBy &&
|
||||
relationCascades.detach?.includes(relationship.mappedBy)
|
||||
@@ -391,20 +483,18 @@ export function defineBelongsToRelationship(
|
||||
HasOne.isHasOne(otherSideRelation) ||
|
||||
HasOneWithForeignKey.isHasOneWithForeignKey(otherSideRelation)
|
||||
) {
|
||||
const foreignKeyName = camelToSnakeCase(`${relationship.name}Id`)
|
||||
|
||||
Object.defineProperty(MikroORMEntity.prototype, foreignKeyName, {
|
||||
value: null,
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
})
|
||||
const foreignKeyName =
|
||||
relationship.options.foreignKeyName ??
|
||||
camelToSnakeCase(`${relationship.name}Id`)
|
||||
|
||||
Property({
|
||||
columnType: "text",
|
||||
type: "string",
|
||||
nullable: relationship.nullable,
|
||||
persist: false,
|
||||
formula(alias) {
|
||||
return alias + "." + foreignKeyName
|
||||
},
|
||||
})(MikroORMEntity.prototype, foreignKeyName)
|
||||
|
||||
const oneToOneOptions: Parameters<typeof OneToOne>[0] = {
|
||||
@@ -413,6 +503,7 @@ export function defineBelongsToRelationship(
|
||||
mappedBy: mappedBy,
|
||||
fieldName: foreignKeyName,
|
||||
owner: true,
|
||||
// orphanRemoval: true,
|
||||
onDelete: shouldCascade ? "cascade" : undefined,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { BaseRelationship } from "./base"
|
||||
import { RelationNullableModifier } from "./nullable"
|
||||
|
||||
export class BelongsTo<T> extends BaseRelationship<T> {
|
||||
export class BelongsTo<
|
||||
T,
|
||||
const OptionalForeignKeyName extends string | undefined = undefined
|
||||
> extends BaseRelationship<T> {
|
||||
type = "belongsTo" as const
|
||||
declare $foreignKey: true
|
||||
declare $foreignKeyName: OptionalForeignKeyName
|
||||
|
||||
static isBelongsTo<T>(relationship: any): relationship is BelongsTo<T> {
|
||||
static isBelongsTo<T>(relationship: any): relationship is BelongsTo<T, any> {
|
||||
return relationship?.type === "belongsTo"
|
||||
}
|
||||
|
||||
@@ -13,6 +17,10 @@ export class BelongsTo<T> extends BaseRelationship<T> {
|
||||
* Apply nullable modifier on the schema
|
||||
*/
|
||||
nullable() {
|
||||
return new RelationNullableModifier<T, BelongsTo<T>, true>(this)
|
||||
return new RelationNullableModifier<
|
||||
T,
|
||||
BelongsTo<T, OptionalForeignKeyName>,
|
||||
true
|
||||
>(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,17 @@ import { RelationNullableModifier } from "./nullable"
|
||||
* You may use the "BelongsTo" relationship to define the inverse
|
||||
* of the "HasOne" relationship
|
||||
*/
|
||||
export class HasOneWithForeignKey<T> extends BaseRelationship<T> {
|
||||
export class HasOneWithForeignKey<
|
||||
T,
|
||||
const OptionalForeignKeyName extends string | undefined = undefined
|
||||
> extends BaseRelationship<T> {
|
||||
type = "hasOneWithFK" as const
|
||||
declare $foreignKey: true
|
||||
declare $foreignKeyName: OptionalForeignKeyName
|
||||
|
||||
static isHasOneWithForeignKey<T>(
|
||||
relationship: any
|
||||
): relationship is HasOneWithForeignKey<T> {
|
||||
): relationship is HasOneWithForeignKey<T, any> {
|
||||
return relationship?.type === "hasOneWithFK"
|
||||
}
|
||||
|
||||
@@ -25,6 +29,10 @@ export class HasOneWithForeignKey<T> extends BaseRelationship<T> {
|
||||
* Apply nullable modifier on the schema
|
||||
*/
|
||||
nullable() {
|
||||
return new RelationNullableModifier<T, HasOneWithForeignKey<T>, true>(this)
|
||||
return new RelationNullableModifier<
|
||||
T,
|
||||
HasOneWithForeignKey<T, OptionalForeignKeyName>,
|
||||
true
|
||||
>(this)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user