chore: abstract the modules repository (#6035)

**What**
Reduce the work effort to create repositories when building new modules by abstracting the most common cases into the base class default implementation returned by a factory

- [x] Migrate all modules

Co-authored-by: Riqwan Thamir <5105988+riqwan@users.noreply.github.com>
This commit is contained in:
Adrien de Peretti
2024-01-10 14:12:02 +01:00
committed by GitHub
parent ef5024980d
commit b6ac768698
38 changed files with 411 additions and 2832 deletions
@@ -1,10 +1,10 @@
import {
Context,
DAL,
FilterQuery,
FilterQuery as InternalFilerQuery,
RepositoryTransformOptions,
} from "@medusajs/types"
import { isString } from "../../common"
import { arrayDifference, isString, MedusaError } from "../../common"
import { MedusaContext } from "../../decorators"
import { buildQuery, InjectTransactionManager } from "../../modules-sdk"
import {
@@ -12,9 +12,22 @@ import {
transactionWrapper,
} from "../utils"
import { mikroOrmSerializer, mikroOrmUpdateDeletedAtRecursively } from "./utils"
import {
EntityManager,
EntitySchema,
FilterQuery,
LoadStrategy,
RequiredEntityData,
} from "@mikro-orm/core"
import {
EntityClass,
EntityName,
FilterQuery as MikroFilterQuery,
} from "@mikro-orm/core/typings"
import { FindOptions as MikroOptions } from "@mikro-orm/core/drivers/IDatabaseDriver"
export class MikroOrmBase<T = any> {
protected readonly manager_: any
readonly manager_: any
protected constructor({ manager }) {
this.manager_ = manager
@@ -53,28 +66,47 @@ export class MikroOrmBase<T = any> {
}
}
export abstract class MikroOrmAbstractBaseRepository<T = any>
extends MikroOrmBase
implements DAL.RepositoryService<T>
{
abstract find(options?: DAL.FindOptions<T>, context?: Context)
/**
* Privileged extends of the abstract classes unless most of the methods can't be implemented
* in your repository. This base repository is also used to provide a base repository
* injection if needed to be able to use the common methods without being related to an entity.
* In this case, none of the method will be implemented except the manager and transaction
* related ones.
*/
abstract findAndCount(
options?: DAL.FindOptions<T>,
context?: Context
): Promise<[T[], number]>
abstract create(data: unknown[], context?: Context): Promise<T[]>
export class MikroOrmBaseRepository<
T extends object = object
> extends MikroOrmBase<T> {
constructor() {
// @ts-ignore
super(...arguments)
}
create(data: unknown[], context?: Context): Promise<T[]> {
throw new Error("Method not implemented.")
}
update(data: unknown[], context?: Context): Promise<T[]> {
throw new Error("Method not implemented.")
}
abstract delete(ids: string[], context?: Context): Promise<void>
delete(ids: string[], context?: Context): Promise<void> {
throw new Error("Method not implemented.")
}
find(options?: DAL.FindOptions<T>, context?: Context): Promise<T[]> {
throw new Error("Method not implemented.")
}
findAndCount(
options?: DAL.FindOptions<T>,
context?: Context
): Promise<[T[], number]> {
throw new Error("Method not implemented.")
}
@InjectTransactionManager()
async softDelete(
idsOrFilter: string[] | FilterQuery,
idsOrFilter: string[] | InternalFilerQuery,
@MedusaContext()
{ transactionManager: manager }: Context = {}
): Promise<[T[], Record<string, unknown[]>]> {
@@ -91,7 +123,11 @@ export abstract class MikroOrmAbstractBaseRepository<T = any>
const entities = await this.find({ where: filter as any })
const date = new Date()
await mikroOrmUpdateDeletedAtRecursively(manager, entities, date)
await mikroOrmUpdateDeletedAtRecursively<T>(
manager,
entities as any[],
date
)
const softDeletedEntitiesMap = getSoftDeletedCascadedEntitiesIdsMappedBy({
entities,
@@ -102,7 +138,7 @@ export abstract class MikroOrmAbstractBaseRepository<T = any>
@InjectTransactionManager()
async restore(
idsOrFilter: string[] | FilterQuery,
idsOrFilter: string[] | InternalFilerQuery,
@MedusaContext()
{ transactionManager: manager }: Context = {}
): Promise<[T[], Record<string, unknown[]>]> {
@@ -122,7 +158,7 @@ export abstract class MikroOrmAbstractBaseRepository<T = any>
const entities = await this.find(query)
await mikroOrmUpdateDeletedAtRecursively(manager, entities, null)
await mikroOrmUpdateDeletedAtRecursively(manager, entities as any[], null)
const softDeletedEntitiesMap = getSoftDeletedCascadedEntitiesIdsMappedBy({
entities,
@@ -151,72 +187,10 @@ export abstract class MikroOrmAbstractBaseRepository<T = any>
}
}
export abstract class MikroOrmAbstractTreeRepositoryBase<T = any>
extends MikroOrmBase<T>
implements DAL.TreeRepositoryService<T>
{
protected constructor({ manager }) {
// @ts-ignore
super(...arguments)
}
abstract find(
options?: DAL.FindOptions<T>,
transformOptions?: RepositoryTransformOptions,
context?: Context
)
abstract findAndCount(
options?: DAL.FindOptions<T>,
transformOptions?: RepositoryTransformOptions,
context?: Context
): Promise<[T[], number]>
abstract create(data: unknown, context?: Context): Promise<T>
abstract delete(id: string, context?: Context): Promise<void>
}
/**
* Privileged extends of the abstract classes unless most of the methods can't be implemented
* in your repository. This base repository is also used to provide a base repository
* injection if needed to be able to use the common methods without being related to an entity.
* In this case, none of the method will be implemented except the manager and transaction
* related ones.
*/
export class MikroOrmBaseRepository extends MikroOrmAbstractBaseRepository {
constructor({ manager }) {
// @ts-ignore
super(...arguments)
}
create(data: unknown[], context?: Context): Promise<any[]> {
throw new Error("Method not implemented.")
}
update(data: unknown[], context?: Context): Promise<any[]> {
throw new Error("Method not implemented.")
}
delete(ids: string[], context?: Context): Promise<void> {
throw new Error("Method not implemented.")
}
find(options?: DAL.FindOptions, context?: Context): Promise<any[]> {
throw new Error("Method not implemented.")
}
findAndCount(
options?: DAL.FindOptions,
context?: Context
): Promise<[any[], number]> {
throw new Error("Method not implemented.")
}
}
export class MikroOrmBaseTreeRepository extends MikroOrmAbstractTreeRepositoryBase {
constructor({ manager }) {
export class MikroOrmBaseTreeRepository<
T extends object = object
> extends MikroOrmBase<T> {
constructor() {
// @ts-ignore
super(...arguments)
}
@@ -225,7 +199,7 @@ export class MikroOrmBaseTreeRepository extends MikroOrmAbstractTreeRepositoryBa
options?: DAL.FindOptions,
transformOptions?: RepositoryTransformOptions,
context?: Context
): Promise<any[]> {
): Promise<T[]> {
throw new Error("Method not implemented.")
}
@@ -233,11 +207,11 @@ export class MikroOrmBaseTreeRepository extends MikroOrmAbstractTreeRepositoryBa
options?: DAL.FindOptions,
transformOptions?: RepositoryTransformOptions,
context?: Context
): Promise<[any[], number]> {
): Promise<[T[], number]> {
throw new Error("Method not implemented.")
}
create(data: unknown, context?: Context): Promise<any> {
create(data: unknown, context?: Context): Promise<T> {
throw new Error("Method not implemented.")
}
@@ -245,3 +219,127 @@ export class MikroOrmBaseTreeRepository extends MikroOrmAbstractTreeRepositoryBa
throw new Error("Method not implemented.")
}
}
type DtoBasedMutationMethods = "create" | "update"
export function mikroOrmBaseRepositoryFactory<
T extends object = object,
TDTos extends { [K in DtoBasedMutationMethods]?: any } = {
[K in DtoBasedMutationMethods]?: any
}
>(
entity: EntityClass<T> | EntitySchema<T> | string,
primaryKey: string = "id"
) {
class MikroOrmAbstractBaseRepository_ extends MikroOrmBaseRepository<T> {
async create(data: TDTos["create"][], context?: Context): Promise<T[]> {
const manager = this.getActiveManager<EntityManager>(context)
const entities = data.map((data_) => {
return manager.create(
entity as EntityName<T>,
data_ as RequiredEntityData<T>
)
})
manager.persist(entities)
return entities
}
async update(data: TDTos["update"][], context?: Context): Promise<T[]> {
const manager = this.getActiveManager<EntityManager>(context)
const primaryKeyValues: string[] = data.map((data_) => data_[primaryKey])
const existingEntities = await this.find(
{
where: {
[primaryKey]: {
$in: primaryKeyValues,
},
},
} as DAL.FindOptions<T>,
context
)
const missingEntities = arrayDifference(
data.map((d) => d[primaryKey]),
existingEntities.map((d: any) => d[primaryKey])
)
if (missingEntities.length) {
const entityName = (entity as EntityClass<T>).name ?? entity
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`${entityName} with ${[primaryKey]} "${missingEntities.join(
", "
)}" not found`
)
}
const existingEntitiesMap = new Map(
existingEntities.map<[string, T]>((entity_: any) => [
entity_[primaryKey],
entity_,
])
)
const entities = data.map((data_) => {
const existingEntity = existingEntitiesMap.get(data_[primaryKey])!
return manager.assign(existingEntity, data_ as RequiredEntityData<T>)
})
manager.persist(entities)
return entities
}
async delete(primaryKeyValues: string[], context?: Context): Promise<void> {
const manager = this.getActiveManager<EntityManager>(context)
await manager.nativeDelete<T>(
entity as EntityName<T>,
{ [primaryKey]: { $in: primaryKeyValues } } as unknown as FilterQuery<T>
)
}
async find(options?: DAL.FindOptions<T>, context?: Context): Promise<T[]> {
const manager = this.getActiveManager<EntityManager>(context)
const findOptions_ = { ...options }
findOptions_.options ??= {}
Object.assign(findOptions_.options, {
strategy: LoadStrategy.SELECT_IN,
})
return await manager.find(
entity as EntityName<T>,
findOptions_.where as MikroFilterQuery<T>,
findOptions_.options as MikroOptions<T>
)
}
async findAndCount(
findOptions: DAL.FindOptions<T> = { where: {} },
context: Context = {}
): Promise<[T[], number]> {
const manager = this.getActiveManager<EntityManager>(context)
const findOptions_ = { ...findOptions }
findOptions_.options ??= {}
Object.assign(findOptions_.options, {
strategy: LoadStrategy.SELECT_IN,
})
return await manager.findAndCount(
entity as EntityName<T>,
findOptions_.where as MikroFilterQuery<T>,
findOptions_.options as MikroOptions<T>
)
}
}
return MikroOrmAbstractBaseRepository_
}
+1
View File
@@ -1,4 +1,5 @@
import { MedusaError } from "../common"
type RuleAttributeInput = string | undefined
export const ReservedPricingRuleAttributes = [