chore(utils): Update base repository to infer primary keys and support composite (#6062)

This commit is contained in:
Adrien de Peretti authored and GitHub committed 2024-01-14 17:13:25 +01:00
1 parent 33ff2415ae
commit 72bc52231c
29 files changed
+264 -51

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
---
"@medusajs/authentication": patch
"@medusajs/pricing": patch
"@medusajs/utils": patch
---
chore(utils): Update base repository to infer primary keys and support composite
@@ -10,7 +10,12 @@ export class AuthProviderRepository extends DALUtils.mikroOrmBaseRepositoryFacto
{ {
create: RepositoryTypes.CreateAuthProviderDTO create: RepositoryTypes.CreateAuthProviderDTO
} }
>(AuthProvider, "provider") { >(AuthProvider) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
async update( async update(
data: RepositoryTypes.UpdateAuthProviderDTO[], data: RepositoryTypes.UpdateAuthProviderDTO[],
context: Context = {} context: Context = {}
@@ -8,6 +8,11 @@ import { SqlEntityManager } from "@mikro-orm/postgresql"
export class AuthUserRepository extends DALUtils.mikroOrmBaseRepositoryFactory( export class AuthUserRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
AuthUser AuthUser
) { ) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
async create( async create(
data: RepositoryTypes.CreateAuthUserDTO[], data: RepositoryTypes.CreateAuthUserDTO[],
context: Context = {} context: Context = {}
@@ -10,6 +10,11 @@ export class AddressRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
create: CreateAddressDTO create: CreateAddressDTO
} }
>(Address) { >(Address) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
async update( async update(
data: { address: Address; update: UpdateAddressDTO }[], data: { address: Address; update: UpdateAddressDTO }[],
context: Context = {} context: Context = {}
+5
View File
@@ -10,6 +10,11 @@ export class CartRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
create: CreateCartDTO create: CreateCartDTO
} }
>(Cart) { >(Cart) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
async update( async update(
data: { cart: Cart; update: UpdateCartDTO }[], data: { cart: Cart; update: UpdateCartDTO }[],
context: Context = {} context: Context = {}
@@ -9,4 +9,9 @@ export class CurrencyRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
create: RepositoryTypes.CreateCurrencyDTO create: RepositoryTypes.CreateCurrencyDTO
update: RepositoryTypes.UpdateCurrencyDTO update: RepositoryTypes.UpdateCurrencyDTO
} }
>(Currency, "code") {} >(Currency) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
}
@@ -9,4 +9,9 @@ export class MoneyAmountRepository extends DALUtils.mikroOrmBaseRepositoryFactor
create: RepositoryTypes.CreateMoneyAmountDTO create: RepositoryTypes.CreateMoneyAmountDTO
update: RepositoryTypes.UpdateMoneyAmountDTO update: RepositoryTypes.UpdateMoneyAmountDTO
} }
>(MoneyAmount) {} >(MoneyAmount) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
}
@@ -10,6 +10,11 @@ export class PriceListRuleValueRepository extends DALUtils.mikroOrmBaseRepositor
update: RepositoryTypes.UpdatePriceListRuleValueDTO update: RepositoryTypes.UpdatePriceListRuleValueDTO
} }
>(PriceListRuleValue) { >(PriceListRuleValue) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
async create( async create(
data: RepositoryTypes.CreatePriceListRuleValueDTO[], data: RepositoryTypes.CreatePriceListRuleValueDTO[],
context: Context = {} context: Context = {}
@@ -7,6 +7,11 @@ import { RepositoryTypes } from "@types"
export class PriceListRuleRepository extends DALUtils.mikroOrmBaseRepositoryFactory( export class PriceListRuleRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
PriceListRule PriceListRule
) { ) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
async create( async create(
data: RepositoryTypes.CreatePriceListRuleDTO[], data: RepositoryTypes.CreatePriceListRuleDTO[],
context: Context = {} context: Context = {}
@@ -7,6 +7,11 @@ import { RepositoryTypes } from "@types"
export class PriceListRepository extends DALUtils.mikroOrmBaseRepositoryFactory( export class PriceListRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
PriceList PriceList
) { ) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
async create( async create(
data: RepositoryTypes.CreatePriceListDTO[], data: RepositoryTypes.CreatePriceListDTO[],
context: Context = {} context: Context = {}
@@ -10,6 +10,11 @@ export class PriceRuleRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
update: RepositoryTypes.UpdatePriceRuleDTO update: RepositoryTypes.UpdatePriceRuleDTO
} }
>(PriceRule) { >(PriceRule) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
async create( async create(
data: RepositoryTypes.CreatePriceRuleDTO[], data: RepositoryTypes.CreatePriceRuleDTO[],
context: Context = {} context: Context = {}
@@ -9,4 +9,9 @@ export class PriceSetMoneyAmountRulesRepository extends DALUtils.mikroOrmBaseRep
create: RepositoryTypes.CreatePriceSetMoneyAmountRulesDTO create: RepositoryTypes.CreatePriceSetMoneyAmountRulesDTO
update: RepositoryTypes.UpdatePriceSetMoneyAmountRulesDTO update: RepositoryTypes.UpdatePriceSetMoneyAmountRulesDTO
} }
>(PriceSetMoneyAmountRules) {} >(PriceSetMoneyAmountRules) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
}
@@ -9,4 +9,9 @@ export class PriceSetMoneyAmountRepository extends DALUtils.mikroOrmBaseReposito
create: RepositoryTypes.CreatePriceSetMoneyAmountDTO create: RepositoryTypes.CreatePriceSetMoneyAmountDTO
update: RepositoryTypes.UpdatePriceSetMoneyAmountDTO update: RepositoryTypes.UpdatePriceSetMoneyAmountDTO
} }
>(PriceSetMoneyAmount) {} >(PriceSetMoneyAmount) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
}
@@ -9,4 +9,9 @@ export class PriceSetRuleTypeRepository extends DALUtils.mikroOrmBaseRepositoryF
create: RepositoryTypes.CreatePriceSetRuleTypeDTO create: RepositoryTypes.CreatePriceSetRuleTypeDTO
update: RepositoryTypes.UpdatePriceSetRuleTypeDTO update: RepositoryTypes.UpdatePriceSetRuleTypeDTO
} }
>(PriceSetRuleType) {} >(PriceSetRuleType) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
}
@@ -9,4 +9,9 @@ export class PriceSetRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
create: RepositoryTypes.CreatePriceSetDTO create: RepositoryTypes.CreatePriceSetDTO
update: RepositoryTypes.UpdatePriceSetDTO update: RepositoryTypes.UpdatePriceSetDTO
} }
>(PriceSet) {} >(PriceSet) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
}
@@ -8,4 +8,9 @@ export class RuleTypeRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
create: RepositoryTypes.CreateRuleTypeDTO create: RepositoryTypes.CreateRuleTypeDTO
update: RepositoryTypes.UpdateRuleTypeDTO update: RepositoryTypes.UpdateRuleTypeDTO
} }
>(RuleType) {} >(RuleType) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
}
@@ -14,6 +14,11 @@ type CreateProductCollection = ProductTypes.CreateProductCollectionDTO & {
export class ProductCollectionRepository extends DALUtils.mikroOrmBaseRepositoryFactory( export class ProductCollectionRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
ProductCollection ProductCollection
) { ) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
async create( async create(
data: CreateProductCollection[], data: CreateProductCollection[],
context: Context = {} context: Context = {}
@@ -7,6 +7,11 @@ import { DALUtils } from "@medusajs/utils"
export class ProductImageRepository extends DALUtils.mikroOrmBaseRepositoryFactory( export class ProductImageRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
Image Image
) { ) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
async upsert(urls: string[], context: Context = {}): Promise<Image[]> { async upsert(urls: string[], context: Context = {}): Promise<Image[]> {
const manager = this.getActiveManager<SqlEntityManager>(context) const manager = this.getActiveManager<SqlEntityManager>(context)
@@ -11,6 +11,11 @@ import { SqlEntityManager } from "@mikro-orm/postgresql"
export class ProductOptionValueRepository extends DALUtils.mikroOrmBaseRepositoryFactory( export class ProductOptionValueRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
ProductOptionValue ProductOptionValue
) { ) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
async upsert( async upsert(
optionValues: (UpdateProductOptionValueDTO | CreateProductOptionValueDTO)[], optionValues: (UpdateProductOptionValueDTO | CreateProductOptionValueDTO)[],
context: Context = {} context: Context = {}
@@ -10,6 +10,11 @@ export class ProductOptionRepository extends DALUtils.mikroOrmBaseRepositoryFact
update: ProductTypes.UpdateProductOptionDTO update: ProductTypes.UpdateProductOptionDTO
} }
>(ProductOption) { >(ProductOption) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
async create( async create(
data: ProductTypes.CreateProductOptionDTO[], data: ProductTypes.CreateProductOptionDTO[],
context: Context = {} context: Context = {}
@@ -15,6 +15,11 @@ export class ProductTagRepository extends DALUtils.mikroOrmBaseRepositoryFactory
update: UpdateProductTagDTO update: UpdateProductTagDTO
} }
>(ProductTag) { >(ProductTag) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
async upsert( async upsert(
tags: UpsertProductTagDTO[], tags: UpsertProductTagDTO[],
context: Context = {} context: Context = {}
@@ -14,6 +14,11 @@ export class ProductTypeRepository extends DALUtils.mikroOrmBaseRepositoryFactor
update: UpdateProductTypeDTO update: UpdateProductTypeDTO
} }
>(ProductType) { >(ProductType) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
async upsert( async upsert(
types: CreateProductTypeDTO[], types: CreateProductTypeDTO[],
context: Context = {} context: Context = {}
@@ -14,4 +14,9 @@ export class ProductVariantRepository extends DALUtils.mikroOrmBaseRepositoryFac
"id" "id"
> >
} }
>(ProductVariant) {} >(ProductVariant) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
}
@@ -24,6 +24,11 @@ export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
create: WithRequiredProperty<ProductTypes.CreateProductOnlyDTO, "status"> create: WithRequiredProperty<ProductTypes.CreateProductOnlyDTO, "status">
} }
>(Product) { >(Product) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
async find( async find(
findOptions: DAL.FindOptions<Product & { q?: string }> = { where: {} }, findOptions: DAL.FindOptions<Product & { q?: string }> = { where: {} },
context: Context = {} context: Context = {}
@@ -8,4 +8,9 @@ export class ApplicationMethodRepository extends DALUtils.mikroOrmBaseRepository
create: CreateApplicationMethodDTO create: CreateApplicationMethodDTO
update: UpdateApplicationMethodDTO update: UpdateApplicationMethodDTO
} }
>(ApplicationMethod) {} >(ApplicationMethod) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
}
@@ -11,4 +11,9 @@ export class PromotionRuleValueRepository extends DALUtils.mikroOrmBaseRepositor
create: CreatePromotionRuleValueDTO create: CreatePromotionRuleValueDTO
update: UpdatePromotionRuleValueDTO update: UpdatePromotionRuleValueDTO
} }
>(PromotionRuleValue) {} >(PromotionRuleValue) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
}
@@ -8,4 +8,9 @@ export class PromotionRuleRepository extends DALUtils.mikroOrmBaseRepositoryFact
create: CreatePromotionRuleDTO create: CreatePromotionRuleDTO
update: UpdatePromotionRuleDTO update: UpdatePromotionRuleDTO
} }
>(PromotionRule) {} >(PromotionRule) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
}
@@ -8,4 +8,9 @@ export class PromotionRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
create: CreatePromotionDTO create: CreatePromotionDTO
Update: UpdatePromotionDTO Update: UpdatePromotionDTO
} }
>(Promotion) {} >(Promotion) {
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
}
@@ -7,7 +7,6 @@ import {
import { import {
EntityManager, EntityManager,
EntitySchema, EntitySchema,
FilterQuery,
LoadStrategy, LoadStrategy,
RequiredEntityData, RequiredEntityData,
} from "@mikro-orm/core" } from "@mikro-orm/core"
@@ -17,9 +16,9 @@ import {
EntityName, EntityName,
FilterQuery as MikroFilterQuery, FilterQuery as MikroFilterQuery,
} from "@mikro-orm/core/typings" } from "@mikro-orm/core/typings"
import { MedusaError, arrayDifference, isString } from "../../common" import { isString, MedusaError } from "../../common"
import { MedusaContext } from "../../decorators" import { MedusaContext } from "../../decorators"
import { InjectTransactionManager, buildQuery } from "../../modules-sdk" import { buildQuery, InjectTransactionManager } from "../../modules-sdk"
import { import {
getSoftDeletedCascadedEntitiesIdsMappedBy, getSoftDeletedCascadedEntitiesIdsMappedBy,
transactionWrapper, transactionWrapper,
@@ -77,10 +76,11 @@ export class MikroOrmBase<T = any> {
export class MikroOrmBaseRepository< export class MikroOrmBaseRepository<
T extends object = object T extends object = object
> extends MikroOrmBase<T> { > extends MikroOrmBase<T> {
constructor() { constructor(...args: any[]) {
// @ts-ignore // @ts-ignore
super(...arguments) super(...arguments)
} }
create(data: unknown[], context?: Context): Promise<T[]> { create(data: unknown[], context?: Context): Promise<T[]> {
throw new Error("Method not implemented.") throw new Error("Method not implemented.")
} }
@@ -89,7 +89,7 @@ export class MikroOrmBaseRepository<
throw new Error("Method not implemented.") throw new Error("Method not implemented.")
} }
delete(ids: string[], context?: Context): Promise<void> { delete(ids: string[] | object[], context?: Context): Promise<void> {
throw new Error("Method not implemented.") throw new Error("Method not implemented.")
} }
@@ -227,11 +227,25 @@ export function mikroOrmBaseRepositoryFactory<
TDTos extends { [K in DtoBasedMutationMethods]?: any } = { TDTos extends { [K in DtoBasedMutationMethods]?: any } = {
[K in DtoBasedMutationMethods]?: any [K in DtoBasedMutationMethods]?: any
} }
>( >(entity: EntityClass<T> | EntitySchema<T>) {
entity: EntityClass<T> | EntitySchema<T> | string,
primaryKey: string = "id"
) {
class MikroOrmAbstractBaseRepository_ extends MikroOrmBaseRepository<T> { class MikroOrmAbstractBaseRepository_ extends MikroOrmBaseRepository<T> {
// @ts-ignore
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
}
static buildUniqueCompositeKeyValue(keys: string[], data: object) {
return keys.map((k) => data[k]).join("_")
}
static retrievePrimaryKeys(entity: EntityClass<T> | EntitySchema<T>) {
return (
(entity as EntitySchema<T>).meta?.primaryKeys ??
(entity as EntityClass<T>).prototype.__meta.primaryKeys
)
}
async create(data: TDTos["create"][], context?: Context): Promise<T[]> { async create(data: TDTos["create"][], context?: Context): Promise<T[]> {
const manager = this.getActiveManager<EntityManager>(context) const manager = this.getActiveManager<EntityManager>(context)
@@ -250,42 +264,71 @@ export function mikroOrmBaseRepositoryFactory<
async update(data: TDTos["update"][], context?: Context): Promise<T[]> { async update(data: TDTos["update"][], context?: Context): Promise<T[]> {
const manager = this.getActiveManager<EntityManager>(context) const manager = this.getActiveManager<EntityManager>(context)
const primaryKeyValues: string[] = data.map((data_) => data_[primaryKey]) const primaryKeys =
const existingEntities = await this.find( MikroOrmAbstractBaseRepository_.retrievePrimaryKeys(entity)
{
where: { let primaryKeysCriteria: { [key: string]: any }[] = []
[primaryKey]: { if (primaryKeys.length === 1) {
$in: primaryKeyValues, primaryKeysCriteria.push({
}, [primaryKeys[0]]: data.map((d) => d[primaryKeys[0]]),
}, })
} as DAL.FindOptions<T>, } else {
context primaryKeysCriteria = data.map((d) => ({
$and: primaryKeys.map((key) => ({ [key]: d[key] })),
}))
}
const allEntities = await Promise.all(
primaryKeysCriteria.map(
async (criteria) =>
await this.find({ where: criteria } as DAL.FindOptions<T>, context)
)
) )
const missingEntities = arrayDifference( const existingEntities = allEntities.flat()
data.map((d) => d[primaryKey]),
existingEntities.map((d: any) => d[primaryKey]) const existingEntitiesMap = new Map<string, T>()
) existingEntities.forEach((entity) => {
if (entity) {
const key =
MikroOrmAbstractBaseRepository_.buildUniqueCompositeKeyValue(
primaryKeys,
entity
)
existingEntitiesMap.set(key, entity)
}
})
const missingEntities = data.filter((data_) => {
const key =
MikroOrmAbstractBaseRepository_.buildUniqueCompositeKeyValue(
primaryKeys,
data_
)
return !existingEntitiesMap.has(key)
})
if (missingEntities.length) { if (missingEntities.length) {
const entityName = (entity as EntityClass<T>).name ?? entity const entityName = (entity as EntityClass<T>).name ?? entity
const missingEntitiesKeys = data.map((data_) =>
primaryKeys.map((key) => data_[key]).join(", ")
)
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_FOUND, MedusaError.Types.NOT_FOUND,
`${entityName} with ${[primaryKey]} "${missingEntities.join( `${entityName} with ${primaryKeys.join(
", " ", "
)}" not found` )} "${missingEntitiesKeys.join(", ")}" not found`
) )
} }
const existingEntitiesMap = new Map(
existingEntities.map<[string, T]>((entity_: any) => [
entity_[primaryKey],
entity_,
])
)
const entities = data.map((data_) => { const entities = data.map((data_) => {
const existingEntity = existingEntitiesMap.get(data_[primaryKey])! const key =
MikroOrmAbstractBaseRepository_.buildUniqueCompositeKeyValue(
primaryKeys,
data_
)
const existingEntity = existingEntitiesMap.get(key)!
return manager.assign(existingEntity, data_ as RequiredEntityData<T>) return manager.assign(existingEntity, data_ as RequiredEntityData<T>)
}) })
@@ -294,13 +337,41 @@ export function mikroOrmBaseRepositoryFactory<
return entities return entities
} }
async delete(primaryKeyValues: string[], context?: Context): Promise<void> { async delete(
primaryKeyValues: string[] | object[],
context?: Context
): Promise<void> {
const manager = this.getActiveManager<EntityManager>(context) const manager = this.getActiveManager<EntityManager>(context)
await manager.nativeDelete<T>( const primaryKeys =
entity as EntityName<T>, MikroOrmAbstractBaseRepository_.retrievePrimaryKeys(entity)
{ [primaryKey]: { $in: primaryKeyValues } } as unknown as FilterQuery<T>
) let deletionCriteria
if (primaryKeys.length > 1) {
deletionCriteria = {
$or: primaryKeyValues.map((compositeKeyValue) => {
const keys = Object.keys(compositeKeyValue)
if (!primaryKeys.every((k) => keys.includes(k))) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Composite key must contain all primary key fields: ${primaryKeys.join(
", "
)}. Found: ${keys}`
)
}
const criteria: { [key: string]: any } = {}
for (const key of primaryKeys) {
criteria[key] = compositeKeyValue[key]
}
return criteria
}),
}
} else {
deletionCriteria = { [primaryKeys[0]]: { $in: primaryKeyValues } }
}
await manager.nativeDelete<T>(entity as EntityName<T>, deletionCriteria)
} }
async find(options?: DAL.FindOptions<T>, context?: Context): Promise<T[]> { async find(options?: DAL.FindOptions<T>, context?: Context): Promise<T[]> {