feat: convert MikroORM entities to DML entities (#10043)

* feat: convert MikroORM entities to DML entities

* feat: wip on repository changes

* continue repositories and types rework

* fix order repository usage

* continue to update product category repository

* Add foreign key as part of the inferred DML type

* ../../core/types/src/dml/index.ts

* ../../core/types/src/dml/index.ts

* fix: relationships mapping

* handle nullable foreign keys types

* handle nullable foreign keys types

* handle nullable foreign keys types

* continue to update product category repository

* fix all product category repositories issues

* fix product category service types

* fix product module service types

* fix product module service types

* fix repository template type

* refactor: use a singleton DMLToMikroORM factory instance

Since the MikroORM MetadataStorage is global, we will also have to turn DML
to MikroORM entities conversion use a global bucket as well

* refactor: update product module to use DML in tests

* wip: tests

* WIP product linkable fixes

* continue type fixing and start test fixing

* test: fix more tests

* fix repository

* fix pivot table computaion + fix mikro orm repository

* fix many to many management and configuration

* fix many to many management and configuration

* fix many to many management and configuration

* update product tag relation configuration

* Introduce experimental dml hooks to fix some issues with categories

* more fixes

* fix product tests

* add missing id prefixes

* fix product category handle management

* test: fix more failing tests

* test: make it all green

* test: fix breaking tests

* fix: build issues

* fix: build issues

* fix: more breaking tests

* refactor: fix issues after merge

* refactor: fix issues after merge

* refactor: surpress types error

* test: fix DML failing tests

* improve many to many inference + tests

* Wip fix columns from product entity

* remove product model before create hook and manage handle validation and transformation at the service level

* test: fix breaking unit tests

* fix: product module service to not update handle on product update

* fix define link and joiner config

* test: fix joiner config test

* test: fix joiner config test

* fix joiner config primary keys

* Fix joiner config builder

* Fix joiner config builder

* test: remove only modifier from test

* refactor: remove hooks usage from product collection

* refactor: remove hooks usage from product-option

* refactor: remove hooks usage for computing category handle

* refactor: remove hooks usage from productCategory model

* refactor: remove hooks from DML

* refactor: remove cruft

* cleanup

* re add foerign key indexes

* chore: remove unused types

* refactor: cleanup

* migration and models configuration adjustments

* cleanup

* fix random ordering

* fix

* test: fix product-category tests

* test: update breaking DML tests

* test: array assertion to not care about ordering

* fix: temporarily apply id ordering for products

* fix ordering

* fix ordering remove logs

---------

Co-authored-by: adrien2p <adrien.deperetti@gmail.com>
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Harminder Virk
2024-11-26 20:01:02 +05:30
committed by GitHub
co-authored by adrien2p Oli Juhl
parent d6fa912b22
commit 9f204817b0
61 changed files with 2315 additions and 1939 deletions
@@ -120,9 +120,9 @@ class Entity3 {
}
}
const Entity1Repository = mikroOrmBaseRepositoryFactory<Entity1>(Entity1)
const Entity2Repository = mikroOrmBaseRepositoryFactory<Entity2>(Entity2)
const Entity3Repository = mikroOrmBaseRepositoryFactory<Entity3>(Entity3)
const Entity1Repository = mikroOrmBaseRepositoryFactory(Entity1)
const Entity2Repository = mikroOrmBaseRepositoryFactory(Entity2)
const Entity3Repository = mikroOrmBaseRepositoryFactory(Entity3)
describe("mikroOrmRepository", () => {
let orm!: MikroORM
@@ -1,8 +1,10 @@
import {
BaseFilterable,
Context,
DAL,
FilterQuery,
FindOptions,
InferEntityType,
InferRepositoryReturnType,
FilterQuery as InternalFilterQuery,
PerformedActions,
RepositoryService,
@@ -15,11 +17,10 @@ import {
EntityName,
EntityProperty,
EntitySchema,
LoadStrategy,
FilterQuery as MikroFilterQuery,
FindOptions as MikroOptions,
LoadStrategy,
ReferenceType,
RequiredEntityData,
} from "@mikro-orm/core"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import {
@@ -28,6 +29,7 @@ import {
MedusaError,
promiseAll,
} from "../../common"
import { toMikroORMEntity } from "../../dml"
import { buildQuery } from "../../modules-sdk/build-query"
import {
getSoftDeletedCascadedEntitiesIdsMappedBy,
@@ -37,7 +39,7 @@ import { dbErrorMapper } from "./db-error-mapper"
import { mikroOrmSerializer } from "./mikro-orm-serializer"
import { mikroOrmUpdateDeletedAtRecursively } from "./utils"
export class MikroOrmBase<T = any> {
export class MikroOrmBase {
readonly manager_: any
protected constructor({ manager }) {
@@ -90,10 +92,12 @@ export class MikroOrmBase<T = any> {
* related ones.
*/
export class MikroOrmBaseRepository<T extends object = object>
extends MikroOrmBase<T>
export class MikroOrmBaseRepository<const T extends object = object>
extends MikroOrmBase
implements RepositoryService<T>
{
entity: EntityClass<InferEntityType<T>>
constructor(...args: any[]) {
// @ts-ignore
super(...arguments)
@@ -144,43 +148,55 @@ export class MikroOrmBaseRepository<T extends object = object>
})
}
create(data: unknown[], context?: Context): Promise<T[]> {
throw new Error("Method not implemented.")
}
update(data: { entity; update }[], context?: Context): Promise<T[]> {
throw new Error("Method not implemented.")
}
delete(
idsOrPKs: FilterQuery<T> & BaseFilterable<FilterQuery<T>>,
create(
data: unknown[],
context?: Context
): Promise<void> {
): Promise<InferRepositoryReturnType<T>[]> {
throw new Error("Method not implemented.")
}
find(options?: DAL.FindOptions<T>, context?: Context): Promise<T[]> {
update(
data: { entity; update }[],
context?: Context
): Promise<InferRepositoryReturnType<T>[]> {
throw new Error("Method not implemented.")
}
delete(idsOrPKs: FindOptions<T>["where"], context?: Context): Promise<void> {
throw new Error("Method not implemented.")
}
find(
options?: DAL.FindOptions<T>,
context?: Context
): Promise<InferRepositoryReturnType<T>[]> {
throw new Error("Method not implemented.")
}
findAndCount(
options?: DAL.FindOptions<T>,
context?: Context
): Promise<[T[], number]> {
): Promise<[InferRepositoryReturnType<T>[], number]> {
throw new Error("Method not implemented.")
}
upsert(data: unknown[], context: Context = {}): Promise<T[]> {
upsert(
data: unknown[],
context: Context = {}
): Promise<InferRepositoryReturnType<T>[]> {
throw new Error("Method not implemented.")
}
upsertWithReplace(
data: unknown[],
config: UpsertWithReplaceConfig<T> = {
config: UpsertWithReplaceConfig<InferRepositoryReturnType<T>> = {
relations: [],
},
context: Context = {}
): Promise<{ entities: T[]; performedActions: PerformedActions }> {
): Promise<{
entities: InferRepositoryReturnType<T>[]
performedActions: PerformedActions
}> {
throw new Error("Method not implemented.")
}
@@ -188,10 +204,10 @@ export class MikroOrmBaseRepository<T extends object = object>
filters:
| string
| string[]
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)[],
| DAL.FindOptions<T>["where"]
| DAL.FindOptions<T>["where"][],
sharedContext: Context = {}
): Promise<[T[], Record<string, unknown[]>]> {
): Promise<[InferRepositoryReturnType<T>[], Record<string, unknown[]>]> {
const entities = await this.find({ where: filters as any }, sharedContext)
const date = new Date()
@@ -212,8 +228,8 @@ export class MikroOrmBaseRepository<T extends object = object>
async restore(
idsOrFilter: string[] | InternalFilterQuery,
sharedContext: Context = {}
): Promise<[T[], Record<string, unknown[]>]> {
const query = buildQuery(idsOrFilter, {
): Promise<[InferRepositoryReturnType<T>[], Record<string, unknown[]>]> {
const query = buildQuery<T>(idsOrFilter, {
withDeleted: true,
})
@@ -245,13 +261,13 @@ export class MikroOrmBaseRepository<T extends object = object>
findOptions.where = {
$and: [findOptions.where, { $or: retrieveConstraintsToApply(q) }],
} as unknown as DAL.FilterQuery<T & { q?: string }>
} as unknown as DAL.FindOptions<T & { q?: string }>["where"]
}
}
export class MikroOrmBaseTreeRepository<
T extends object = object
> extends MikroOrmBase<T> {
const T extends object = object
> extends MikroOrmBase {
constructor() {
// @ts-ignore
super(...arguments)
@@ -261,7 +277,7 @@ export class MikroOrmBaseTreeRepository<
options?: DAL.FindOptions,
transformOptions?: RepositoryTransformOptions,
context?: Context
): Promise<T[]> {
): Promise<InferRepositoryReturnType<T>[]> {
throw new Error("Method not implemented.")
}
@@ -269,15 +285,21 @@ export class MikroOrmBaseTreeRepository<
options?: DAL.FindOptions,
transformOptions?: RepositoryTransformOptions,
context?: Context
): Promise<[T[], number]> {
): Promise<[InferRepositoryReturnType<T>[], number]> {
throw new Error("Method not implemented.")
}
create(data: unknown[], context?: Context): Promise<T[]> {
create(
data: unknown[],
context?: Context
): Promise<InferRepositoryReturnType<T>[]> {
throw new Error("Method not implemented.")
}
update(data: unknown[], context?: Context): Promise<T[]> {
update(
data: unknown[],
context?: Context
): Promise<InferRepositoryReturnType<T>[]> {
throw new Error("Method not implemented.")
}
@@ -286,12 +308,18 @@ export class MikroOrmBaseTreeRepository<
}
}
export function mikroOrmBaseRepositoryFactory<T extends object = object>(
entity: any
export function mikroOrmBaseRepositoryFactory<const T extends object>(
entity: T
): {
new ({ manager }: { manager: any }): MikroOrmBaseRepository<T>
} {
const mikroOrmEntity = toMikroORMEntity(entity) as EntityClass<
InferEntityType<T>
>
class MikroOrmAbstractBaseRepository_ extends MikroOrmBaseRepository<T> {
entity = mikroOrmEntity
// @ts-ignore
constructor(...args: any[]) {
// @ts-ignore
@@ -315,19 +343,19 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
})
}
async create(data: any[], context?: Context): Promise<T[]> {
async create(
data: any[],
context?: Context
): Promise<InferRepositoryReturnType<T>[]> {
const manager = this.getActiveManager<EntityManager>(context)
const entities = data.map((data_) => {
return manager.create(
entity as EntityName<T>,
data_ as RequiredEntityData<T>
)
return manager.create(this.entity, data_)
})
manager.persist(entities)
return entities
return entities as InferRepositoryReturnType<T>[]
}
/**
@@ -350,7 +378,7 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
const relations = manager
.getDriver()
.getMetadata()
.get(entity.name).relations
.get(this.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.
@@ -397,7 +425,10 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
}
}
async update(data: { entity; update }[], context?: Context): Promise<T[]> {
async update(
data: { entity; update }[],
context?: Context
): Promise<InferRepositoryReturnType<T>[]> {
const manager = this.getActiveManager<EntityManager>(context)
await this.initManyToManyToDetachAllItemsIfNeeded(data, context)
@@ -411,17 +442,17 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
}
async delete(
filters: FilterQuery<T> & BaseFilterable<FilterQuery<T>>,
filters: FindOptions<T>["where"],
context?: Context
): Promise<void> {
const manager = this.getActiveManager<EntityManager>(context)
await manager.nativeDelete<T>(entity as EntityName<T>, filters as any)
await manager.nativeDelete<T>(this.entity, filters)
}
async find(
options: DAL.FindOptions<T> = { where: {} },
options: DAL.FindOptions<T> = { where: {} } as DAL.FindOptions<T>,
context?: Context
): Promise<T[]> {
): Promise<InferRepositoryReturnType<T>[]> {
const manager = this.getActiveManager<EntityManager>(context)
const findOptions_ = { ...options }
@@ -443,17 +474,17 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
findOptions: findOptions_,
})
return await manager.find(
entity as EntityName<T>,
return (await manager.find(
this.entity as EntityName<T>,
findOptions_.where as MikroFilterQuery<T>,
findOptions_.options as MikroOptions<T>
)
)) as InferRepositoryReturnType<T>[]
}
async findAndCount(
findOptions: DAL.FindOptions<T> = { where: {} },
findOptions: DAL.FindOptions<T> = { where: {} } as DAL.FindOptions<T>,
context: Context = {}
): Promise<[T[], number]> {
): Promise<[InferRepositoryReturnType<T>[], number]> {
const manager = this.getActiveManager<EntityManager>(context)
const findOptions_ = { ...findOptions }
@@ -467,18 +498,22 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
findOptions: findOptions_,
})
return await manager.findAndCount(
entity as EntityName<T>,
findOptions_.where as MikroFilterQuery<T>,
findOptions_.options as MikroOptions<T>
)
return (await manager.findAndCount(
this.entity,
findOptions_.where,
findOptions_.options as any // MikroOptions<T>
)) as [InferRepositoryReturnType<T>[], number]
}
async upsert(data: any[], context: Context = {}): Promise<T[]> {
async upsert(
data: any[],
context: Context = {}
): Promise<InferRepositoryReturnType<T>[]> {
const manager = this.getActiveManager<EntityManager>(context)
const primaryKeys =
MikroOrmAbstractBaseRepository_.retrievePrimaryKeys(entity)
const primaryKeys = MikroOrmAbstractBaseRepository_.retrievePrimaryKeys(
this.entity
)
let primaryKeysCriteria: { [key: string]: any }[] = []
if (primaryKeys.length === 1) {
@@ -497,7 +532,7 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
}))
}
let allEntities: T[][] = []
let allEntities: InferRepositoryReturnType<T>[][] = []
if (primaryKeysCriteria.length) {
allEntities = await Promise.all(
@@ -513,7 +548,10 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
const existingEntities = allEntities.flat()
const existingEntitiesMap = new Map<string, T>()
const existingEntitiesMap = new Map<
string,
InferRepositoryReturnType<T>
>()
existingEntities.forEach((entity) => {
if (entity) {
const key =
@@ -525,9 +563,9 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
}
})
const upsertedEntities: T[] = []
const createdEntities: T[] = []
const updatedEntities: T[] = []
const upsertedEntities: InferRepositoryReturnType<T>[] = []
const createdEntities: InferRepositoryReturnType<T>[] = []
const updatedEntities: InferRepositoryReturnType<T>[] = []
data.forEach((data_) => {
// In case the data provided are just strings, then we build an object with the primary key as the key and the data as the valuecd -
@@ -542,8 +580,8 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
const updatedType = manager.assign(existingEntity, data_)
updatedEntities.push(updatedType)
} else {
const newEntity = manager.create<T>(entity, data_)
createdEntities.push(newEntity)
const newEntity = manager.create(this.entity, data_)
createdEntities.push(newEntity as InferRepositoryReturnType<T>)
}
})
@@ -558,7 +596,7 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
}
// TODO return the all, created, updated entities
return upsertedEntities
return upsertedEntities as InferRepositoryReturnType<T>[]
}
// UpsertWithReplace does several things to simplify module implementation.
@@ -570,11 +608,14 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
// We only support 1-level depth of upserts. We don't support custom fields on the many-to-many pivot tables for now
async upsertWithReplace(
data: any[],
config: UpsertWithReplaceConfig<T> = {
config: UpsertWithReplaceConfig<InferRepositoryReturnType<T>> = {
relations: [],
},
context: Context = {}
): Promise<{ entities: T[]; performedActions: PerformedActions }> {
): Promise<{
entities: InferRepositoryReturnType<T>[]
performedActions: PerformedActions
}> {
const performedActions: PerformedActions = {
created: {},
updated: {},
@@ -593,7 +634,7 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
const allRelations = manager
.getDriver()
.getMetadata()
.get(entity.name).relations
.get(this.entity.name).relations
const nonexistentRelations = arrayDifference(
(config.relations as any) ?? [],
@@ -624,7 +665,11 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
)
})
const mainEntity = this.getEntityWithId(manager, entity.name, entryCopy)
const mainEntity = this.getEntityWithId(
manager,
this.entity.name,
entryCopy
)
reconstructedResponse.push({ ...mainEntity, ...reconstructedEntry })
originalDataMap.set(mainEntity.id, entry)
@@ -634,7 +679,7 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
let {
orderedEntities: upsertedTopLevelEntities,
performedActions: performedActions_,
} = await this.upsertMany_(manager, entity.name, toUpsert)
} = await this.upsertMany_(manager, this.entity.name, toUpsert)
this.mergePerformedActions(performedActions, performedActions_)
@@ -954,10 +999,10 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
filters:
| string
| string[]
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)[],
| DAL.FindOptions<T>["where"]
| DAL.FindOptions<T>["where"][],
sharedContext: Context = {}
): Promise<[T[], Record<string, unknown[]>]> {
): Promise<[InferRepositoryReturnType<T>[], Record<string, unknown[]>]> {
if (Array.isArray(filters) && !filters.filter(Boolean).length) {
return [[], {}]
}
@@ -975,10 +1020,10 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
filters:
| string
| string[]
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)[],
| DAL.FindOptions<T>["where"]
| DAL.FindOptions<T>["where"][],
sharedContext: Context = {}
): Promise<[T[], Record<string, unknown[]>]> {
): Promise<[InferRepositoryReturnType<T>[], Record<string, unknown[]>]> {
if (Array.isArray(filters) && !filters.filter(Boolean).length) {
return [[], {}]
}
@@ -996,11 +1041,12 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
filters:
| string
| string[]
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)[]
) {
const primaryKeys =
MikroOrmAbstractBaseRepository_.retrievePrimaryKeys(entity)
| DAL.FindOptions<T>["where"]
| DAL.FindOptions<T>["where"][]
): DAL.FindOptions<T>["where"] {
const primaryKeys = MikroOrmAbstractBaseRepository_.retrievePrimaryKeys(
this.entity
)
const filterArray = Array.isArray(filters) ? filters : [filters]
const normalizedFilters: FilterQuery = {
@@ -1014,7 +1060,7 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
}),
}
return normalizedFilters
return normalizedFilters as DAL.FindOptions<T>["where"]
}
}