Skip to content

Commit

Permalink
feat: convert MikroORM entities to DML entities (#10043)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Co-authored-by: Oli Juhl <[email protected]>
  • Loading branch information
3 people authored Nov 26, 2024
1 parent d6fa912 commit 9f20481
Show file tree
Hide file tree
Showing 61 changed files with 2,117 additions and 1,741 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ medusaIntegrationTestRunner({
// BREAKING: Type input changed from {type: {value: string}} to {type_id: string}
type_id: baseType.id,
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
images: [{
images: [
{
url: "image-one",
},
{
Expand Down Expand Up @@ -139,7 +140,6 @@ medusaIntegrationTestRunner({
])
)
})


it("returns a list of products with all statuses when no status or invalid status is provided", async () => {
const res = await api
Expand Down Expand Up @@ -991,7 +991,10 @@ medusaIntegrationTestRunner({
})

it("should get a product with images ordered by rank", async () => {
const res = await api.get(`/admin/products/${baseProduct.id}`, adminHeaders)
const res = await api.get(
`/admin/products/${baseProduct.id}`,
adminHeaders
)

expect(res.data.product.images).toEqual(
expect.arrayContaining([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1436,7 +1436,7 @@ medusaIntegrationTestRunner({
},
{
url: "image-two",
}
},
],
})

Expand Down Expand Up @@ -1487,7 +1487,10 @@ medusaIntegrationTestRunner({
})

it("should retrieve product with images ordered by rank", async () => {
const response = await api.get(`/store/products/${product.id}`, storeHeaders)
const response = await api.get(
`/store/products/${product.id}`,
storeHeaders
)

expect(response.data.product.images).toEqual(
expect.arrayContaining([
Expand Down
10 changes: 6 additions & 4 deletions packages/core/types/src/dal/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { InferEntityType } from "../dml"
import { Dictionary, FilterQuery, Order } from "./utils"

export { FilterQuery, OperatorMap } from "./utils"
Expand All @@ -22,7 +23,7 @@ export interface BaseFilterable<T> {
/**
* The options to apply when retrieving an item.
*/
export interface OptionsQuery<T, P extends string = never> {
export interface OptionsQuery<T> {
/**
* Relations to populate in the retrieved items.
*/
Expand Down Expand Up @@ -54,7 +55,7 @@ export interface OptionsQuery<T, P extends string = never> {
/**
* Load strategy (e.g for mikro orm it accept select-in or joined)
*/
strategy?: 'select-in' | 'joined' | string & {}
strategy?: "select-in" | "joined" | (string & {})
}

/**
Expand All @@ -66,12 +67,13 @@ export type FindOptions<T = any> = {
/**
* The filters to apply on the items.
*/
where: FilterQuery<T> & BaseFilterable<FilterQuery<T>>
where: FilterQuery<InferEntityType<T>> &
BaseFilterable<FilterQuery<InferEntityType<T>>>

/**
* The options to apply when retrieving the items.
*/
options?: OptionsQuery<T, any>
options?: OptionsQuery<InferEntityType<T>>
}

/**
Expand Down
65 changes: 43 additions & 22 deletions packages/core/types/src/dal/repository-service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import { RepositoryTransformOptions } from "../common"
import { Context } from "../shared-context"
import {
BaseFilterable,
FilterQuery,
FilterQuery as InternalFilterQuery,
FindOptions,
UpsertWithReplaceConfig,
} from "./index"
import { EntityClass } from "@mikro-orm/core"
import { IDmlEntity, InferTypeOf } from "../dml"

type EntityClassName = string
type EntityValues = { id: string }[]

/**
* Either infer the properties from a DML object or from a Mikro orm class prototype.
*/
export type InferRepositoryReturnType<T> = T extends IDmlEntity<any, any>
? InferTypeOf<T>
: EntityClass<T>["prototype"]

export type PerformedActions = {
created: Record<EntityClassName, EntityValues>
updated: Record<EntityClassName, EntityValues>
Expand All @@ -22,7 +29,7 @@ export type PerformedActions = {
* This layer helps to separate the business logic (service layer) from accessing the
* ORM directly and allows to switch to another ORM without changing the business logic.
*/
interface BaseRepositoryService<T = any> {
interface BaseRepositoryService {
transaction<TManager = unknown>(
task: (transactionManager: TManager) => Promise<any>,
context?: {
Expand All @@ -42,22 +49,28 @@ interface BaseRepositoryService<T = any> {
): Promise<TOutput>
}

export interface RepositoryService<T = any> extends BaseRepositoryService<T> {
find(options?: FindOptions<T>, context?: Context): Promise<T[]>
export interface RepositoryService<T = any> extends BaseRepositoryService {
find(
options?: FindOptions<T>,
context?: Context
): Promise<InferRepositoryReturnType<T>[]>

findAndCount(
options?: FindOptions<T>,
context?: Context
): Promise<[T[], number]>

create(data: any[], context?: Context): Promise<T[]>
): Promise<[InferRepositoryReturnType<T>[], number]>

update(data: { entity; update }[], context?: Context): Promise<T[]>
create(
data: any[],
context?: Context
): Promise<InferRepositoryReturnType<T>[]>

delete(
idsOrPKs: FilterQuery<T> & BaseFilterable<FilterQuery<T>>,
update(
data: { entity; update }[],
context?: Context
): Promise<void>
): Promise<InferRepositoryReturnType<T>[]>

delete(idsOrPKs: FindOptions<T>["where"], context?: Context): Promise<void>

/**
* Soft delete entities and cascade to related entities if configured.
Expand All @@ -74,37 +87,45 @@ export interface RepositoryService<T = any> extends BaseRepositoryService<T> {
| InternalFilterQuery
| InternalFilterQuery[],
context?: Context
): Promise<[T[], Record<string, unknown[]>]>
): Promise<[InferRepositoryReturnType<T>[], Record<string, unknown[]>]>

restore(
idsOrFilter: string[] | InternalFilterQuery,
context?: Context
): Promise<[T[], Record<string, unknown[]>]>
): Promise<[InferRepositoryReturnType<T>[], Record<string, unknown[]>]>

upsert(data: any[], context?: Context): Promise<T[]>
upsert(
data: any[],
context?: Context
): Promise<InferRepositoryReturnType<T>[]>

upsertWithReplace(
data: any[],
config?: UpsertWithReplaceConfig<T>,
config?: UpsertWithReplaceConfig<InferRepositoryReturnType<T>>,
context?: Context
): Promise<{ entities: T[]; performedActions: PerformedActions }>
): Promise<{
entities: InferRepositoryReturnType<T>[]
performedActions: PerformedActions
}>
}

export interface TreeRepositoryService<T = any>
extends BaseRepositoryService<T> {
export interface TreeRepositoryService<T = any> extends BaseRepositoryService {
find(
options?: FindOptions<T>,
transformOptions?: RepositoryTransformOptions,
context?: Context
): Promise<T[]>
): Promise<InferRepositoryReturnType<T>[]>

findAndCount(
options?: FindOptions<T>,
transformOptions?: RepositoryTransformOptions,
context?: Context
): Promise<[T[], number]>
): Promise<[InferRepositoryReturnType<T>[], number]>

create(data: unknown[], context?: Context): Promise<T[]>
create(
data: unknown[],
context?: Context
): Promise<InferRepositoryReturnType<T>[]>

delete(ids: string[], context?: Context): Promise<void>
}
Expand Down
40 changes: 23 additions & 17 deletions packages/core/types/src/dal/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Constructor } from "../modules-sdk"

type ExpandProperty<T> = T extends (infer U)[] ? NonNullable<U> : NonNullable<T>

export type Dictionary<T = any> = {
Expand Down Expand Up @@ -84,25 +86,29 @@ type FilterValue<T> =

type PrevLimit = [never, 0, 1, 2]

export type FilterQueryProperties<T, Prev extends number = 3> = {
[Key in keyof T]?: T[Key] extends
| boolean
| number
| string
| bigint
| symbol
| Date
? T[Key] | OperatorMap<T[Key]>
: T[Key] extends infer U
? U extends { [x: number]: infer V }
? V extends object
? FilterQuery<Partial<V>, PrevLimit[Prev]>
: never
: never
: never
}

export type FilterQuery<T = any, Prev extends number = 3> = Prev extends never
? never
: {
[Key in keyof T]?: T[Key] extends
| boolean
| number
| string
| bigint
| symbol
| Date
? T[Key] | OperatorMap<T[Key]>
: T[Key] extends infer U
? U extends { [x: number]: infer V }
? V extends object
? FilterQuery<Partial<V>, PrevLimit[Prev]>
: never
: never
: never
}
: T extends Constructor<infer Prototype>
? FilterQueryProperties<Prototype, Prev>
: FilterQueryProperties<T, Prev>

declare type QueryOrder = "ASC" | "DESC" | "asc" | "desc"

Expand Down
49 changes: 28 additions & 21 deletions packages/core/types/src/dml/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ export type RelationshipTypes =
| "belongsTo"
| "manyToMany"

/**
* Return true if the relationship is nullable
*/
export type IsNullableRelation<T> = T extends () => IDmlEntity<any, any> | null
? true
: false

/**
* The meta-data returned by the property parse method
*/
Expand Down Expand Up @@ -135,14 +142,12 @@ export interface EntityConstructor<Props> extends Function {
* "belongsTo" relation meaning "hasOne" and "ManyToOne"
*/
export type InferForeignKeys<Schema extends DMLSchema> = {
[K in keyof Schema as Schema[K] extends { type: infer Type }
? Type extends RelationshipTypes
? `${K & string}_id`
: never
: never]: Schema[K] extends { type: infer Type }
? Type extends RelationshipTypes
? string
: never
[K in keyof Schema as Schema[K] extends { type: "belongsTo" }
? `${K & string}_id`
: never]: Schema[K] extends { type: "belongsTo" }
? null extends Schema[K]["$dataType"]
? string | null
: string
: never
}

Expand Down Expand Up @@ -182,19 +187,21 @@ export type InferManyToManyFields<Relation> = InferHasManyFields<Relation>
* Inferring the types of the schema fields from the DML
* entity
*/
export type InferSchemaFields<Schema extends DMLSchema> = Prettify<{
[K in keyof Schema]: Schema[K] extends RelationshipType<any>
? Schema[K]["type"] extends "belongsTo"
? InferBelongsToFields<Schema[K]["$dataType"]>
: Schema[K]["type"] extends "hasOne"
? InferHasOneFields<Schema[K]["$dataType"]>
: Schema[K]["type"] extends "hasMany"
? InferHasManyFields<Schema[K]["$dataType"]>
: Schema[K]["type"] extends "manyToMany"
? InferManyToManyFields<Schema[K]["$dataType"]>
: never
: Schema[K]["$dataType"]
}>
export type InferSchemaFields<Schema extends DMLSchema> = Prettify<
{
[K in keyof Schema]: Schema[K] extends RelationshipType<any>
? Schema[K]["type"] extends "belongsTo"
? InferBelongsToFields<Schema[K]["$dataType"]>
: Schema[K]["type"] extends "hasOne"
? InferHasOneFields<Schema[K]["$dataType"]>
: Schema[K]["type"] extends "hasMany"
? InferHasManyFields<Schema[K]["$dataType"]>
: Schema[K]["type"] extends "manyToMany"
? InferManyToManyFields<Schema[K]["$dataType"]>
: never
: Schema[K]["$dataType"]
} & InferForeignKeys<Schema>
>

/**
* Helper to infer the schema type of a DmlEntity
Expand Down
2 changes: 1 addition & 1 deletion packages/core/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"scripts": {
"build": "rimraf dist && tsc --build",
"watch": "tsc --build --watch",
"test": "jest --silent=false --bail --maxWorkers=50% --forceExit --testPathIgnorePatterns='/integration-tests/' -- src/**/__tests__/**/*.ts",
"test": "jest --silent --bail --maxWorkers=50% --forceExit --testPathIgnorePatterns='/integration-tests/' -- src/**/__tests__/**/*.ts",
"test:integration": "jest --silent --bail --runInBand --forceExit -- src/**/integration-tests/__tests__/**/*.ts"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 9f20481

Please sign in to comment.