Skip to content

Commit

Permalink
chore(utils): Soft delete should allow self referencing circular deps (
Browse files Browse the repository at this point in the history
…#6504)

**What**
Self referencing circular dep should not be an error and should be accepted
  • Loading branch information
adrien2p authored Feb 26, 2024
1 parent d983329 commit 56cbf88
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/fair-baboons-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@medusajs/utils": patch
---

chore(utils): Soft delete should allow self referencing circular deps
2 changes: 1 addition & 1 deletion packages/tax/integration-tests/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import { ITaxModuleService } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk"

Expand Down
37 changes: 37 additions & 0 deletions packages/utils/src/dal/mikro-orm/__fixtures__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,42 @@ class DeepRecursiveEntity4 {
entity1: DeepRecursiveEntity1
}

// Internal circular dependency

@Entity()
class InternalCircularDependencyEntity1 {
constructor(props: {
id: string
deleted_at: Date | null
parent?: InternalCircularDependencyEntity1
}) {
this.id = props.id
this.deleted_at = props.deleted_at

if (props.parent) {
this.parent = props.parent
}
}

@PrimaryKey()
id: string

@Property()
deleted_at: Date | null

@OneToMany(
() => InternalCircularDependencyEntity1,
(entity) => entity.parent,
{
cascade: ["soft-remove"] as any,
}
)
children = new Collection<InternalCircularDependencyEntity1>(this)

@ManyToOne(() => InternalCircularDependencyEntity1)
parent: InternalCircularDependencyEntity1
}

export {
RecursiveEntity1,
RecursiveEntity2,
Expand All @@ -198,4 +234,5 @@ export {
DeepRecursiveEntity2,
DeepRecursiveEntity3,
DeepRecursiveEntity4,
InternalCircularDependencyEntity1,
}
22 changes: 22 additions & 0 deletions packages/utils/src/dal/mikro-orm/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
DeepRecursiveEntity4,
Entity1,
Entity2,
InternalCircularDependencyEntity1,
RecursiveEntity1,
RecursiveEntity2,
} from "../__fixtures__/utils"
Expand Down Expand Up @@ -38,6 +39,7 @@ describe("mikroOrmUpdateDeletedAtRecursively", () => {
DeepRecursiveEntity2,
DeepRecursiveEntity3,
DeepRecursiveEntity4,
InternalCircularDependencyEntity1,
],
dbName: "test",
type: "postgresql",
Expand Down Expand Up @@ -65,6 +67,26 @@ describe("mikroOrmUpdateDeletedAtRecursively", () => {
expect(entity2.deleted_at).toEqual(deletedAt)
})

it("should successfully mark the entities deleted_at recursively with internal parent/child relation", async () => {
const manager = orm.em.fork() as SqlEntityManager
const entity1 = new InternalCircularDependencyEntity1({
id: "1",
deleted_at: null,
})

const childEntity1 = new InternalCircularDependencyEntity1({
id: "2",
deleted_at: null,
parent: entity1,
})

const deletedAt = new Date()
await mikroOrmUpdateDeletedAtRecursively(manager, [entity1], deletedAt)

expect(entity1.deleted_at).toEqual(deletedAt)
expect(childEntity1.deleted_at).toEqual(deletedAt)
})

it("should throw an error when a circular dependency is detected", async () => {
const manager = orm.em.fork() as SqlEntityManager
const entity1 = new RecursiveEntity1({ id: "1", deleted_at: null })
Expand Down
30 changes: 26 additions & 4 deletions packages/utils/src/dal/mikro-orm/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ import { SqlEntityManager } from "@mikro-orm/postgresql"
function detectCircularDependency(
manager: SqlEntityManager,
entityMetadata: EntityMetadata,
visited: Set<string> = new Set()
visited: Set<string> = new Set(),
shouldStop: boolean = false
) {
if (shouldStop) {
return
}

visited.add(entityMetadata.className)

const relations = entityMetadata.relations
Expand All @@ -17,7 +22,9 @@ function detectCircularDependency(
for (const relation of relationsToCascade) {
const branchVisited = new Set(Array.from(visited))

if (branchVisited.has(relation.name)) {
const isSelfCircularDependency = entityMetadata.class === relation.entity()

if (!isSelfCircularDependency && branchVisited.has(relation.name)) {
const dependencies = Array.from(visited)
dependencies.push(entityMetadata.className)
const circularDependencyStr = dependencies.join(" -> ")
Expand All @@ -33,7 +40,12 @@ function detectCircularDependency(
.getMetadata()
.get(relation.type)

detectCircularDependency(manager, relationEntityMetadata, branchVisited)
detectCircularDependency(
manager,
relationEntityMetadata,
branchVisited,
isSelfCircularDependency
)
}
}

Expand Down Expand Up @@ -82,6 +94,9 @@ async function performCascadingSoftDeletion<T>(
if (!entityRelation) {
// Fixes the case of many to many through pivot table
entityRelation = await retrieveEntity()
if (!entityRelation) {
continue
}
}

const isCollection = "toArray" in entityRelation
Expand All @@ -94,10 +109,17 @@ async function performCascadingSoftDeletion<T>(
}
relationEntities = entityRelation.getItems()
} else {
const initializedEntityRelation = await wrap(entityRelation).init()
const wrappedEntity = wrap(entityRelation)
const initializedEntityRelation = wrappedEntity.isInitialized()
? entityRelation
: await wrap(entityRelation).init()
relationEntities = [initializedEntityRelation]
}

if (!relationEntities.length) {
continue
}

await mikroOrmUpdateDeletedAtRecursively(manager, relationEntities, value)
}

Expand Down

0 comments on commit 56cbf88

Please sign in to comment.