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:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/utils": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(utils): Mikro orm repository update many to many should detach all items by default
|
||||||
+36
@@ -162,6 +162,42 @@ describe("mikroOrmRepository", () => {
|
|||||||
await orm.close(true)
|
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", () => {
|
describe("upsert with replace", () => {
|
||||||
it("should successfully create a flat entity", async () => {
|
it("should successfully create a flat entity", async () => {
|
||||||
const entity1 = { id: "1", title: "en1", amount: 100 }
|
const entity1 = { id: "1", title: "en1", amount: 100 }
|
||||||
|
|||||||
@@ -330,15 +330,84 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
|
|||||||
return entities
|
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[]> {
|
async update(data: { entity; update }[], context?: Context): Promise<T[]> {
|
||||||
const manager = this.getActiveManager<EntityManager>(context)
|
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 data.map((d) => d.entity)
|
||||||
|
|
||||||
return entities
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(
|
async delete(
|
||||||
|
|||||||
Reference in New Issue
Block a user