feat: add check for uniqueness when creating links with isList=false (#11767)

This commit is contained in:
Harminder Virk
2025-03-17 13:23:18 +05:30
committed by GitHub
parent 879c623705
commit cae47d9e49
5 changed files with 145 additions and 26 deletions

View File

@@ -5,7 +5,13 @@ import {
ModuleJoinerRelationship,
} from "@medusajs/types"
import { isObject, Modules, promiseAll, toPascalCase } from "@medusajs/utils"
import {
isObject,
MedusaError,
Modules,
promiseAll,
toPascalCase,
} from "@medusajs/utils"
import { MedusaModule } from "./medusa-module"
import { convertRecordsToLinkDefinition } from "./utils/convert-data-to-link-definition"
import { linkingErrorMessage } from "./utils/linking-error"
@@ -380,18 +386,88 @@ export class Link {
const allLinks = Array.isArray(link) ? link : [link]
const serviceLinks = new Map<
string,
[string | string[], string, Record<string, unknown>?][]
{
linksToCreate: [string | string[], string, Record<string, unknown>?][]
linksToValidateForUniqueness: {
filters: { [key: string]: string }[]
services: string[]
}
}
>()
for (const link of allLinks) {
const service = this.getLinkModuleOrThrow(link)
const relationships = service.__joinerConfig.relationships
const { moduleA, moduleB, moduleBKey, primaryKeys } =
this.getLinkDataConfig(link)
if (!serviceLinks.has(service.__definition.key)) {
serviceLinks.set(service.__definition.key, [])
serviceLinks.set(service.__definition.key, {
/**
* Tuple of foreign key and the primary keys that must be
* persisted to the pivot table for representing the
* link
*/
linksToCreate: [],
/**
* An array of objects to validate for uniqueness before persisting
* data to the pivot table. When a link uses "isList: false", we
* have to limit a relationship with this entity to be a one-to-one
* or one-to-many
*/
linksToValidateForUniqueness: {
filters: [],
services: [],
},
})
}
relationships?.forEach((relationship) => {
const linksToValidateForUniqueness = serviceLinks.get(
service.__definition.key
)!.linksToValidateForUniqueness!
linksToValidateForUniqueness.services.push(relationship.serviceName)
/**
* When isList is set on false on the relationship, then it means
* we have a one-to-one or many-to-one relationship with the
* other side and we have limit duplicate entries from other
* entity. For example:
*
* - A brand has a many to one relationship with a product.
* - A product can have only one brand. Aka (brand.isList = false)
* - A brand can have multiple products. Aka (products.isList = true)
*
* A result of this, we have to ensure that a product_id can only appear
* once in the pivot table that is used for tracking "brand<>products"
* relationship.
*/
if (relationship.isList === false) {
const otherSide = relationships.find(
(other) => other.foreignKey !== relationship.foreignKey
)
if (!otherSide) {
return
}
if (moduleBKey === otherSide.foreignKey) {
linksToValidateForUniqueness.filters.push({
[otherSide.foreignKey]: link[moduleB][moduleBKey],
})
} else {
primaryKeys.forEach((pk) => {
if (pk === otherSide.foreignKey) {
linksToValidateForUniqueness.filters.push({
[otherSide.foreignKey]: link[moduleA][pk],
})
}
})
}
}
})
const pkValue =
primaryKeys.length === 1
? link[moduleA][primaryKeys[0]]
@@ -403,15 +479,39 @@ export class Link {
fields.push(link.data)
}
serviceLinks.get(service.__definition.key)?.push(fields as any)
serviceLinks
.get(service.__definition.key)
?.linksToCreate.push(fields as any)
}
for (const [serviceName, data] of serviceLinks) {
if (data.linksToValidateForUniqueness.filters.length) {
const service = this.modulesMap.get(serviceName)!
const existingLinks = await service.list(
{
$or: data.linksToValidateForUniqueness.filters,
},
{
take: 1,
}
)
if (existingLinks.length > 0) {
const serviceA = data.linksToValidateForUniqueness.services[0]
const serviceB = data.linksToValidateForUniqueness.services[1]
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot create multiple links between '${serviceA}' and '${serviceB}'`
)
}
}
}
const promises: Promise<unknown[]>[] = []
for (const [serviceName, links] of serviceLinks) {
for (const [serviceName, data] of serviceLinks) {
const service = this.modulesMap.get(serviceName)!
promises.push(service.create(links))
promises.push(service.create(data.linksToCreate))
}
return (await promiseAll(promises)).flat()