fix(utils): Mikro orm repository update many to many should detach all items by default (#9917)
* fix(utils): Mikro orm repository update many to many should detach all items by default * Create shiny-spiders-raise.md
This commit is contained in:
committed by
GitHub
parent
61cb97da26
commit
16b4cc433e
5
.changeset/shiny-spiders-raise.md
Normal file
5
.changeset/shiny-spiders-raise.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@medusajs/utils": patch
|
||||
---
|
||||
|
||||
fix(utils): Mikro orm repository update many to many should detach all items by default
|
||||
@@ -162,6 +162,42 @@ describe("mikroOrmRepository", () => {
|
||||
await orm.close(true)
|
||||
})
|
||||
|
||||
it("should successfully update a many to many collection providing an empty array", async () => {
|
||||
const entity1 = {
|
||||
id: "1",
|
||||
title: "en1",
|
||||
entity3: [{ title: "en3-1" }, { title: "en3-2" }],
|
||||
}
|
||||
|
||||
let manager = orm.em.fork()
|
||||
await manager1().create([entity1], { transactionManager: manager })
|
||||
await manager.flush()
|
||||
|
||||
const [createdEntity1] = await manager1().find({
|
||||
where: { id: "1" },
|
||||
options: { populate: ["entity3"] },
|
||||
})
|
||||
|
||||
expect(createdEntity1.entity3.getItems()).toHaveLength(2)
|
||||
|
||||
manager = orm.em.fork()
|
||||
await manager1().update(
|
||||
[{ entity: createdEntity1, update: { entity3: [] } }],
|
||||
{
|
||||
transactionManager: manager,
|
||||
}
|
||||
)
|
||||
await manager.flush()
|
||||
|
||||
const updatedEntity1 = await manager1().find({
|
||||
where: { id: "1" },
|
||||
options: { populate: ["entity3"] },
|
||||
})
|
||||
|
||||
expect(updatedEntity1).toHaveLength(1)
|
||||
expect(updatedEntity1[0].entity3.getItems()).toHaveLength(0)
|
||||
})
|
||||
|
||||
describe("upsert with replace", () => {
|
||||
it("should successfully create a flat entity", async () => {
|
||||
const entity1 = { id: "1", title: "en1", amount: 100 }
|
||||
|
||||
@@ -330,15 +330,84 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
|
||||
return entities
|
||||
}
|
||||
|
||||
/**
|
||||
* On a many to many relation, we expect to detach all the pivot items in case an empty array is provided.
|
||||
* In that case, this relation needs to be init as well as its counter part in order to be
|
||||
* able to perform the removal action.
|
||||
*
|
||||
* This action performs the initialization in the provided entity and therefore mutate in place.
|
||||
*
|
||||
* @param {{entity, update}[]} data
|
||||
* @param context
|
||||
* @private
|
||||
*/
|
||||
private async initManyToManyToDetachAllItemsIfNeeded(
|
||||
data: { entity; update }[],
|
||||
context?: Context
|
||||
) {
|
||||
const manager = this.getActiveManager<EntityManager>(context)
|
||||
|
||||
const relations = manager
|
||||
.getDriver()
|
||||
.getMetadata()
|
||||
.get(entity.name).relations
|
||||
|
||||
// In case an empty array is provided for a collection relation of type m:n, this relation needs to be init in order to be
|
||||
// able to perform an application cascade action.
|
||||
const collectionsToRemoveAllFrom: Map<
|
||||
string,
|
||||
{ name: string; mappedBy?: string }
|
||||
> = new Map()
|
||||
data.forEach(({ update }) =>
|
||||
Object.keys(update).filter((key) => {
|
||||
const relation = relations.find((relation) => relation.name === key)
|
||||
const shouldInit =
|
||||
relation &&
|
||||
relation.reference === ReferenceType.MANY_TO_MANY &&
|
||||
Array.isArray(update[key]) &&
|
||||
!update[key].length
|
||||
|
||||
if (shouldInit) {
|
||||
collectionsToRemoveAllFrom.set(key, {
|
||||
name: key,
|
||||
mappedBy: relations.find((r) => r.name === key)?.mappedBy,
|
||||
})
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
for (const [
|
||||
collectionToRemoveAllFrom,
|
||||
descriptor,
|
||||
] of collectionsToRemoveAllFrom) {
|
||||
await promiseAll(
|
||||
data.map(async ({ entity }) => {
|
||||
if (!descriptor.mappedBy) {
|
||||
return await entity[collectionToRemoveAllFrom].init()
|
||||
}
|
||||
|
||||
await entity[collectionToRemoveAllFrom].init()
|
||||
const items = entity[collectionToRemoveAllFrom]
|
||||
|
||||
for (const item of items) {
|
||||
await item[descriptor.mappedBy!].init()
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async update(data: { entity; update }[], context?: Context): Promise<T[]> {
|
||||
const manager = this.getActiveManager<EntityManager>(context)
|
||||
const entities = data.map((data_) => {
|
||||
return manager.assign(data_.entity, data_.update)
|
||||
|
||||
await this.initManyToManyToDetachAllItemsIfNeeded(data, context)
|
||||
|
||||
data.map((_, index) => {
|
||||
manager.assign(data[index].entity, data[index].update)
|
||||
manager.persist(data[index].entity)
|
||||
})
|
||||
|
||||
manager.persist(entities)
|
||||
|
||||
return entities
|
||||
return data.map((d) => d.entity)
|
||||
}
|
||||
|
||||
async delete(
|
||||
|
||||
Reference in New Issue
Block a user