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:
61 files changed
+2315
-1939
No files matched your search
@@ -74,7 +74,8 @@ medusaIntegrationTestRunner({
|
|||||||
// BREAKING: Type input changed from {type: {value: string}} to {type_id: string}
|
// BREAKING: Type input changed from {type: {value: string}} to {type_id: string}
|
||||||
type_id: baseType.id,
|
type_id: baseType.id,
|
||||||
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
||||||
images: [{
|
images: [
|
||||||
|
{
|
||||||
url: "image-one",
|
url: "image-one",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -139,7 +140,6 @@ medusaIntegrationTestRunner({
|
|||||||
])
|
])
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
it("returns a list of products with all statuses when no status or invalid status is provided", async () => {
|
it("returns a list of products with all statuses when no status or invalid status is provided", async () => {
|
||||||
const res = await api
|
const res = await api
|
||||||
@@ -991,7 +991,10 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should get a product with images ordered by rank", async () => {
|
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(res.data.product.images).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
|
|||||||
@@ -1436,7 +1436,7 @@ medusaIntegrationTestRunner({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: "image-two",
|
url: "image-two",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1487,7 +1487,10 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should retrieve product with images ordered by rank", async () => {
|
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(response.data.product.images).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { InferEntityType } from "../dml"
|
||||||
import { Dictionary, FilterQuery, Order } from "./utils"
|
import { Dictionary, FilterQuery, Order } from "./utils"
|
||||||
|
|
||||||
export { FilterQuery, OperatorMap } from "./utils"
|
export { FilterQuery, OperatorMap } from "./utils"
|
||||||
@@ -22,7 +23,7 @@ export interface BaseFilterable<T> {
|
|||||||
/**
|
/**
|
||||||
* The options to apply when retrieving an item.
|
* 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.
|
* Relations to populate in the retrieved items.
|
||||||
*/
|
*/
|
||||||
@@ -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)
|
* Load strategy (e.g for mikro orm it accept select-in or joined)
|
||||||
*/
|
*/
|
||||||
strategy?: 'select-in' | 'joined' | string & {}
|
strategy?: "select-in" | "joined" | (string & {})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,12 +67,13 @@ export type FindOptions<T = any> = {
|
|||||||
/**
|
/**
|
||||||
* The filters to apply on the items.
|
* 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.
|
* The options to apply when retrieving the items.
|
||||||
*/
|
*/
|
||||||
options?: OptionsQuery<T, any>
|
options?: OptionsQuery<InferEntityType<T>>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
import { RepositoryTransformOptions } from "../common"
|
import { RepositoryTransformOptions } from "../common"
|
||||||
import { Context } from "../shared-context"
|
import { Context } from "../shared-context"
|
||||||
import {
|
import {
|
||||||
BaseFilterable,
|
|
||||||
FilterQuery,
|
|
||||||
FilterQuery as InternalFilterQuery,
|
FilterQuery as InternalFilterQuery,
|
||||||
FindOptions,
|
FindOptions,
|
||||||
UpsertWithReplaceConfig,
|
UpsertWithReplaceConfig,
|
||||||
} from "./index"
|
} from "./index"
|
||||||
|
import { EntityClass } from "@mikro-orm/core"
|
||||||
|
import { IDmlEntity, InferTypeOf } from "../dml"
|
||||||
|
|
||||||
type EntityClassName = string
|
type EntityClassName = string
|
||||||
type EntityValues = { id: 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 = {
|
export type PerformedActions = {
|
||||||
created: Record<EntityClassName, EntityValues>
|
created: Record<EntityClassName, EntityValues>
|
||||||
updated: Record<EntityClassName, EntityValues>
|
updated: Record<EntityClassName, EntityValues>
|
||||||
@@ -22,7 +29,7 @@ export type PerformedActions = {
|
|||||||
* This layer helps to separate the business logic (service layer) from accessing the
|
* 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.
|
* ORM directly and allows to switch to another ORM without changing the business logic.
|
||||||
*/
|
*/
|
||||||
interface BaseRepositoryService<T = any> {
|
interface BaseRepositoryService {
|
||||||
transaction<TManager = unknown>(
|
transaction<TManager = unknown>(
|
||||||
task: (transactionManager: TManager) => Promise<any>,
|
task: (transactionManager: TManager) => Promise<any>,
|
||||||
context?: {
|
context?: {
|
||||||
@@ -42,22 +49,28 @@ interface BaseRepositoryService<T = any> {
|
|||||||
): Promise<TOutput>
|
): Promise<TOutput>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RepositoryService<T = any> extends BaseRepositoryService<T> {
|
export interface RepositoryService<T = any> extends BaseRepositoryService {
|
||||||
find(options?: FindOptions<T>, context?: Context): Promise<T[]>
|
find(
|
||||||
|
options?: FindOptions<T>,
|
||||||
|
context?: Context
|
||||||
|
): Promise<InferRepositoryReturnType<T>[]>
|
||||||
|
|
||||||
findAndCount(
|
findAndCount(
|
||||||
options?: FindOptions<T>,
|
options?: FindOptions<T>,
|
||||||
context?: Context
|
context?: Context
|
||||||
): Promise<[T[], number]>
|
): Promise<[InferRepositoryReturnType<T>[], number]>
|
||||||
|
|
||||||
create(data: any[], context?: Context): Promise<T[]>
|
create(
|
||||||
|
data: any[],
|
||||||
update(data: { entity; update }[], context?: Context): Promise<T[]>
|
|
||||||
|
|
||||||
delete(
|
|
||||||
idsOrPKs: FilterQuery<T> & BaseFilterable<FilterQuery<T>>,
|
|
||||||
context?: Context
|
context?: Context
|
||||||
): Promise<void>
|
): Promise<InferRepositoryReturnType<T>[]>
|
||||||
|
|
||||||
|
update(
|
||||||
|
data: { entity; update }[],
|
||||||
|
context?: Context
|
||||||
|
): Promise<InferRepositoryReturnType<T>[]>
|
||||||
|
|
||||||
|
delete(idsOrPKs: FindOptions<T>["where"], context?: Context): Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Soft delete entities and cascade to related entities if configured.
|
* Soft delete entities and cascade to related entities if configured.
|
||||||
@@ -74,37 +87,45 @@ export interface RepositoryService<T = any> extends BaseRepositoryService<T> {
|
|||||||
| InternalFilterQuery
|
| InternalFilterQuery
|
||||||
| InternalFilterQuery[],
|
| InternalFilterQuery[],
|
||||||
context?: Context
|
context?: Context
|
||||||
): Promise<[T[], Record<string, unknown[]>]>
|
): Promise<[InferRepositoryReturnType<T>[], Record<string, unknown[]>]>
|
||||||
|
|
||||||
restore(
|
restore(
|
||||||
idsOrFilter: string[] | InternalFilterQuery,
|
idsOrFilter: string[] | InternalFilterQuery,
|
||||||
context?: Context
|
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(
|
upsertWithReplace(
|
||||||
data: any[],
|
data: any[],
|
||||||
config?: UpsertWithReplaceConfig<T>,
|
config?: UpsertWithReplaceConfig<InferRepositoryReturnType<T>>,
|
||||||
context?: Context
|
context?: Context
|
||||||
): Promise<{ entities: T[]; performedActions: PerformedActions }>
|
): Promise<{
|
||||||
|
entities: InferRepositoryReturnType<T>[]
|
||||||
|
performedActions: PerformedActions
|
||||||
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TreeRepositoryService<T = any>
|
export interface TreeRepositoryService<T = any> extends BaseRepositoryService {
|
||||||
extends BaseRepositoryService<T> {
|
|
||||||
find(
|
find(
|
||||||
options?: FindOptions<T>,
|
options?: FindOptions<T>,
|
||||||
transformOptions?: RepositoryTransformOptions,
|
transformOptions?: RepositoryTransformOptions,
|
||||||
context?: Context
|
context?: Context
|
||||||
): Promise<T[]>
|
): Promise<InferRepositoryReturnType<T>[]>
|
||||||
|
|
||||||
findAndCount(
|
findAndCount(
|
||||||
options?: FindOptions<T>,
|
options?: FindOptions<T>,
|
||||||
transformOptions?: RepositoryTransformOptions,
|
transformOptions?: RepositoryTransformOptions,
|
||||||
context?: Context
|
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>
|
delete(ids: string[], context?: Context): Promise<void>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Constructor } from "../modules-sdk"
|
||||||
|
|
||||||
type ExpandProperty<T> = T extends (infer U)[] ? NonNullable<U> : NonNullable<T>
|
type ExpandProperty<T> = T extends (infer U)[] ? NonNullable<U> : NonNullable<T>
|
||||||
|
|
||||||
export type Dictionary<T = any> = {
|
export type Dictionary<T = any> = {
|
||||||
@@ -84,25 +86,29 @@ type FilterValue<T> =
|
|||||||
|
|
||||||
type PrevLimit = [never, 0, 1, 2]
|
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
|
export type FilterQuery<T = any, Prev extends number = 3> = Prev extends never
|
||||||
? never
|
? never
|
||||||
: {
|
: T extends Constructor<infer Prototype>
|
||||||
[Key in keyof T]?: T[Key] extends
|
? FilterQueryProperties<Prototype, Prev>
|
||||||
| boolean
|
: FilterQueryProperties<T, Prev>
|
||||||
| 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
|
|
||||||
}
|
|
||||||
|
|
||||||
declare type QueryOrder = "ASC" | "DESC" | "asc" | "desc"
|
declare type QueryOrder = "ASC" | "DESC" | "asc" | "desc"
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,13 @@ export type RelationshipTypes =
|
|||||||
| "belongsTo"
|
| "belongsTo"
|
||||||
| "manyToMany"
|
| "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
|
* The meta-data returned by the property parse method
|
||||||
*/
|
*/
|
||||||
@@ -135,14 +142,12 @@ export interface EntityConstructor<Props> extends Function {
|
|||||||
* "belongsTo" relation meaning "hasOne" and "ManyToOne"
|
* "belongsTo" relation meaning "hasOne" and "ManyToOne"
|
||||||
*/
|
*/
|
||||||
export type InferForeignKeys<Schema extends DMLSchema> = {
|
export type InferForeignKeys<Schema extends DMLSchema> = {
|
||||||
[K in keyof Schema as Schema[K] extends { type: infer Type }
|
[K in keyof Schema as Schema[K] extends { type: "belongsTo" }
|
||||||
? Type extends RelationshipTypes
|
? `${K & string}_id`
|
||||||
? `${K & string}_id`
|
: never]: Schema[K] extends { type: "belongsTo" }
|
||||||
: never
|
? null extends Schema[K]["$dataType"]
|
||||||
: never]: Schema[K] extends { type: infer Type }
|
? string | null
|
||||||
? Type extends RelationshipTypes
|
: string
|
||||||
? string
|
|
||||||
: never
|
|
||||||
: never
|
: never
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,19 +187,21 @@ export type InferManyToManyFields<Relation> = InferHasManyFields<Relation>
|
|||||||
* Inferring the types of the schema fields from the DML
|
* Inferring the types of the schema fields from the DML
|
||||||
* entity
|
* entity
|
||||||
*/
|
*/
|
||||||
export type InferSchemaFields<Schema extends DMLSchema> = Prettify<{
|
export type InferSchemaFields<Schema extends DMLSchema> = Prettify<
|
||||||
[K in keyof Schema]: Schema[K] extends RelationshipType<any>
|
{
|
||||||
? Schema[K]["type"] extends "belongsTo"
|
[K in keyof Schema]: Schema[K] extends RelationshipType<any>
|
||||||
? InferBelongsToFields<Schema[K]["$dataType"]>
|
? Schema[K]["type"] extends "belongsTo"
|
||||||
: Schema[K]["type"] extends "hasOne"
|
? InferBelongsToFields<Schema[K]["$dataType"]>
|
||||||
? InferHasOneFields<Schema[K]["$dataType"]>
|
: Schema[K]["type"] extends "hasOne"
|
||||||
: Schema[K]["type"] extends "hasMany"
|
? InferHasOneFields<Schema[K]["$dataType"]>
|
||||||
? InferHasManyFields<Schema[K]["$dataType"]>
|
: Schema[K]["type"] extends "hasMany"
|
||||||
: Schema[K]["type"] extends "manyToMany"
|
? InferHasManyFields<Schema[K]["$dataType"]>
|
||||||
? InferManyToManyFields<Schema[K]["$dataType"]>
|
: Schema[K]["type"] extends "manyToMany"
|
||||||
: never
|
? InferManyToManyFields<Schema[K]["$dataType"]>
|
||||||
: Schema[K]["$dataType"]
|
: never
|
||||||
}>
|
: Schema[K]["$dataType"]
|
||||||
|
} & InferForeignKeys<Schema>
|
||||||
|
>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to infer the schema type of a DmlEntity
|
* Helper to infer the schema type of a DmlEntity
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rimraf dist && tsc --build",
|
"build": "rimraf dist && tsc --build",
|
||||||
"watch": "tsc --build --watch",
|
"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"
|
"test:integration": "jest --silent --bail --runInBand --forceExit -- src/**/integration-tests/__tests__/**/*.ts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-3
@@ -120,9 +120,9 @@ class Entity3 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Entity1Repository = mikroOrmBaseRepositoryFactory<Entity1>(Entity1)
|
const Entity1Repository = mikroOrmBaseRepositoryFactory(Entity1)
|
||||||
const Entity2Repository = mikroOrmBaseRepositoryFactory<Entity2>(Entity2)
|
const Entity2Repository = mikroOrmBaseRepositoryFactory(Entity2)
|
||||||
const Entity3Repository = mikroOrmBaseRepositoryFactory<Entity3>(Entity3)
|
const Entity3Repository = mikroOrmBaseRepositoryFactory(Entity3)
|
||||||
|
|
||||||
describe("mikroOrmRepository", () => {
|
describe("mikroOrmRepository", () => {
|
||||||
let orm!: MikroORM
|
let orm!: MikroORM
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
BaseFilterable,
|
|
||||||
Context,
|
Context,
|
||||||
DAL,
|
DAL,
|
||||||
FilterQuery,
|
FilterQuery,
|
||||||
|
FindOptions,
|
||||||
|
InferEntityType,
|
||||||
|
InferRepositoryReturnType,
|
||||||
FilterQuery as InternalFilterQuery,
|
FilterQuery as InternalFilterQuery,
|
||||||
PerformedActions,
|
PerformedActions,
|
||||||
RepositoryService,
|
RepositoryService,
|
||||||
@@ -15,11 +17,10 @@ import {
|
|||||||
EntityName,
|
EntityName,
|
||||||
EntityProperty,
|
EntityProperty,
|
||||||
EntitySchema,
|
EntitySchema,
|
||||||
|
LoadStrategy,
|
||||||
FilterQuery as MikroFilterQuery,
|
FilterQuery as MikroFilterQuery,
|
||||||
FindOptions as MikroOptions,
|
FindOptions as MikroOptions,
|
||||||
LoadStrategy,
|
|
||||||
ReferenceType,
|
ReferenceType,
|
||||||
RequiredEntityData,
|
|
||||||
} from "@mikro-orm/core"
|
} from "@mikro-orm/core"
|
||||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||||
import {
|
import {
|
||||||
@@ -28,6 +29,7 @@ import {
|
|||||||
MedusaError,
|
MedusaError,
|
||||||
promiseAll,
|
promiseAll,
|
||||||
} from "../../common"
|
} from "../../common"
|
||||||
|
import { toMikroORMEntity } from "../../dml"
|
||||||
import { buildQuery } from "../../modules-sdk/build-query"
|
import { buildQuery } from "../../modules-sdk/build-query"
|
||||||
import {
|
import {
|
||||||
getSoftDeletedCascadedEntitiesIdsMappedBy,
|
getSoftDeletedCascadedEntitiesIdsMappedBy,
|
||||||
@@ -37,7 +39,7 @@ import { dbErrorMapper } from "./db-error-mapper"
|
|||||||
import { mikroOrmSerializer } from "./mikro-orm-serializer"
|
import { mikroOrmSerializer } from "./mikro-orm-serializer"
|
||||||
import { mikroOrmUpdateDeletedAtRecursively } from "./utils"
|
import { mikroOrmUpdateDeletedAtRecursively } from "./utils"
|
||||||
|
|
||||||
export class MikroOrmBase<T = any> {
|
export class MikroOrmBase {
|
||||||
readonly manager_: any
|
readonly manager_: any
|
||||||
|
|
||||||
protected constructor({ manager }) {
|
protected constructor({ manager }) {
|
||||||
@@ -90,10 +92,12 @@ export class MikroOrmBase<T = any> {
|
|||||||
* related ones.
|
* related ones.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class MikroOrmBaseRepository<T extends object = object>
|
export class MikroOrmBaseRepository<const T extends object = object>
|
||||||
extends MikroOrmBase<T>
|
extends MikroOrmBase
|
||||||
implements RepositoryService<T>
|
implements RepositoryService<T>
|
||||||
{
|
{
|
||||||
|
entity: EntityClass<InferEntityType<T>>
|
||||||
|
|
||||||
constructor(...args: any[]) {
|
constructor(...args: any[]) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
super(...arguments)
|
super(...arguments)
|
||||||
@@ -144,43 +148,55 @@ export class MikroOrmBaseRepository<T extends object = object>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
create(data: unknown[], context?: Context): Promise<T[]> {
|
create(
|
||||||
throw new Error("Method not implemented.")
|
data: unknown[],
|
||||||
}
|
|
||||||
|
|
||||||
update(data: { entity; update }[], context?: Context): Promise<T[]> {
|
|
||||||
throw new Error("Method not implemented.")
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(
|
|
||||||
idsOrPKs: FilterQuery<T> & BaseFilterable<FilterQuery<T>>,
|
|
||||||
context?: Context
|
context?: Context
|
||||||
): Promise<void> {
|
): Promise<InferRepositoryReturnType<T>[]> {
|
||||||
throw new Error("Method not implemented.")
|
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.")
|
throw new Error("Method not implemented.")
|
||||||
}
|
}
|
||||||
|
|
||||||
findAndCount(
|
findAndCount(
|
||||||
options?: DAL.FindOptions<T>,
|
options?: DAL.FindOptions<T>,
|
||||||
context?: Context
|
context?: Context
|
||||||
): Promise<[T[], number]> {
|
): Promise<[InferRepositoryReturnType<T>[], number]> {
|
||||||
throw new Error("Method not implemented.")
|
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.")
|
throw new Error("Method not implemented.")
|
||||||
}
|
}
|
||||||
|
|
||||||
upsertWithReplace(
|
upsertWithReplace(
|
||||||
data: unknown[],
|
data: unknown[],
|
||||||
config: UpsertWithReplaceConfig<T> = {
|
config: UpsertWithReplaceConfig<InferRepositoryReturnType<T>> = {
|
||||||
relations: [],
|
relations: [],
|
||||||
},
|
},
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
): Promise<{ entities: T[]; performedActions: PerformedActions }> {
|
): Promise<{
|
||||||
|
entities: InferRepositoryReturnType<T>[]
|
||||||
|
performedActions: PerformedActions
|
||||||
|
}> {
|
||||||
throw new Error("Method not implemented.")
|
throw new Error("Method not implemented.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,10 +204,10 @@ export class MikroOrmBaseRepository<T extends object = object>
|
|||||||
filters:
|
filters:
|
||||||
| string
|
| string
|
||||||
| string[]
|
| string[]
|
||||||
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)
|
| DAL.FindOptions<T>["where"]
|
||||||
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)[],
|
| DAL.FindOptions<T>["where"][],
|
||||||
sharedContext: Context = {}
|
sharedContext: Context = {}
|
||||||
): Promise<[T[], Record<string, unknown[]>]> {
|
): Promise<[InferRepositoryReturnType<T>[], Record<string, unknown[]>]> {
|
||||||
const entities = await this.find({ where: filters as any }, sharedContext)
|
const entities = await this.find({ where: filters as any }, sharedContext)
|
||||||
const date = new Date()
|
const date = new Date()
|
||||||
|
|
||||||
@@ -212,8 +228,8 @@ export class MikroOrmBaseRepository<T extends object = object>
|
|||||||
async restore(
|
async restore(
|
||||||
idsOrFilter: string[] | InternalFilterQuery,
|
idsOrFilter: string[] | InternalFilterQuery,
|
||||||
sharedContext: Context = {}
|
sharedContext: Context = {}
|
||||||
): Promise<[T[], Record<string, unknown[]>]> {
|
): Promise<[InferRepositoryReturnType<T>[], Record<string, unknown[]>]> {
|
||||||
const query = buildQuery(idsOrFilter, {
|
const query = buildQuery<T>(idsOrFilter, {
|
||||||
withDeleted: true,
|
withDeleted: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -245,13 +261,13 @@ export class MikroOrmBaseRepository<T extends object = object>
|
|||||||
|
|
||||||
findOptions.where = {
|
findOptions.where = {
|
||||||
$and: [findOptions.where, { $or: retrieveConstraintsToApply(q) }],
|
$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<
|
export class MikroOrmBaseTreeRepository<
|
||||||
T extends object = object
|
const T extends object = object
|
||||||
> extends MikroOrmBase<T> {
|
> extends MikroOrmBase {
|
||||||
constructor() {
|
constructor() {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
super(...arguments)
|
super(...arguments)
|
||||||
@@ -261,7 +277,7 @@ export class MikroOrmBaseTreeRepository<
|
|||||||
options?: DAL.FindOptions,
|
options?: DAL.FindOptions,
|
||||||
transformOptions?: RepositoryTransformOptions,
|
transformOptions?: RepositoryTransformOptions,
|
||||||
context?: Context
|
context?: Context
|
||||||
): Promise<T[]> {
|
): Promise<InferRepositoryReturnType<T>[]> {
|
||||||
throw new Error("Method not implemented.")
|
throw new Error("Method not implemented.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,15 +285,21 @@ export class MikroOrmBaseTreeRepository<
|
|||||||
options?: DAL.FindOptions,
|
options?: DAL.FindOptions,
|
||||||
transformOptions?: RepositoryTransformOptions,
|
transformOptions?: RepositoryTransformOptions,
|
||||||
context?: Context
|
context?: Context
|
||||||
): Promise<[T[], number]> {
|
): Promise<[InferRepositoryReturnType<T>[], number]> {
|
||||||
throw new Error("Method not implemented.")
|
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.")
|
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.")
|
throw new Error("Method not implemented.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,12 +308,18 @@ export class MikroOrmBaseTreeRepository<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mikroOrmBaseRepositoryFactory<T extends object = object>(
|
export function mikroOrmBaseRepositoryFactory<const T extends object>(
|
||||||
entity: any
|
entity: T
|
||||||
): {
|
): {
|
||||||
new ({ manager }: { manager: any }): MikroOrmBaseRepository<T>
|
new ({ manager }: { manager: any }): MikroOrmBaseRepository<T>
|
||||||
} {
|
} {
|
||||||
|
const mikroOrmEntity = toMikroORMEntity(entity) as EntityClass<
|
||||||
|
InferEntityType<T>
|
||||||
|
>
|
||||||
|
|
||||||
class MikroOrmAbstractBaseRepository_ extends MikroOrmBaseRepository<T> {
|
class MikroOrmAbstractBaseRepository_ extends MikroOrmBaseRepository<T> {
|
||||||
|
entity = mikroOrmEntity
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
constructor(...args: any[]) {
|
constructor(...args: any[]) {
|
||||||
// @ts-ignore
|
// @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 manager = this.getActiveManager<EntityManager>(context)
|
||||||
|
|
||||||
const entities = data.map((data_) => {
|
const entities = data.map((data_) => {
|
||||||
return manager.create(
|
return manager.create(this.entity, data_)
|
||||||
entity as EntityName<T>,
|
|
||||||
data_ as RequiredEntityData<T>
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
manager.persist(entities)
|
manager.persist(entities)
|
||||||
|
|
||||||
return entities
|
return entities as InferRepositoryReturnType<T>[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -350,7 +378,7 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
|
|||||||
const relations = manager
|
const relations = manager
|
||||||
.getDriver()
|
.getDriver()
|
||||||
.getMetadata()
|
.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
|
// 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.
|
// 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)
|
const manager = this.getActiveManager<EntityManager>(context)
|
||||||
|
|
||||||
await this.initManyToManyToDetachAllItemsIfNeeded(data, context)
|
await this.initManyToManyToDetachAllItemsIfNeeded(data, context)
|
||||||
@@ -411,17 +442,17 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async delete(
|
async delete(
|
||||||
filters: FilterQuery<T> & BaseFilterable<FilterQuery<T>>,
|
filters: FindOptions<T>["where"],
|
||||||
context?: Context
|
context?: Context
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const manager = this.getActiveManager<EntityManager>(context)
|
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(
|
async find(
|
||||||
options: DAL.FindOptions<T> = { where: {} },
|
options: DAL.FindOptions<T> = { where: {} } as DAL.FindOptions<T>,
|
||||||
context?: Context
|
context?: Context
|
||||||
): Promise<T[]> {
|
): Promise<InferRepositoryReturnType<T>[]> {
|
||||||
const manager = this.getActiveManager<EntityManager>(context)
|
const manager = this.getActiveManager<EntityManager>(context)
|
||||||
|
|
||||||
const findOptions_ = { ...options }
|
const findOptions_ = { ...options }
|
||||||
@@ -443,17 +474,17 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
|
|||||||
findOptions: findOptions_,
|
findOptions: findOptions_,
|
||||||
})
|
})
|
||||||
|
|
||||||
return await manager.find(
|
return (await manager.find(
|
||||||
entity as EntityName<T>,
|
this.entity as EntityName<T>,
|
||||||
findOptions_.where as MikroFilterQuery<T>,
|
findOptions_.where as MikroFilterQuery<T>,
|
||||||
findOptions_.options as MikroOptions<T>
|
findOptions_.options as MikroOptions<T>
|
||||||
)
|
)) as InferRepositoryReturnType<T>[]
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAndCount(
|
async findAndCount(
|
||||||
findOptions: DAL.FindOptions<T> = { where: {} },
|
findOptions: DAL.FindOptions<T> = { where: {} } as DAL.FindOptions<T>,
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
): Promise<[T[], number]> {
|
): Promise<[InferRepositoryReturnType<T>[], number]> {
|
||||||
const manager = this.getActiveManager<EntityManager>(context)
|
const manager = this.getActiveManager<EntityManager>(context)
|
||||||
|
|
||||||
const findOptions_ = { ...findOptions }
|
const findOptions_ = { ...findOptions }
|
||||||
@@ -467,18 +498,22 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
|
|||||||
findOptions: findOptions_,
|
findOptions: findOptions_,
|
||||||
})
|
})
|
||||||
|
|
||||||
return await manager.findAndCount(
|
return (await manager.findAndCount(
|
||||||
entity as EntityName<T>,
|
this.entity,
|
||||||
findOptions_.where as MikroFilterQuery<T>,
|
findOptions_.where,
|
||||||
findOptions_.options as MikroOptions<T>
|
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 manager = this.getActiveManager<EntityManager>(context)
|
||||||
|
|
||||||
const primaryKeys =
|
const primaryKeys = MikroOrmAbstractBaseRepository_.retrievePrimaryKeys(
|
||||||
MikroOrmAbstractBaseRepository_.retrievePrimaryKeys(entity)
|
this.entity
|
||||||
|
)
|
||||||
|
|
||||||
let primaryKeysCriteria: { [key: string]: any }[] = []
|
let primaryKeysCriteria: { [key: string]: any }[] = []
|
||||||
if (primaryKeys.length === 1) {
|
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) {
|
if (primaryKeysCriteria.length) {
|
||||||
allEntities = await Promise.all(
|
allEntities = await Promise.all(
|
||||||
@@ -513,7 +548,10 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
|
|||||||
|
|
||||||
const existingEntities = allEntities.flat()
|
const existingEntities = allEntities.flat()
|
||||||
|
|
||||||
const existingEntitiesMap = new Map<string, T>()
|
const existingEntitiesMap = new Map<
|
||||||
|
string,
|
||||||
|
InferRepositoryReturnType<T>
|
||||||
|
>()
|
||||||
existingEntities.forEach((entity) => {
|
existingEntities.forEach((entity) => {
|
||||||
if (entity) {
|
if (entity) {
|
||||||
const key =
|
const key =
|
||||||
@@ -525,9 +563,9 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const upsertedEntities: T[] = []
|
const upsertedEntities: InferRepositoryReturnType<T>[] = []
|
||||||
const createdEntities: T[] = []
|
const createdEntities: InferRepositoryReturnType<T>[] = []
|
||||||
const updatedEntities: T[] = []
|
const updatedEntities: InferRepositoryReturnType<T>[] = []
|
||||||
|
|
||||||
data.forEach((data_) => {
|
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 -
|
// 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_)
|
const updatedType = manager.assign(existingEntity, data_)
|
||||||
updatedEntities.push(updatedType)
|
updatedEntities.push(updatedType)
|
||||||
} else {
|
} else {
|
||||||
const newEntity = manager.create<T>(entity, data_)
|
const newEntity = manager.create(this.entity, data_)
|
||||||
createdEntities.push(newEntity)
|
createdEntities.push(newEntity as InferRepositoryReturnType<T>)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -558,7 +596,7 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO return the all, created, updated entities
|
// TODO return the all, created, updated entities
|
||||||
return upsertedEntities
|
return upsertedEntities as InferRepositoryReturnType<T>[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpsertWithReplace does several things to simplify module implementation.
|
// 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
|
// 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(
|
async upsertWithReplace(
|
||||||
data: any[],
|
data: any[],
|
||||||
config: UpsertWithReplaceConfig<T> = {
|
config: UpsertWithReplaceConfig<InferRepositoryReturnType<T>> = {
|
||||||
relations: [],
|
relations: [],
|
||||||
},
|
},
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
): Promise<{ entities: T[]; performedActions: PerformedActions }> {
|
): Promise<{
|
||||||
|
entities: InferRepositoryReturnType<T>[]
|
||||||
|
performedActions: PerformedActions
|
||||||
|
}> {
|
||||||
const performedActions: PerformedActions = {
|
const performedActions: PerformedActions = {
|
||||||
created: {},
|
created: {},
|
||||||
updated: {},
|
updated: {},
|
||||||
@@ -593,7 +634,7 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
|
|||||||
const allRelations = manager
|
const allRelations = manager
|
||||||
.getDriver()
|
.getDriver()
|
||||||
.getMetadata()
|
.getMetadata()
|
||||||
.get(entity.name).relations
|
.get(this.entity.name).relations
|
||||||
|
|
||||||
const nonexistentRelations = arrayDifference(
|
const nonexistentRelations = arrayDifference(
|
||||||
(config.relations as any) ?? [],
|
(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 })
|
reconstructedResponse.push({ ...mainEntity, ...reconstructedEntry })
|
||||||
originalDataMap.set(mainEntity.id, entry)
|
originalDataMap.set(mainEntity.id, entry)
|
||||||
|
|
||||||
@@ -634,7 +679,7 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
|
|||||||
let {
|
let {
|
||||||
orderedEntities: upsertedTopLevelEntities,
|
orderedEntities: upsertedTopLevelEntities,
|
||||||
performedActions: performedActions_,
|
performedActions: performedActions_,
|
||||||
} = await this.upsertMany_(manager, entity.name, toUpsert)
|
} = await this.upsertMany_(manager, this.entity.name, toUpsert)
|
||||||
|
|
||||||
this.mergePerformedActions(performedActions, performedActions_)
|
this.mergePerformedActions(performedActions, performedActions_)
|
||||||
|
|
||||||
@@ -954,10 +999,10 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
|
|||||||
filters:
|
filters:
|
||||||
| string
|
| string
|
||||||
| string[]
|
| string[]
|
||||||
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)
|
| DAL.FindOptions<T>["where"]
|
||||||
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)[],
|
| DAL.FindOptions<T>["where"][],
|
||||||
sharedContext: Context = {}
|
sharedContext: Context = {}
|
||||||
): Promise<[T[], Record<string, unknown[]>]> {
|
): Promise<[InferRepositoryReturnType<T>[], Record<string, unknown[]>]> {
|
||||||
if (Array.isArray(filters) && !filters.filter(Boolean).length) {
|
if (Array.isArray(filters) && !filters.filter(Boolean).length) {
|
||||||
return [[], {}]
|
return [[], {}]
|
||||||
}
|
}
|
||||||
@@ -975,10 +1020,10 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
|
|||||||
filters:
|
filters:
|
||||||
| string
|
| string
|
||||||
| string[]
|
| string[]
|
||||||
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)
|
| DAL.FindOptions<T>["where"]
|
||||||
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)[],
|
| DAL.FindOptions<T>["where"][],
|
||||||
sharedContext: Context = {}
|
sharedContext: Context = {}
|
||||||
): Promise<[T[], Record<string, unknown[]>]> {
|
): Promise<[InferRepositoryReturnType<T>[], Record<string, unknown[]>]> {
|
||||||
if (Array.isArray(filters) && !filters.filter(Boolean).length) {
|
if (Array.isArray(filters) && !filters.filter(Boolean).length) {
|
||||||
return [[], {}]
|
return [[], {}]
|
||||||
}
|
}
|
||||||
@@ -996,11 +1041,12 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
|
|||||||
filters:
|
filters:
|
||||||
| string
|
| string
|
||||||
| string[]
|
| string[]
|
||||||
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)
|
| DAL.FindOptions<T>["where"]
|
||||||
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)[]
|
| DAL.FindOptions<T>["where"][]
|
||||||
) {
|
): DAL.FindOptions<T>["where"] {
|
||||||
const primaryKeys =
|
const primaryKeys = MikroOrmAbstractBaseRepository_.retrievePrimaryKeys(
|
||||||
MikroOrmAbstractBaseRepository_.retrievePrimaryKeys(entity)
|
this.entity
|
||||||
|
)
|
||||||
|
|
||||||
const filterArray = Array.isArray(filters) ? filters : [filters]
|
const filterArray = Array.isArray(filters) ? filters : [filters]
|
||||||
const normalizedFilters: FilterQuery = {
|
const normalizedFilters: FilterQuery = {
|
||||||
@@ -1014,7 +1060,7 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
return normalizedFilters
|
return normalizedFilters as DAL.FindOptions<T>["where"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,15 @@ import { DmlEntity } from "../entity"
|
|||||||
import { model } from "../entity-builder"
|
import { model } from "../entity-builder"
|
||||||
import { DuplicateIdPropertyError } from "../errors"
|
import { DuplicateIdPropertyError } from "../errors"
|
||||||
import {
|
import {
|
||||||
createMikrORMEntity,
|
mikroORMEntityBuilder,
|
||||||
toMikroOrmEntities,
|
|
||||||
toMikroORMEntity,
|
toMikroORMEntity,
|
||||||
|
toMikroOrmEntities,
|
||||||
} from "../helpers/create-mikro-orm-entity"
|
} from "../helpers/create-mikro-orm-entity"
|
||||||
|
|
||||||
describe("Entity builder", () => {
|
describe("Entity builder", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
MetadataStorage.clear()
|
MetadataStorage.clear()
|
||||||
|
mikroORMEntityBuilder.clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
const defaultColumnMetadata = {
|
const defaultColumnMetadata = {
|
||||||
@@ -992,12 +993,14 @@ describe("Entity builder", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const User = toMikroORMEntity(user)
|
const User = toMikroORMEntity(user)
|
||||||
expectTypeOf(new User()).toMatchTypeOf<{
|
expectTypeOf(new User()).toEqualTypeOf<{
|
||||||
id: number
|
id: number
|
||||||
username: string
|
username: string
|
||||||
email: string
|
email: string
|
||||||
role: "moderator" | "admin" | "guest"
|
role: "moderator" | "admin" | "guest"
|
||||||
deleted_at: Date | null
|
deleted_at: Date | null
|
||||||
|
created_at: Date
|
||||||
|
updated_at: Date
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||||
@@ -1939,6 +1942,11 @@ describe("Entity builder", () => {
|
|||||||
expression:
|
expression:
|
||||||
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_email_unique" ON "user" (email) WHERE deleted_at IS NULL',
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_email_unique" ON "user" (email) WHERE deleted_at IS NULL',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expression:
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_user_deleted_at" ON "user" (deleted_at) WHERE deleted_at IS NULL',
|
||||||
|
name: "IDX_user_deleted_at",
|
||||||
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
expect(metaData.filters).toEqual({
|
expect(metaData.filters).toEqual({
|
||||||
@@ -2050,6 +2058,11 @@ describe("Entity builder", () => {
|
|||||||
expression:
|
expression:
|
||||||
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_email_unique" ON "platform"."user" (email) WHERE deleted_at IS NULL',
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_email_unique" ON "platform"."user" (email) WHERE deleted_at IS NULL',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expression:
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_user_deleted_at" ON "platform"."user" (deleted_at) WHERE deleted_at IS NULL',
|
||||||
|
name: "IDX_user_deleted_at",
|
||||||
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
expect(metaData.filters).toEqual({
|
expect(metaData.filters).toEqual({
|
||||||
@@ -2160,6 +2173,11 @@ describe("Entity builder", () => {
|
|||||||
expression:
|
expression:
|
||||||
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_myEmail_unique" ON "user" (myEmail) WHERE deleted_at IS NULL',
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_myEmail_unique" ON "user" (myEmail) WHERE deleted_at IS NULL',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expression:
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_user_deleted_at" ON "user" (deleted_at) WHERE deleted_at IS NULL',
|
||||||
|
name: "IDX_user_deleted_at",
|
||||||
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
expect(metaData.filters).toEqual({
|
expect(metaData.filters).toEqual({
|
||||||
@@ -2819,7 +2837,6 @@ describe("Entity builder", () => {
|
|||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
setter: false,
|
setter: false,
|
||||||
type: "string",
|
type: "string",
|
||||||
isForeignKey: true,
|
|
||||||
persist: false,
|
persist: false,
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
@@ -2943,12 +2960,21 @@ describe("Entity builder", () => {
|
|||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: undefined,
|
onDelete: undefined,
|
||||||
reference: "m:1",
|
reference: "m:1",
|
||||||
isForeignKey: true,
|
|
||||||
},
|
},
|
||||||
...defaultColumnMetadata,
|
...defaultColumnMetadata,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(metaData.indexes).toEqual([
|
expect(metaData.indexes).toEqual([
|
||||||
|
{
|
||||||
|
expression:
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_user_group_id" ON "user" (group_id) WHERE deleted_at IS NULL',
|
||||||
|
name: "IDX_user_group_id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expression:
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_user_deleted_at" ON "user" (deleted_at) WHERE deleted_at IS NULL',
|
||||||
|
name: "IDX_user_deleted_at",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
expression:
|
expression:
|
||||||
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_email_account_unique" ON "user" (email, account) WHERE deleted_at IS NULL',
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_email_account_unique" ON "user" (email, account) WHERE deleted_at IS NULL',
|
||||||
@@ -2969,11 +2995,6 @@ describe("Entity builder", () => {
|
|||||||
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_unique-name" ON "user" (organization, account, group_id) WHERE deleted_at IS NULL',
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_unique-name" ON "user" (organization, account, group_id) WHERE deleted_at IS NULL',
|
||||||
name: "IDX_unique-name",
|
name: "IDX_unique-name",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
expression:
|
|
||||||
'CREATE INDEX IF NOT EXISTS "IDX_user_group_id" ON "user" (group_id) WHERE deleted_at IS NULL',
|
|
||||||
name: "IDX_user_group_id",
|
|
||||||
},
|
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -3025,6 +3046,16 @@ describe("Entity builder", () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
expect(metaData.indexes).toEqual([
|
expect(metaData.indexes).toEqual([
|
||||||
|
{
|
||||||
|
expression:
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_user_group_id" ON "user" (group_id) WHERE deleted_at IS NULL',
|
||||||
|
name: "IDX_user_group_id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expression:
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_user_deleted_at" ON "user" (deleted_at) WHERE deleted_at IS NULL',
|
||||||
|
name: "IDX_user_deleted_at",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
expression:
|
expression:
|
||||||
'CREATE INDEX IF NOT EXISTS "IDX_user_organization_account" ON "user" (organization, account) WHERE email IS NOT NULL AND deleted_at IS NULL',
|
'CREATE INDEX IF NOT EXISTS "IDX_user_organization_account" ON "user" (organization, account) WHERE email IS NOT NULL AND deleted_at IS NULL',
|
||||||
@@ -3050,11 +3081,6 @@ describe("Entity builder", () => {
|
|||||||
'CREATE INDEX IF NOT EXISTS "IDX_user_account_group_id" ON "user" (account, group_id) WHERE is_owner IS TRUE AND deleted_at IS NULL',
|
'CREATE INDEX IF NOT EXISTS "IDX_user_account_group_id" ON "user" (account, group_id) WHERE is_owner IS TRUE AND deleted_at IS NULL',
|
||||||
name: "IDX_user_account_group_id",
|
name: "IDX_user_account_group_id",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
expression:
|
|
||||||
'CREATE INDEX IF NOT EXISTS "IDX_user_group_id" ON "user" (group_id) WHERE deleted_at IS NULL',
|
|
||||||
name: "IDX_user_group_id",
|
|
||||||
},
|
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -3121,6 +3147,11 @@ describe("Entity builder", () => {
|
|||||||
'CREATE INDEX IF NOT EXISTS "IDX_user_group_id" ON "user" (group_id) WHERE deleted_at IS NULL',
|
'CREATE INDEX IF NOT EXISTS "IDX_user_group_id" ON "user" (group_id) WHERE deleted_at IS NULL',
|
||||||
name: "IDX_user_group_id",
|
name: "IDX_user_group_id",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expression:
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_user_deleted_at" ON "user" (deleted_at) WHERE deleted_at IS NULL',
|
||||||
|
name: "IDX_user_deleted_at",
|
||||||
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
const Setting = toMikroORMEntity(setting)
|
const Setting = toMikroORMEntity(setting)
|
||||||
@@ -3132,6 +3163,11 @@ describe("Entity builder", () => {
|
|||||||
'CREATE INDEX IF NOT EXISTS "IDX_setting_user_id" ON "setting" (user_id) WHERE deleted_at IS NULL',
|
'CREATE INDEX IF NOT EXISTS "IDX_setting_user_id" ON "setting" (user_id) WHERE deleted_at IS NULL',
|
||||||
name: "IDX_setting_user_id",
|
name: "IDX_setting_user_id",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expression:
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_setting_deleted_at" ON "setting" (deleted_at) WHERE deleted_at IS NULL',
|
||||||
|
name: "IDX_setting_deleted_at",
|
||||||
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -3543,7 +3579,6 @@ describe("Entity builder", () => {
|
|||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
reference: "m:1",
|
reference: "m:1",
|
||||||
isForeignKey: true,
|
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -3616,6 +3651,11 @@ describe("Entity builder", () => {
|
|||||||
}
|
}
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const userInstance = new User()
|
||||||
|
expectTypeOf<
|
||||||
|
(typeof userInstance)["email"]["user_id"]
|
||||||
|
>().toEqualTypeOf<string>()
|
||||||
|
|
||||||
expectTypeOf(new Email()).toMatchTypeOf<{
|
expectTypeOf(new Email()).toMatchTypeOf<{
|
||||||
email: string
|
email: string
|
||||||
isVerified: boolean
|
isVerified: boolean
|
||||||
@@ -3631,6 +3671,7 @@ describe("Entity builder", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}>()
|
}>()
|
||||||
|
expectTypeOf(new Email().user_id).toEqualTypeOf<string>()
|
||||||
|
|
||||||
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||||
expect(metaData.className).toEqual("User")
|
expect(metaData.className).toEqual("User")
|
||||||
@@ -3740,7 +3781,6 @@ describe("Entity builder", () => {
|
|||||||
name: "user_id",
|
name: "user_id",
|
||||||
getter: false,
|
getter: false,
|
||||||
setter: false,
|
setter: false,
|
||||||
isForeignKey: true,
|
|
||||||
persist: false,
|
persist: false,
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
@@ -3810,6 +3850,11 @@ describe("Entity builder", () => {
|
|||||||
}
|
}
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const userInstance = new User()
|
||||||
|
expectTypeOf<(typeof userInstance)["email"]["user_id"]>().toEqualTypeOf<
|
||||||
|
string | null
|
||||||
|
>()
|
||||||
|
|
||||||
expectTypeOf(new Email()).toMatchTypeOf<{
|
expectTypeOf(new Email()).toMatchTypeOf<{
|
||||||
email: string
|
email: string
|
||||||
isVerified: boolean
|
isVerified: boolean
|
||||||
@@ -3822,6 +3867,7 @@ describe("Entity builder", () => {
|
|||||||
}
|
}
|
||||||
} | null
|
} | null
|
||||||
}>()
|
}>()
|
||||||
|
expectTypeOf(new Email().user_id).toEqualTypeOf<string | null>()
|
||||||
|
|
||||||
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||||
expect(metaData.className).toEqual("User")
|
expect(metaData.className).toEqual("User")
|
||||||
@@ -3931,7 +3977,6 @@ describe("Entity builder", () => {
|
|||||||
name: "user_id",
|
name: "user_id",
|
||||||
getter: false,
|
getter: false,
|
||||||
setter: false,
|
setter: false,
|
||||||
isForeignKey: true,
|
|
||||||
persist: false,
|
persist: false,
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
@@ -4121,7 +4166,6 @@ describe("Entity builder", () => {
|
|||||||
mapToPk: true,
|
mapToPk: true,
|
||||||
fieldName: "user_id",
|
fieldName: "user_id",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
isForeignKey: true,
|
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -4310,7 +4354,6 @@ describe("Entity builder", () => {
|
|||||||
mapToPk: true,
|
mapToPk: true,
|
||||||
fieldName: "user_id",
|
fieldName: "user_id",
|
||||||
nullable: true,
|
nullable: true,
|
||||||
isForeignKey: true,
|
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -4566,7 +4609,6 @@ describe("Entity builder", () => {
|
|||||||
name: "user_id",
|
name: "user_id",
|
||||||
getter: false,
|
getter: false,
|
||||||
setter: false,
|
setter: false,
|
||||||
isForeignKey: true,
|
|
||||||
persist: false,
|
persist: false,
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
@@ -4765,7 +4807,6 @@ describe("Entity builder", () => {
|
|||||||
name: "user_id",
|
name: "user_id",
|
||||||
getter: false,
|
getter: false,
|
||||||
setter: false,
|
setter: false,
|
||||||
isForeignKey: true,
|
|
||||||
persist: false,
|
persist: false,
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
@@ -4872,7 +4913,6 @@ describe("Entity builder", () => {
|
|||||||
mapToPk: true,
|
mapToPk: true,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: undefined,
|
onDelete: undefined,
|
||||||
isForeignKey: true,
|
|
||||||
},
|
},
|
||||||
children: {
|
children: {
|
||||||
cascade: undefined,
|
cascade: undefined,
|
||||||
@@ -4983,7 +5023,6 @@ describe("Entity builder", () => {
|
|||||||
name: "parent_id",
|
name: "parent_id",
|
||||||
type: "string",
|
type: "string",
|
||||||
columnType: "text",
|
columnType: "text",
|
||||||
isForeignKey: true,
|
|
||||||
persist: false,
|
persist: false,
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
getter: false,
|
getter: false,
|
||||||
@@ -5110,8 +5149,9 @@ describe("Entity builder", () => {
|
|||||||
reference: "m:n",
|
reference: "m:n",
|
||||||
name: "teams",
|
name: "teams",
|
||||||
entity: "Team",
|
entity: "Team",
|
||||||
|
owner: true,
|
||||||
pivotTable: "team_users",
|
pivotTable: "team_users",
|
||||||
mappedBy: "users",
|
inversedBy: "users",
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -5177,7 +5217,9 @@ describe("Entity builder", () => {
|
|||||||
users: {
|
users: {
|
||||||
reference: "m:n",
|
reference: "m:n",
|
||||||
name: "users",
|
name: "users",
|
||||||
|
mappedBy: "teams",
|
||||||
entity: "User",
|
entity: "User",
|
||||||
|
owner: false,
|
||||||
pivotTable: "team_users",
|
pivotTable: "team_users",
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
@@ -5288,8 +5330,9 @@ describe("Entity builder", () => {
|
|||||||
reference: "m:n",
|
reference: "m:n",
|
||||||
name: "teams",
|
name: "teams",
|
||||||
entity: "Team",
|
entity: "Team",
|
||||||
|
owner: true,
|
||||||
pivotTable: "team_users",
|
pivotTable: "team_users",
|
||||||
mappedBy: "users",
|
inversedBy: "users",
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -5356,6 +5399,8 @@ describe("Entity builder", () => {
|
|||||||
reference: "m:n",
|
reference: "m:n",
|
||||||
name: "users",
|
name: "users",
|
||||||
entity: "User",
|
entity: "User",
|
||||||
|
owner: false,
|
||||||
|
mappedBy: "teams",
|
||||||
pivotTable: "team_users",
|
pivotTable: "team_users",
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
@@ -5500,6 +5545,188 @@ describe("Entity builder", () => {
|
|||||||
reference: "m:n",
|
reference: "m:n",
|
||||||
name: "teams",
|
name: "teams",
|
||||||
entity: "Team",
|
entity: "Team",
|
||||||
|
owner: true,
|
||||||
|
pivotTable: "team_users",
|
||||||
|
inversedBy: "users",
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "created_at",
|
||||||
|
fieldName: "created_at",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
onCreate: expect.any(Function),
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
updated_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "updated_at",
|
||||||
|
fieldName: "updated_at",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
onCreate: expect.any(Function),
|
||||||
|
onUpdate: expect.any(Function),
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
deleted_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "deleted_at",
|
||||||
|
fieldName: "deleted_at",
|
||||||
|
nullable: true,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const teamMetaData = MetadataStorage.getMetadataFromDecorator(Team)
|
||||||
|
expect(teamMetaData.className).toEqual("Team")
|
||||||
|
expect(teamMetaData.path).toEqual("Team")
|
||||||
|
expect(teamMetaData.properties).toEqual({
|
||||||
|
id: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "number",
|
||||||
|
columnType: "integer",
|
||||||
|
name: "id",
|
||||||
|
fieldName: "id",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "string",
|
||||||
|
columnType: "text",
|
||||||
|
name: "name",
|
||||||
|
fieldName: "name",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
reference: "m:n",
|
||||||
|
name: "users",
|
||||||
|
entity: "User",
|
||||||
|
owner: false,
|
||||||
|
pivotTable: "team_users",
|
||||||
|
mappedBy: "teams",
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "created_at",
|
||||||
|
fieldName: "created_at",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
onCreate: expect.any(Function),
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
updated_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "updated_at",
|
||||||
|
fieldName: "updated_at",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
onCreate: expect.any(Function),
|
||||||
|
onUpdate: expect.any(Function),
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
deleted_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "deleted_at",
|
||||||
|
fieldName: "deleted_at",
|
||||||
|
nullable: true,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("define mappedBy on both sides and reverse order of registering entities", () => {
|
||||||
|
const team = model.define("team", {
|
||||||
|
id: model.number(),
|
||||||
|
name: model.text(),
|
||||||
|
users: model.manyToMany(() => user, { mappedBy: "teams" }),
|
||||||
|
})
|
||||||
|
|
||||||
|
const user = model.define("user", {
|
||||||
|
id: model.number(),
|
||||||
|
username: model.text(),
|
||||||
|
teams: model.manyToMany(() => team, { mappedBy: "users" }),
|
||||||
|
})
|
||||||
|
|
||||||
|
const Team = toMikroORMEntity(team)
|
||||||
|
const User = toMikroORMEntity(user)
|
||||||
|
|
||||||
|
expectTypeOf(new User()).toMatchTypeOf<{
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
teams: {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
users: {
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
}[]
|
||||||
|
}[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
expectTypeOf(new Team()).toMatchTypeOf<{
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
users: {
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
teams: {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}[]
|
||||||
|
}[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||||
|
expect(metaData.className).toEqual("User")
|
||||||
|
expect(metaData.path).toEqual("User")
|
||||||
|
expect(metaData.properties).toEqual({
|
||||||
|
id: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "number",
|
||||||
|
columnType: "integer",
|
||||||
|
name: "id",
|
||||||
|
fieldName: "id",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "string",
|
||||||
|
columnType: "text",
|
||||||
|
name: "username",
|
||||||
|
fieldName: "username",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
teams: {
|
||||||
|
reference: "m:n",
|
||||||
|
name: "teams",
|
||||||
|
entity: "Team",
|
||||||
|
owner: false,
|
||||||
pivotTable: "team_users",
|
pivotTable: "team_users",
|
||||||
mappedBy: "users",
|
mappedBy: "users",
|
||||||
},
|
},
|
||||||
@@ -5568,11 +5795,8 @@ describe("Entity builder", () => {
|
|||||||
reference: "m:n",
|
reference: "m:n",
|
||||||
name: "users",
|
name: "users",
|
||||||
entity: "User",
|
entity: "User",
|
||||||
|
owner: true,
|
||||||
pivotTable: "team_users",
|
pivotTable: "team_users",
|
||||||
/**
|
|
||||||
* The other side should be inversed in order for Mikro ORM
|
|
||||||
* to work. Both sides cannot have mappedBy.
|
|
||||||
*/
|
|
||||||
inversedBy: "teams",
|
inversedBy: "teams",
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
@@ -5613,190 +5837,6 @@ describe("Entity builder", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test("define mappedBy on both sides and reverse order of registering entities", () => {
|
|
||||||
const team = model.define("team", {
|
|
||||||
id: model.number(),
|
|
||||||
name: model.text(),
|
|
||||||
users: model.manyToMany(() => user, { mappedBy: "teams" }),
|
|
||||||
})
|
|
||||||
|
|
||||||
const user = model.define("user", {
|
|
||||||
id: model.number(),
|
|
||||||
username: model.text(),
|
|
||||||
teams: model.manyToMany(() => team, { mappedBy: "users" }),
|
|
||||||
})
|
|
||||||
|
|
||||||
const entityBuilder = createMikrORMEntity()
|
|
||||||
const Team = entityBuilder(team)
|
|
||||||
const User = entityBuilder(user)
|
|
||||||
|
|
||||||
expectTypeOf(new User()).toMatchTypeOf<{
|
|
||||||
id: number
|
|
||||||
username: string
|
|
||||||
teams: {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
users: {
|
|
||||||
id: number
|
|
||||||
username: string
|
|
||||||
}[]
|
|
||||||
}[]
|
|
||||||
}>()
|
|
||||||
|
|
||||||
expectTypeOf(new Team()).toMatchTypeOf<{
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
users: {
|
|
||||||
id: number
|
|
||||||
username: string
|
|
||||||
teams: {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
}[]
|
|
||||||
}[]
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
|
||||||
expect(metaData.className).toEqual("User")
|
|
||||||
expect(metaData.path).toEqual("User")
|
|
||||||
expect(metaData.properties).toEqual({
|
|
||||||
id: {
|
|
||||||
reference: "scalar",
|
|
||||||
type: "number",
|
|
||||||
columnType: "integer",
|
|
||||||
name: "id",
|
|
||||||
fieldName: "id",
|
|
||||||
nullable: false,
|
|
||||||
getter: false,
|
|
||||||
setter: false,
|
|
||||||
},
|
|
||||||
username: {
|
|
||||||
reference: "scalar",
|
|
||||||
type: "string",
|
|
||||||
columnType: "text",
|
|
||||||
name: "username",
|
|
||||||
fieldName: "username",
|
|
||||||
nullable: false,
|
|
||||||
getter: false,
|
|
||||||
setter: false,
|
|
||||||
},
|
|
||||||
teams: {
|
|
||||||
reference: "m:n",
|
|
||||||
name: "teams",
|
|
||||||
entity: "Team",
|
|
||||||
pivotTable: "team_users",
|
|
||||||
/**
|
|
||||||
* The other side should be inversed in order for Mikro ORM
|
|
||||||
* to work. Both sides cannot have mappedBy.
|
|
||||||
*/
|
|
||||||
inversedBy: "users",
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
reference: "scalar",
|
|
||||||
type: "date",
|
|
||||||
columnType: "timestamptz",
|
|
||||||
name: "created_at",
|
|
||||||
fieldName: "created_at",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
onCreate: expect.any(Function),
|
|
||||||
nullable: false,
|
|
||||||
getter: false,
|
|
||||||
setter: false,
|
|
||||||
},
|
|
||||||
updated_at: {
|
|
||||||
reference: "scalar",
|
|
||||||
type: "date",
|
|
||||||
columnType: "timestamptz",
|
|
||||||
name: "updated_at",
|
|
||||||
fieldName: "updated_at",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
onCreate: expect.any(Function),
|
|
||||||
onUpdate: expect.any(Function),
|
|
||||||
nullable: false,
|
|
||||||
getter: false,
|
|
||||||
setter: false,
|
|
||||||
},
|
|
||||||
deleted_at: {
|
|
||||||
reference: "scalar",
|
|
||||||
type: "date",
|
|
||||||
columnType: "timestamptz",
|
|
||||||
name: "deleted_at",
|
|
||||||
fieldName: "deleted_at",
|
|
||||||
nullable: true,
|
|
||||||
getter: false,
|
|
||||||
setter: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const teamMetaData = MetadataStorage.getMetadataFromDecorator(Team)
|
|
||||||
expect(teamMetaData.className).toEqual("Team")
|
|
||||||
expect(teamMetaData.path).toEqual("Team")
|
|
||||||
expect(teamMetaData.properties).toEqual({
|
|
||||||
id: {
|
|
||||||
reference: "scalar",
|
|
||||||
type: "number",
|
|
||||||
columnType: "integer",
|
|
||||||
name: "id",
|
|
||||||
fieldName: "id",
|
|
||||||
nullable: false,
|
|
||||||
getter: false,
|
|
||||||
setter: false,
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
reference: "scalar",
|
|
||||||
type: "string",
|
|
||||||
columnType: "text",
|
|
||||||
name: "name",
|
|
||||||
fieldName: "name",
|
|
||||||
nullable: false,
|
|
||||||
getter: false,
|
|
||||||
setter: false,
|
|
||||||
},
|
|
||||||
users: {
|
|
||||||
reference: "m:n",
|
|
||||||
name: "users",
|
|
||||||
entity: "User",
|
|
||||||
pivotTable: "team_users",
|
|
||||||
mappedBy: "teams",
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
reference: "scalar",
|
|
||||||
type: "date",
|
|
||||||
columnType: "timestamptz",
|
|
||||||
name: "created_at",
|
|
||||||
fieldName: "created_at",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
onCreate: expect.any(Function),
|
|
||||||
nullable: false,
|
|
||||||
getter: false,
|
|
||||||
setter: false,
|
|
||||||
},
|
|
||||||
updated_at: {
|
|
||||||
reference: "scalar",
|
|
||||||
type: "date",
|
|
||||||
columnType: "timestamptz",
|
|
||||||
name: "updated_at",
|
|
||||||
fieldName: "updated_at",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
onCreate: expect.any(Function),
|
|
||||||
onUpdate: expect.any(Function),
|
|
||||||
nullable: false,
|
|
||||||
getter: false,
|
|
||||||
setter: false,
|
|
||||||
},
|
|
||||||
deleted_at: {
|
|
||||||
reference: "scalar",
|
|
||||||
type: "date",
|
|
||||||
columnType: "timestamptz",
|
|
||||||
name: "deleted_at",
|
|
||||||
fieldName: "deleted_at",
|
|
||||||
nullable: true,
|
|
||||||
getter: false,
|
|
||||||
setter: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test("define multiple many to many relationships to the same entity", () => {
|
test("define multiple many to many relationships to the same entity", () => {
|
||||||
const team = model.define("team", {
|
const team = model.define("team", {
|
||||||
id: model.number(),
|
id: model.number(),
|
||||||
@@ -5816,9 +5856,8 @@ describe("Entity builder", () => {
|
|||||||
teams: model.manyToMany(() => team, { mappedBy: "users" }),
|
teams: model.manyToMany(() => team, { mappedBy: "users" }),
|
||||||
})
|
})
|
||||||
|
|
||||||
const entityBuilder = createMikrORMEntity()
|
const Team = toMikroORMEntity(team)
|
||||||
const Team = entityBuilder(team)
|
const User = toMikroORMEntity(user)
|
||||||
const User = entityBuilder(user)
|
|
||||||
|
|
||||||
expectTypeOf(new User()).toMatchTypeOf<{
|
expectTypeOf(new User()).toMatchTypeOf<{
|
||||||
id: number
|
id: number
|
||||||
@@ -5886,19 +5925,17 @@ describe("Entity builder", () => {
|
|||||||
reference: "m:n",
|
reference: "m:n",
|
||||||
name: "teams",
|
name: "teams",
|
||||||
entity: "Team",
|
entity: "Team",
|
||||||
|
owner: false,
|
||||||
pivotTable: "team_users",
|
pivotTable: "team_users",
|
||||||
/**
|
mappedBy: "users",
|
||||||
* The other side should be inversed in order for Mikro ORM
|
|
||||||
* to work. Both sides cannot have mappedBy.
|
|
||||||
*/
|
|
||||||
inversedBy: "users",
|
|
||||||
},
|
},
|
||||||
activeTeams: {
|
activeTeams: {
|
||||||
reference: "m:n",
|
reference: "m:n",
|
||||||
name: "activeTeams",
|
name: "activeTeams",
|
||||||
entity: "Team",
|
entity: "Team",
|
||||||
|
owner: false,
|
||||||
pivotTable: "team_users",
|
pivotTable: "team_users",
|
||||||
inversedBy: "activeTeamsUsers",
|
mappedBy: "activeTeamsUsers",
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -5965,15 +6002,17 @@ describe("Entity builder", () => {
|
|||||||
reference: "m:n",
|
reference: "m:n",
|
||||||
name: "users",
|
name: "users",
|
||||||
entity: "User",
|
entity: "User",
|
||||||
|
owner: true,
|
||||||
pivotTable: "team_users",
|
pivotTable: "team_users",
|
||||||
mappedBy: "teams",
|
inversedBy: "teams",
|
||||||
},
|
},
|
||||||
activeTeamsUsers: {
|
activeTeamsUsers: {
|
||||||
reference: "m:n",
|
reference: "m:n",
|
||||||
name: "activeTeamsUsers",
|
name: "activeTeamsUsers",
|
||||||
entity: "User",
|
entity: "User",
|
||||||
|
owner: true,
|
||||||
pivotTable: "team_users",
|
pivotTable: "team_users",
|
||||||
mappedBy: "activeTeams",
|
inversedBy: "activeTeams",
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -6086,8 +6125,9 @@ describe("Entity builder", () => {
|
|||||||
reference: "m:n",
|
reference: "m:n",
|
||||||
name: "teams",
|
name: "teams",
|
||||||
entity: "Team",
|
entity: "Team",
|
||||||
|
owner: true,
|
||||||
pivotTable: "platform.team_users",
|
pivotTable: "platform.team_users",
|
||||||
mappedBy: "users",
|
inversedBy: "users",
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -6155,6 +6195,8 @@ describe("Entity builder", () => {
|
|||||||
reference: "m:n",
|
reference: "m:n",
|
||||||
name: "users",
|
name: "users",
|
||||||
entity: "User",
|
entity: "User",
|
||||||
|
owner: false,
|
||||||
|
mappedBy: "teams",
|
||||||
pivotTable: "platform.team_users",
|
pivotTable: "platform.team_users",
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
@@ -6195,13 +6237,168 @@ describe("Entity builder", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("should compute the pivot table name correctly", () => {
|
||||||
|
const team = model.define("teamSquad", {
|
||||||
|
id: model.number(),
|
||||||
|
name: model.text(),
|
||||||
|
users: model.manyToMany(() => user),
|
||||||
|
})
|
||||||
|
|
||||||
|
const user = model.define("RandomUser", {
|
||||||
|
id: model.number(),
|
||||||
|
username: model.text(),
|
||||||
|
teams: model.manyToMany(() => team, {
|
||||||
|
mappedBy: "users",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const User = toMikroORMEntity(user)
|
||||||
|
const Team = toMikroORMEntity(team)
|
||||||
|
|
||||||
|
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||||
|
expect(metaData.className).toEqual("RandomUser")
|
||||||
|
expect(metaData.path).toEqual("RandomUser")
|
||||||
|
expect(metaData.properties).toEqual({
|
||||||
|
id: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "number",
|
||||||
|
columnType: "integer",
|
||||||
|
name: "id",
|
||||||
|
fieldName: "id",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "string",
|
||||||
|
columnType: "text",
|
||||||
|
name: "username",
|
||||||
|
fieldName: "username",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
teams: {
|
||||||
|
reference: "m:n",
|
||||||
|
name: "teams",
|
||||||
|
entity: "TeamSquad",
|
||||||
|
owner: true,
|
||||||
|
pivotTable: "random_user_team_squads",
|
||||||
|
inversedBy: "users",
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "created_at",
|
||||||
|
fieldName: "created_at",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
onCreate: expect.any(Function),
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
updated_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "updated_at",
|
||||||
|
fieldName: "updated_at",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
onCreate: expect.any(Function),
|
||||||
|
onUpdate: expect.any(Function),
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
deleted_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "deleted_at",
|
||||||
|
fieldName: "deleted_at",
|
||||||
|
nullable: true,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const teamMetaData = MetadataStorage.getMetadataFromDecorator(Team)
|
||||||
|
expect(teamMetaData.className).toEqual("TeamSquad")
|
||||||
|
expect(teamMetaData.path).toEqual("TeamSquad")
|
||||||
|
expect(teamMetaData.properties).toEqual({
|
||||||
|
id: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "number",
|
||||||
|
columnType: "integer",
|
||||||
|
name: "id",
|
||||||
|
fieldName: "id",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "string",
|
||||||
|
columnType: "text",
|
||||||
|
name: "name",
|
||||||
|
fieldName: "name",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
reference: "m:n",
|
||||||
|
name: "users",
|
||||||
|
entity: "RandomUser",
|
||||||
|
owner: false,
|
||||||
|
mappedBy: "teams",
|
||||||
|
pivotTable: "random_user_team_squads",
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "created_at",
|
||||||
|
fieldName: "created_at",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
onCreate: expect.any(Function),
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
updated_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "updated_at",
|
||||||
|
fieldName: "updated_at",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
onCreate: expect.any(Function),
|
||||||
|
onUpdate: expect.any(Function),
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
deleted_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "deleted_at",
|
||||||
|
fieldName: "deleted_at",
|
||||||
|
nullable: true,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test("define custom pivot table name", () => {
|
test("define custom pivot table name", () => {
|
||||||
const team = model.define("team", {
|
const team = model.define("team", {
|
||||||
id: model.number(),
|
id: model.number(),
|
||||||
name: model.text(),
|
name: model.text(),
|
||||||
users: model.manyToMany(() => user, {
|
users: model.manyToMany(() => user),
|
||||||
pivotTable: "users_teams",
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const user = model.define("user", {
|
const user = model.define("user", {
|
||||||
@@ -6270,8 +6467,9 @@ describe("Entity builder", () => {
|
|||||||
reference: "m:n",
|
reference: "m:n",
|
||||||
name: "teams",
|
name: "teams",
|
||||||
entity: "Team",
|
entity: "Team",
|
||||||
|
owner: true,
|
||||||
pivotTable: "users_teams",
|
pivotTable: "users_teams",
|
||||||
mappedBy: "users",
|
inversedBy: "users",
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -6337,7 +6535,9 @@ describe("Entity builder", () => {
|
|||||||
users: {
|
users: {
|
||||||
reference: "m:n",
|
reference: "m:n",
|
||||||
name: "users",
|
name: "users",
|
||||||
|
owner: false,
|
||||||
entity: "User",
|
entity: "User",
|
||||||
|
mappedBy: "teams",
|
||||||
pivotTable: "users_teams",
|
pivotTable: "users_teams",
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
@@ -6454,7 +6654,6 @@ describe("Entity builder", () => {
|
|||||||
mapToPk: true,
|
mapToPk: true,
|
||||||
fieldName: "user_id",
|
fieldName: "user_id",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
isForeignKey: true,
|
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -6473,7 +6672,6 @@ describe("Entity builder", () => {
|
|||||||
mapToPk: true,
|
mapToPk: true,
|
||||||
fieldName: "team_id",
|
fieldName: "team_id",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
isForeignKey: true,
|
|
||||||
},
|
},
|
||||||
team: {
|
team: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -6549,8 +6747,9 @@ describe("Entity builder", () => {
|
|||||||
reference: "m:n",
|
reference: "m:n",
|
||||||
name: "teams",
|
name: "teams",
|
||||||
entity: "Team",
|
entity: "Team",
|
||||||
|
owner: true,
|
||||||
pivotEntity: "TeamUsers",
|
pivotEntity: "TeamUsers",
|
||||||
mappedBy: "users",
|
inversedBy: "users",
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -6617,6 +6816,8 @@ describe("Entity builder", () => {
|
|||||||
reference: "m:n",
|
reference: "m:n",
|
||||||
name: "users",
|
name: "users",
|
||||||
entity: "User",
|
entity: "User",
|
||||||
|
owner: false,
|
||||||
|
mappedBy: "teams",
|
||||||
pivotEntity: "TeamUsers",
|
pivotEntity: "TeamUsers",
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
|
|||||||
@@ -56,6 +56,14 @@ export type ManyToManyOptions = RelationshipOptions &
|
|||||||
* @ignore
|
* @ignore
|
||||||
*/
|
*/
|
||||||
pivotEntity?: never
|
pivotEntity?: never
|
||||||
|
/**
|
||||||
|
* The column name in the pivot table that for the current entity
|
||||||
|
*/
|
||||||
|
joinColumn?: string
|
||||||
|
/**
|
||||||
|
* The column name in the pivot table for the opposite entity
|
||||||
|
*/
|
||||||
|
inverseJoinColumn?: string
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { PropertyType } from "@medusajs/types"
|
import type { PropertyType } from "@medusajs/types"
|
||||||
import { DmlEntity } from "../entity"
|
import { DmlEntity } from "../entity"
|
||||||
import { parseEntityName } from "./entity-builder/parse-entity-name"
|
import { parseEntityName } from "./entity-builder/parse-entity-name"
|
||||||
import { getGraphQLAttributeFromDMLPropety } from "./graphql-builder/get-attribute"
|
|
||||||
import { setGraphQLRelationship } from "./graphql-builder/set-relationship"
|
import { setGraphQLRelationship } from "./graphql-builder/set-relationship"
|
||||||
|
import { getGraphQLAttributeFromDMLPropety } from "./graphql-builder/get-attribute"
|
||||||
|
|
||||||
export function generateGraphQLFromEntity<T extends DmlEntity<any, any>>(
|
export function generateGraphQLFromEntity<T extends DmlEntity<any, any>>(
|
||||||
entity: T
|
entity: T
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type {
|
import type {
|
||||||
|
Constructor,
|
||||||
DMLSchema,
|
DMLSchema,
|
||||||
EntityConstructor,
|
EntityConstructor,
|
||||||
IDmlEntity,
|
IDmlEntity,
|
||||||
@@ -6,10 +7,11 @@ import type {
|
|||||||
PropertyType,
|
PropertyType,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { Entity, Filter } from "@mikro-orm/core"
|
import { Entity, Filter } from "@mikro-orm/core"
|
||||||
import { mikroOrmSoftDeletableFilterOptions } from "../../dal"
|
|
||||||
import { DmlEntity } from "../entity"
|
import { DmlEntity } from "../entity"
|
||||||
import { DuplicateIdPropertyError } from "../errors"
|
|
||||||
import { IdProperty } from "../properties/id"
|
import { IdProperty } from "../properties/id"
|
||||||
|
import { DuplicateIdPropertyError } from "../errors"
|
||||||
|
import { mikroOrmSoftDeletableFilterOptions } from "../../dal"
|
||||||
import { applySearchable } from "./entity-builder/apply-searchable"
|
import { applySearchable } from "./entity-builder/apply-searchable"
|
||||||
import { defineProperty } from "./entity-builder/define-property"
|
import { defineProperty } from "./entity-builder/define-property"
|
||||||
import { defineRelationship } from "./entity-builder/define-relationship"
|
import { defineRelationship } from "./entity-builder/define-relationship"
|
||||||
@@ -21,7 +23,7 @@ import { applyEntityIndexes, applyIndexes } from "./mikro-orm/apply-indexes"
|
|||||||
* value is a function that can be used to convert DML entities
|
* value is a function that can be used to convert DML entities
|
||||||
* to Mikro ORM entities.
|
* to Mikro ORM entities.
|
||||||
*/
|
*/
|
||||||
export function createMikrORMEntity() {
|
function createMikrORMEntity() {
|
||||||
/**
|
/**
|
||||||
* The following property is used to track many to many relationship
|
* The following property is used to track many to many relationship
|
||||||
* between two entities. It is needed because we have to mark one
|
* between two entities. It is needed because we have to mark one
|
||||||
@@ -35,20 +37,21 @@ export function createMikrORMEntity() {
|
|||||||
* - [user.teams]: true // the teams relationship on user is an owner
|
* - [user.teams]: true // the teams relationship on user is an owner
|
||||||
* - [team.users] // cannot be an owner
|
* - [team.users] // cannot be an owner
|
||||||
*/
|
*/
|
||||||
// TODO: if we use the util toMikroOrmEntities then a new builder will be used each time, lets think about this. Currently if means that with many to many we need to use the same builder
|
let MANY_TO_MANY_TRACKED_RELATIONS: Record<string, boolean> = {}
|
||||||
const MANY_TO_MANY_TRACKED_RELATIONS: Record<string, boolean> = {}
|
let ENTITIES: Record<string, Constructor<any>> = {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper function to define a Mikro ORM entity from a
|
* A helper function to define a Mikro ORM entity from a
|
||||||
* DML entity.
|
* DML entity.
|
||||||
*/
|
*/
|
||||||
return function createEntity<T extends DmlEntity<any, any>>(
|
function createEntity<T extends DmlEntity<any, any>>(entity: T): Infer<T> {
|
||||||
entity: T
|
|
||||||
): Infer<T> {
|
|
||||||
class MikroORMEntity {}
|
class MikroORMEntity {}
|
||||||
|
|
||||||
const { schema, cascades, indexes: entityIndexes = [] } = entity.parse()
|
const { schema, cascades, indexes: entityIndexes = [] } = entity.parse()
|
||||||
const { modelName, tableName } = parseEntityName(entity)
|
const { modelName, tableName } = parseEntityName(entity)
|
||||||
|
if (ENTITIES[modelName]) {
|
||||||
|
return ENTITIES[modelName] as Infer<T>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assigning name to the class constructor, so that it matches
|
* Assigning name to the class constructor, so that it matches
|
||||||
@@ -80,11 +83,14 @@ export function createMikrORMEntity() {
|
|||||||
hasIdAlreadyDefined = true
|
hasIdAlreadyDefined = true
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProperty(MikroORMEntity, name, property as PropertyType<any>)
|
defineProperty(MikroORMEntity, property as PropertyType<any>, {
|
||||||
|
propertyName: name,
|
||||||
|
tableName,
|
||||||
|
})
|
||||||
applyIndexes(MikroORMEntity, tableName, field)
|
applyIndexes(MikroORMEntity, tableName, field)
|
||||||
applySearchable(MikroORMEntity, field)
|
applySearchable(MikroORMEntity, field)
|
||||||
} else {
|
} else {
|
||||||
defineRelationship(MikroORMEntity, field, cascades, context)
|
defineRelationship(MikroORMEntity, entity, field, cascades, context)
|
||||||
applySearchable(MikroORMEntity, field)
|
applySearchable(MikroORMEntity, field)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -94,12 +100,31 @@ export function createMikrORMEntity() {
|
|||||||
/**
|
/**
|
||||||
* Converting class to a MikroORM entity
|
* Converting class to a MikroORM entity
|
||||||
*/
|
*/
|
||||||
return Entity({ tableName })(
|
const RegisteredEntity = Entity({ tableName })(
|
||||||
Filter(mikroOrmSoftDeletableFilterOptions)(MikroORMEntity)
|
Filter(mikroOrmSoftDeletableFilterOptions)(MikroORMEntity)
|
||||||
) as Infer<T>
|
) as Infer<T>
|
||||||
|
|
||||||
|
ENTITIES[modelName] = RegisteredEntity
|
||||||
|
return RegisteredEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the internally tracked entities and relationships
|
||||||
|
*/
|
||||||
|
createEntity.clear = function () {
|
||||||
|
MANY_TO_MANY_TRACKED_RELATIONS = {}
|
||||||
|
ENTITIES = {}
|
||||||
|
}
|
||||||
|
return createEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to convert DML entities to MikroORM entity. Use
|
||||||
|
* "toMikroORMEntity" if you are ensure the input is a DML entity
|
||||||
|
* or not.
|
||||||
|
*/
|
||||||
|
export const mikroORMEntityBuilder = createMikrORMEntity()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a DML entity and returns a Mikro ORM entity otherwise
|
* Takes a DML entity and returns a Mikro ORM entity otherwise
|
||||||
* return the input idempotently
|
* return the input idempotently
|
||||||
@@ -111,7 +136,7 @@ export const toMikroORMEntity = <T>(
|
|||||||
let mikroOrmEntity: T | EntityConstructor<any> = entity
|
let mikroOrmEntity: T | EntityConstructor<any> = entity
|
||||||
|
|
||||||
if (DmlEntity.isDmlEntity(entity)) {
|
if (DmlEntity.isDmlEntity(entity)) {
|
||||||
mikroOrmEntity = createMikrORMEntity()(entity)
|
mikroOrmEntity = mikroORMEntityBuilder(entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
return mikroOrmEntity as T extends IDmlEntity<any, any> ? Infer<T> : T
|
return mikroOrmEntity as T extends IDmlEntity<any, any> ? Infer<T> : T
|
||||||
@@ -123,11 +148,9 @@ export const toMikroORMEntity = <T>(
|
|||||||
* @param entities
|
* @param entities
|
||||||
*/
|
*/
|
||||||
export const toMikroOrmEntities = function <T extends any[]>(entities: T) {
|
export const toMikroOrmEntities = function <T extends any[]>(entities: T) {
|
||||||
const entityBuilder = createMikrORMEntity()
|
|
||||||
|
|
||||||
return entities.map((entity) => {
|
return entities.map((entity) => {
|
||||||
if (DmlEntity.isDmlEntity(entity)) {
|
if (DmlEntity.isDmlEntity(entity)) {
|
||||||
return entityBuilder(entity)
|
return mikroORMEntityBuilder(entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
return entity
|
return entity
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
Utils,
|
Utils,
|
||||||
} from "@mikro-orm/core"
|
} from "@mikro-orm/core"
|
||||||
import { PrimaryKeyModifier } from "../../properties/primary-key"
|
import { PrimaryKeyModifier } from "../../properties/primary-key"
|
||||||
|
import { applyEntityIndexes } from "../mikro-orm/apply-indexes"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DML entity data types to PostgreSQL data types via
|
* DML entity data types to PostgreSQL data types via
|
||||||
@@ -64,7 +65,8 @@ const PROPERTY_TYPES: {
|
|||||||
const SPECIAL_PROPERTIES: {
|
const SPECIAL_PROPERTIES: {
|
||||||
[propertyName: string]: (
|
[propertyName: string]: (
|
||||||
MikroORMEntity: EntityConstructor<any>,
|
MikroORMEntity: EntityConstructor<any>,
|
||||||
field: PropertyMetadata
|
field: PropertyMetadata,
|
||||||
|
tableName: string
|
||||||
) => void
|
) => void
|
||||||
} = {
|
} = {
|
||||||
created_at: (MikroORMEntity, field) => {
|
created_at: (MikroORMEntity, field) => {
|
||||||
@@ -88,6 +90,21 @@ const SPECIAL_PROPERTIES: {
|
|||||||
onUpdate: () => new Date(),
|
onUpdate: () => new Date(),
|
||||||
})(MikroORMEntity.prototype, field.fieldName)
|
})(MikroORMEntity.prototype, field.fieldName)
|
||||||
},
|
},
|
||||||
|
deleted_at: (MikroORMEntity, field, tableName) => {
|
||||||
|
Property({
|
||||||
|
columnType: "timestamptz",
|
||||||
|
type: "date",
|
||||||
|
nullable: true,
|
||||||
|
fieldName: field.fieldName,
|
||||||
|
})(MikroORMEntity.prototype, field.fieldName)
|
||||||
|
|
||||||
|
applyEntityIndexes(MikroORMEntity, tableName, [
|
||||||
|
{
|
||||||
|
on: ["deleted_at"],
|
||||||
|
where: "deleted_at IS NULL",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -95,8 +112,8 @@ const SPECIAL_PROPERTIES: {
|
|||||||
*/
|
*/
|
||||||
export function defineProperty(
|
export function defineProperty(
|
||||||
MikroORMEntity: EntityConstructor<any>,
|
MikroORMEntity: EntityConstructor<any>,
|
||||||
propertyName: string,
|
property: PropertyType<any>,
|
||||||
property: PropertyType<any>
|
{ tableName, propertyName }: { tableName: string; propertyName: string }
|
||||||
) {
|
) {
|
||||||
const field = property.parse(propertyName)
|
const field = property.parse(propertyName)
|
||||||
/**
|
/**
|
||||||
@@ -112,18 +129,18 @@ export function defineProperty(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (SPECIAL_PROPERTIES[field.fieldName]) {
|
if (SPECIAL_PROPERTIES[field.fieldName]) {
|
||||||
SPECIAL_PROPERTIES[field.fieldName](MikroORMEntity, field)
|
SPECIAL_PROPERTIES[field.fieldName](MikroORMEntity, field, tableName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Defining an big number property
|
|
||||||
* A big number property always comes with a raw_{{ fieldName }} column
|
|
||||||
* where the config of the bigNumber is set.
|
|
||||||
* The `raw_` field is generated during DML schema generation as a json
|
|
||||||
* dataType.
|
|
||||||
*/
|
|
||||||
if (field.dataType.name === "bigNumber") {
|
if (field.dataType.name === "bigNumber") {
|
||||||
|
/**
|
||||||
|
* Defining an big number property
|
||||||
|
* A big number property always comes with a raw_{{ fieldName }} column
|
||||||
|
* where the config of the bigNumber is set.
|
||||||
|
* The `raw_` field is generated during DML schema generation as a json
|
||||||
|
* dataType.
|
||||||
|
*/
|
||||||
MikroOrmBigNumberProperty({
|
MikroOrmBigNumberProperty({
|
||||||
nullable: field.nullable,
|
nullable: field.nullable,
|
||||||
fieldName: field.fieldName,
|
fieldName: field.fieldName,
|
||||||
|
|||||||
@@ -16,17 +16,77 @@ import {
|
|||||||
rel,
|
rel,
|
||||||
} from "@mikro-orm/core"
|
} from "@mikro-orm/core"
|
||||||
import { camelToSnakeCase, pluralize } from "../../../common"
|
import { camelToSnakeCase, pluralize } from "../../../common"
|
||||||
import { ForeignKey } from "../../../dal/mikro-orm/decorators/foreign-key"
|
|
||||||
import { DmlEntity } from "../../entity"
|
import { DmlEntity } from "../../entity"
|
||||||
import { HasMany } from "../../relations/has-many"
|
import { HasMany } from "../../relations/has-many"
|
||||||
import { HasOne } from "../../relations/has-one"
|
import { HasOne } from "../../relations/has-one"
|
||||||
import { ManyToMany as DmlManyToMany } from "../../relations/many-to-many"
|
import { ManyToMany as DmlManyToMany } from "../../relations/many-to-many"
|
||||||
|
import { applyEntityIndexes } from "../mikro-orm/apply-indexes"
|
||||||
import { parseEntityName } from "./parse-entity-name"
|
import { parseEntityName } from "./parse-entity-name"
|
||||||
|
|
||||||
type Context = {
|
type Context = {
|
||||||
MANY_TO_MANY_TRACKED_RELATIONS: Record<string, boolean>
|
MANY_TO_MANY_TRACKED_RELATIONS: Record<string, boolean>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function retrieveOtherSideRelationshipManyToMany({
|
||||||
|
relationship,
|
||||||
|
relatedEntity,
|
||||||
|
relatedModelName,
|
||||||
|
entity,
|
||||||
|
}: {
|
||||||
|
relationship: RelationshipMetadata
|
||||||
|
relatedEntity: DmlEntity<
|
||||||
|
Record<string, PropertyType<any> | RelationshipType<any>>,
|
||||||
|
any
|
||||||
|
>
|
||||||
|
relatedModelName: string
|
||||||
|
entity: DmlEntity<any, any>
|
||||||
|
}): [string, RelationshipType<any>] {
|
||||||
|
if (relationship.mappedBy) {
|
||||||
|
return [
|
||||||
|
relationship.mappedBy,
|
||||||
|
relatedEntity.parse().schema[relationship.mappedBy],
|
||||||
|
] as [string, RelationshipType<any>]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since we don't have the information about the other side of the
|
||||||
|
* relationship, we will try to find all the other side many to many that refers to the current entity.
|
||||||
|
* If there is any, we will try to find if at least one of them has a mappedBy.
|
||||||
|
*/
|
||||||
|
const potentialOtherSide = Object.entries(relatedEntity.schema)
|
||||||
|
.filter(([, propConfig]) => DmlManyToMany.isManyToMany(propConfig))
|
||||||
|
.filter(([prop, propConfig]) => {
|
||||||
|
const parsedProp = propConfig.parse(prop) as RelationshipMetadata
|
||||||
|
|
||||||
|
const relatedEntity =
|
||||||
|
typeof parsedProp.entity === "function"
|
||||||
|
? parsedProp.entity()
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
if (!relatedEntity) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid relationship reference for "${relatedModelName}.${prop}". Make sure to define the relationship using a factory function`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
(parsedProp.mappedBy === relationship.name &&
|
||||||
|
parseEntityName(relatedEntity).modelName ===
|
||||||
|
parseEntityName(entity).modelName) ||
|
||||||
|
parseEntityName(relatedEntity).modelName ===
|
||||||
|
parseEntityName(entity).modelName
|
||||||
|
)
|
||||||
|
}) as unknown as [string, RelationshipType<any>][]
|
||||||
|
|
||||||
|
if (potentialOtherSide.length > 1) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid relationship reference for "${entity.name}.${relationship.name}". Make sure to set the mappedBy property on one side or the other or both.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return potentialOtherSide[0] ?? []
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates a many to many relationship without mappedBy and checks if the other side of the relationship is defined and possesses mappedBy.
|
* Validates a many to many relationship without mappedBy and checks if the other side of the relationship is defined and possesses mappedBy.
|
||||||
* @param MikroORMEntity
|
* @param MikroORMEntity
|
||||||
@@ -39,6 +99,7 @@ function validateManyToManyRelationshipWithoutMappedBy({
|
|||||||
relationship,
|
relationship,
|
||||||
relatedEntity,
|
relatedEntity,
|
||||||
relatedModelName,
|
relatedModelName,
|
||||||
|
entity,
|
||||||
}: {
|
}: {
|
||||||
MikroORMEntity: EntityConstructor<any>
|
MikroORMEntity: EntityConstructor<any>
|
||||||
relationship: RelationshipMetadata
|
relationship: RelationshipMetadata
|
||||||
@@ -47,42 +108,23 @@ function validateManyToManyRelationshipWithoutMappedBy({
|
|||||||
any
|
any
|
||||||
>
|
>
|
||||||
relatedModelName: string
|
relatedModelName: string
|
||||||
|
entity: DmlEntity<any, any>
|
||||||
}) {
|
}) {
|
||||||
/**
|
/**
|
||||||
* Since we don't have the information about the other side of the
|
* Since we don't have the information about the other side of the
|
||||||
* relationship, we will try to find all the other side many to many that refers to the current entity.
|
* relationship, we will try to find all the other side many to many that refers to the current entity.
|
||||||
* If there is any, we will try to find if at least one of them has a mappedBy.
|
* If there is any, we will try to find if at least one of them has a mappedBy.
|
||||||
*/
|
*/
|
||||||
const potentialOtherSides = Object.entries(relatedEntity.schema)
|
const [, potentialOtherSide] = retrieveOtherSideRelationshipManyToMany({
|
||||||
.filter(([, propConfig]) => DmlManyToMany.isManyToMany(propConfig))
|
relationship,
|
||||||
.filter(([prop, propConfig]) => {
|
relatedEntity,
|
||||||
const parsedProp = propConfig.parse(prop) as RelationshipMetadata
|
relatedModelName,
|
||||||
const relatedEntity =
|
entity,
|
||||||
typeof parsedProp.entity === "function"
|
})
|
||||||
? parsedProp.entity()
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
if (!relatedEntity) {
|
if (!potentialOtherSide) {
|
||||||
throw new Error(
|
|
||||||
`Invalid relationship reference for "${relatedModelName}.${prop}". Make sure to define the relationship using a factory function`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseEntityName(relatedEntity).modelName === MikroORMEntity.name
|
|
||||||
}) as unknown as [string, RelationshipType<any>][]
|
|
||||||
|
|
||||||
if (potentialOtherSides.length) {
|
|
||||||
const hasMappedBy = potentialOtherSides.some(
|
|
||||||
([, propConfig]) => !!propConfig.parse("").mappedBy
|
|
||||||
)
|
|
||||||
if (!hasMappedBy) {
|
|
||||||
throw new Error(
|
|
||||||
`Invalid relationship reference for "${MikroORMEntity.name}.${relationship.name}". "mappedBy" should be defined on one side or the other.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid relationship reference for "${MikroORMEntity.name}.${relationship.name}". The other side of the relationship is missing.`
|
`Invalid relationship reference for "${MikroORMEntity.name}.${relationship.name}". "mappedBy" should be defined on one side or the other.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,6 +182,7 @@ export function defineHasManyRelationship(
|
|||||||
*/
|
*/
|
||||||
export function defineBelongsToRelationship(
|
export function defineBelongsToRelationship(
|
||||||
MikroORMEntity: EntityConstructor<any>,
|
MikroORMEntity: EntityConstructor<any>,
|
||||||
|
entity: DmlEntity<any, any>,
|
||||||
relationship: RelationshipMetadata,
|
relationship: RelationshipMetadata,
|
||||||
relatedEntity: DmlEntity<
|
relatedEntity: DmlEntity<
|
||||||
Record<string, PropertyType<any> | RelationshipType<any>>,
|
Record<string, PropertyType<any> | RelationshipType<any>>,
|
||||||
@@ -194,8 +237,7 @@ export function defineBelongsToRelationship(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this[relationship.name] ??= this[foreignKeyName]
|
this[foreignKeyName] ??= this[relationship.name]?.id ?? null
|
||||||
this[foreignKeyName] ??= this[relationship.name]?.id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -222,7 +264,6 @@ export function defineBelongsToRelationship(
|
|||||||
nullable: relationship.nullable,
|
nullable: relationship.nullable,
|
||||||
onDelete: shouldCascade ? "cascade" : undefined,
|
onDelete: shouldCascade ? "cascade" : undefined,
|
||||||
})(MikroORMEntity.prototype, foreignKeyName)
|
})(MikroORMEntity.prototype, foreignKeyName)
|
||||||
ForeignKey()(MikroORMEntity.prototype, foreignKeyName)
|
|
||||||
|
|
||||||
if (DmlManyToMany.isManyToMany(otherSideRelation)) {
|
if (DmlManyToMany.isManyToMany(otherSideRelation)) {
|
||||||
Property({
|
Property({
|
||||||
@@ -239,6 +280,13 @@ export function defineBelongsToRelationship(
|
|||||||
})(MikroORMEntity.prototype, relationship.name)
|
})(MikroORMEntity.prototype, relationship.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { tableName } = parseEntityName(entity)
|
||||||
|
applyEntityIndexes(MikroORMEntity, tableName, [
|
||||||
|
{
|
||||||
|
on: [foreignKeyName],
|
||||||
|
where: "deleted_at IS NULL",
|
||||||
|
},
|
||||||
|
])
|
||||||
applyForeignKeyAssignationHooks(foreignKeyName)
|
applyForeignKeyAssignationHooks(foreignKeyName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -270,7 +318,14 @@ export function defineBelongsToRelationship(
|
|||||||
nullable: relationship.nullable,
|
nullable: relationship.nullable,
|
||||||
persist: false,
|
persist: false,
|
||||||
})(MikroORMEntity.prototype, foreignKeyName)
|
})(MikroORMEntity.prototype, foreignKeyName)
|
||||||
ForeignKey()(MikroORMEntity.prototype, foreignKeyName)
|
|
||||||
|
const { tableName } = parseEntityName(entity)
|
||||||
|
applyEntityIndexes(MikroORMEntity, tableName, [
|
||||||
|
{
|
||||||
|
on: [foreignKeyName],
|
||||||
|
where: "deleted_at IS NULL",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
applyForeignKeyAssignationHooks(foreignKeyName)
|
applyForeignKeyAssignationHooks(foreignKeyName)
|
||||||
return
|
return
|
||||||
@@ -289,6 +344,7 @@ export function defineBelongsToRelationship(
|
|||||||
*/
|
*/
|
||||||
export function defineManyToManyRelationship(
|
export function defineManyToManyRelationship(
|
||||||
MikroORMEntity: EntityConstructor<any>,
|
MikroORMEntity: EntityConstructor<any>,
|
||||||
|
entity: DmlEntity<any, any>,
|
||||||
relationship: RelationshipMetadata,
|
relationship: RelationshipMetadata,
|
||||||
relatedEntity: DmlEntity<
|
relatedEntity: DmlEntity<
|
||||||
Record<string, PropertyType<any> | RelationshipType<any>>,
|
Record<string, PropertyType<any> | RelationshipType<any>>,
|
||||||
@@ -296,59 +352,60 @@ export function defineManyToManyRelationship(
|
|||||||
>,
|
>,
|
||||||
{
|
{
|
||||||
relatedModelName,
|
relatedModelName,
|
||||||
|
relatedTableName,
|
||||||
pgSchema,
|
pgSchema,
|
||||||
}: { relatedModelName: string; pgSchema: string | undefined },
|
}: {
|
||||||
|
relatedModelName: string
|
||||||
|
pgSchema: string | undefined
|
||||||
|
relatedTableName: string
|
||||||
|
},
|
||||||
{ MANY_TO_MANY_TRACKED_RELATIONS }: Context
|
{ MANY_TO_MANY_TRACKED_RELATIONS }: Context
|
||||||
) {
|
) {
|
||||||
let mappedBy = relationship.mappedBy
|
let mappedBy = relationship.mappedBy
|
||||||
let inversedBy: undefined | string
|
let inversedBy: undefined | string
|
||||||
let pivotEntityName: undefined | string
|
let pivotEntityName: undefined | string
|
||||||
let pivotTableName: undefined | string
|
let pivotTableName: undefined | string
|
||||||
|
let joinColumn: undefined | string = relationship.options.joinColumn
|
||||||
|
let inverseJoinColumn: undefined | string =
|
||||||
|
relationship.options.inverseJoinColumn
|
||||||
|
|
||||||
|
const [otherSideRelationshipProperty, otherSideRelationship] =
|
||||||
|
retrieveOtherSideRelationshipManyToMany({
|
||||||
|
relationship,
|
||||||
|
relatedEntity,
|
||||||
|
relatedModelName,
|
||||||
|
entity,
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validating other side of relationship when mapped by is defined
|
* Validating other side of relationship when mapped by is defined
|
||||||
*/
|
*/
|
||||||
if (mappedBy) {
|
if (mappedBy) {
|
||||||
const otherSideRelation = relatedEntity.parse().schema[mappedBy]
|
if (!otherSideRelationship) {
|
||||||
if (!otherSideRelation) {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Missing property "${mappedBy}" on "${relatedModelName}" entity. Make sure to define it as a relationship`
|
`Missing property "${mappedBy}" on "${relatedModelName}" entity. Make sure to define it as a relationship`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!DmlManyToMany.isManyToMany(otherSideRelation)) {
|
if (!DmlManyToMany.isManyToMany(otherSideRelationship)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid relationship reference for "${mappedBy}" on "${relatedModelName}" entity. Make sure to define a manyToMany relationship`
|
`Invalid relationship reference for "${mappedBy}" on "${relatedModelName}" entity. Make sure to define a manyToMany relationship`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the other side has defined a mapped by and if that
|
|
||||||
* mapping is already tracked as the owner.
|
|
||||||
*
|
|
||||||
* - If yes, we will inverse our mapped by
|
|
||||||
* - Otherwise, we will track ourselves as the owner.
|
|
||||||
*/
|
|
||||||
if (
|
|
||||||
otherSideRelation.parse(mappedBy).mappedBy &&
|
|
||||||
MANY_TO_MANY_TRACKED_RELATIONS[`${relatedModelName}.${mappedBy}`]
|
|
||||||
) {
|
|
||||||
inversedBy = mappedBy
|
|
||||||
mappedBy = undefined
|
|
||||||
} else {
|
|
||||||
MANY_TO_MANY_TRACKED_RELATIONS[
|
|
||||||
`${MikroORMEntity.name}.${relationship.name}`
|
|
||||||
] = true
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
validateManyToManyRelationshipWithoutMappedBy({
|
validateManyToManyRelationshipWithoutMappedBy({
|
||||||
MikroORMEntity,
|
MikroORMEntity,
|
||||||
relationship,
|
relationship,
|
||||||
relatedEntity,
|
relatedEntity,
|
||||||
relatedModelName,
|
relatedModelName,
|
||||||
|
entity,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MANY_TO_MANY_TRACKED_RELATIONS[
|
||||||
|
`${MikroORMEntity.name}.${relationship.name}`
|
||||||
|
] = true
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validating pivot entity when it is defined and computing
|
* Validating pivot entity when it is defined and computing
|
||||||
* its name
|
* its name
|
||||||
@@ -371,6 +428,10 @@ export function defineManyToManyRelationship(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!pivotEntityName) {
|
if (!pivotEntityName) {
|
||||||
|
const { tableName } = parseEntityName(entity)
|
||||||
|
let tableNameWithoutSchema: string
|
||||||
|
let relatedTableNameWithoutSchema: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pivot table name is created as follows (when not explicitly provided)
|
* Pivot table name is created as follows (when not explicitly provided)
|
||||||
*
|
*
|
||||||
@@ -379,20 +440,58 @@ export function defineManyToManyRelationship(
|
|||||||
* - Converting them from camelCase to snake_case.
|
* - Converting them from camelCase to snake_case.
|
||||||
* - And finally pluralizing the second entity name.
|
* - And finally pluralizing the second entity name.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
let [schema, ...tableTokens] = tableName.split(".")
|
||||||
|
if (!tableTokens.length) {
|
||||||
|
tableNameWithoutSchema = schema
|
||||||
|
} else {
|
||||||
|
tableNameWithoutSchema = tableTokens.join(".")
|
||||||
|
}
|
||||||
|
|
||||||
|
const [relatedSchema, ...relatedTableTokens] = relatedTableName.split(".")
|
||||||
|
if (!relatedTableTokens.length) {
|
||||||
|
relatedTableNameWithoutSchema = relatedSchema
|
||||||
|
} else {
|
||||||
|
relatedTableNameWithoutSchema = relatedTableTokens.join(".")
|
||||||
|
}
|
||||||
|
|
||||||
pivotTableName =
|
pivotTableName =
|
||||||
relationship.options.pivotTable ??
|
relationship.options.pivotTable ??
|
||||||
[MikroORMEntity.name.toLowerCase(), relatedModelName.toLowerCase()]
|
otherSideRelationship.parse("").options.pivotTable ??
|
||||||
|
[tableNameWithoutSchema, relatedTableNameWithoutSchema]
|
||||||
.sort()
|
.sort()
|
||||||
.map((token, index) => {
|
.map((token, index) => {
|
||||||
if (index === 1) {
|
if (index === 1) {
|
||||||
return pluralize(camelToSnakeCase(token))
|
return pluralize(token)
|
||||||
}
|
}
|
||||||
return camelToSnakeCase(token)
|
return token
|
||||||
})
|
})
|
||||||
.join("_")
|
.join("_")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const otherSideRelationOptions = otherSideRelationship.parse("").options
|
||||||
|
|
||||||
|
const isOwner =
|
||||||
|
!!joinColumn ||
|
||||||
|
!!inverseJoinColumn ||
|
||||||
|
!!relationship.options.pivotTable ||
|
||||||
|
/**
|
||||||
|
* We can't infer it from the current entity so lets
|
||||||
|
* look at the otherside configuration as well to make a choice
|
||||||
|
*/
|
||||||
|
(!otherSideRelationOptions.pivotTable &&
|
||||||
|
!otherSideRelationOptions.joinColumn &&
|
||||||
|
!otherSideRelationOptions.inverseJoinColumn &&
|
||||||
|
!MANY_TO_MANY_TRACKED_RELATIONS[
|
||||||
|
`${relatedModelName}.${otherSideRelationshipProperty}`
|
||||||
|
])
|
||||||
|
|
||||||
|
const mappedByProp = isOwner ? "inversedBy" : "mappedBy"
|
||||||
|
const mappedByPropValue =
|
||||||
|
mappedBy ?? inversedBy ?? otherSideRelationshipProperty
|
||||||
|
|
||||||
ManyToMany({
|
ManyToMany({
|
||||||
|
owner: isOwner,
|
||||||
entity: relatedModelName,
|
entity: relatedModelName,
|
||||||
...(pivotTableName
|
...(pivotTableName
|
||||||
? {
|
? {
|
||||||
@@ -402,8 +501,9 @@ export function defineManyToManyRelationship(
|
|||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(pivotEntityName ? { pivotEntity: pivotEntityName } : {}),
|
...(pivotEntityName ? { pivotEntity: pivotEntityName } : {}),
|
||||||
...(mappedBy ? { mappedBy: mappedBy as any } : {}),
|
...({ [mappedByProp]: mappedByPropValue } as any),
|
||||||
...(inversedBy ? { inversedBy: inversedBy as any } : {}),
|
...(joinColumn ? { joinColumn } : {}),
|
||||||
|
...(inverseJoinColumn ? { inverseJoinColumn } : {}),
|
||||||
})(MikroORMEntity.prototype, relationship.name)
|
})(MikroORMEntity.prototype, relationship.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,6 +512,7 @@ export function defineManyToManyRelationship(
|
|||||||
*/
|
*/
|
||||||
export function defineRelationship(
|
export function defineRelationship(
|
||||||
MikroORMEntity: EntityConstructor<any>,
|
MikroORMEntity: EntityConstructor<any>,
|
||||||
|
entity: DmlEntity<any, any>,
|
||||||
relationship: RelationshipMetadata,
|
relationship: RelationshipMetadata,
|
||||||
cascades: EntityCascades<string[]>,
|
cascades: EntityCascades<string[]>,
|
||||||
context: Context
|
context: Context
|
||||||
@@ -474,6 +575,7 @@ export function defineRelationship(
|
|||||||
case "belongsTo":
|
case "belongsTo":
|
||||||
defineBelongsToRelationship(
|
defineBelongsToRelationship(
|
||||||
MikroORMEntity,
|
MikroORMEntity,
|
||||||
|
entity,
|
||||||
relationship,
|
relationship,
|
||||||
relatedEntity,
|
relatedEntity,
|
||||||
relatedEntityInfo
|
relatedEntityInfo
|
||||||
@@ -482,6 +584,7 @@ export function defineRelationship(
|
|||||||
case "manyToMany":
|
case "manyToMany":
|
||||||
defineManyToManyRelationship(
|
defineManyToManyRelationship(
|
||||||
MikroORMEntity,
|
MikroORMEntity,
|
||||||
|
entity,
|
||||||
relationship,
|
relationship,
|
||||||
relatedEntity,
|
relatedEntity,
|
||||||
relatedEntityInfo,
|
relatedEntityInfo,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
EntityIndex,
|
EntityIndex,
|
||||||
PropertyMetadata,
|
PropertyMetadata,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { MetadataStorage } from "@mikro-orm/core"
|
|
||||||
import { createPsqlIndexStatementHelper } from "../../../common"
|
import { createPsqlIndexStatementHelper } from "../../../common"
|
||||||
import { validateIndexFields } from "../mikro-orm/build-indexes"
|
import { validateIndexFields } from "../mikro-orm/build-indexes"
|
||||||
|
|
||||||
@@ -38,8 +37,7 @@ export function applyEntityIndexes(
|
|||||||
tableName: string,
|
tableName: string,
|
||||||
entityIndexes: EntityIndex[] = []
|
entityIndexes: EntityIndex[] = []
|
||||||
) {
|
) {
|
||||||
const foreignKeyIndexes = applyForeignKeyIndexes(MikroORMEntity)
|
const indexes = [...entityIndexes]
|
||||||
const indexes = [...entityIndexes, ...foreignKeyIndexes]
|
|
||||||
|
|
||||||
indexes.forEach((index) => {
|
indexes.forEach((index) => {
|
||||||
validateIndexFields(MikroORMEntity, index)
|
validateIndexFields(MikroORMEntity, index)
|
||||||
@@ -55,29 +53,3 @@ export function applyEntityIndexes(
|
|||||||
entityIndexStatement.MikroORMIndex()(MikroORMEntity)
|
entityIndexStatement.MikroORMIndex()(MikroORMEntity)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
When a "oneToMany" relationship is found on the MikroORM entity, we create an index by default
|
|
||||||
on the foreign key property.
|
|
||||||
*/
|
|
||||||
function applyForeignKeyIndexes(MikroORMEntity: EntityConstructor<any>) {
|
|
||||||
const foreignKeyIndexes: EntityIndex[] = []
|
|
||||||
|
|
||||||
for (const foreignKey of getEntityForeignKeys(MikroORMEntity)) {
|
|
||||||
foreignKeyIndexes.push({
|
|
||||||
on: [foreignKey],
|
|
||||||
where: "deleted_at IS NULL",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return foreignKeyIndexes
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEntityForeignKeys(MikroORMEntity: EntityConstructor<any>) {
|
|
||||||
const properties =
|
|
||||||
MetadataStorage.getMetadataFromDecorator(MikroORMEntity).properties
|
|
||||||
|
|
||||||
return Object.keys(properties).filter(
|
|
||||||
(propertyName) => properties[propertyName].isForeignKey
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,10 @@ import {
|
|||||||
MikroORM,
|
MikroORM,
|
||||||
} from "@mikro-orm/core"
|
} from "@mikro-orm/core"
|
||||||
import { model } from "../../entity-builder"
|
import { model } from "../../entity-builder"
|
||||||
import { toMikroOrmEntities } from "../../helpers/create-mikro-orm-entity"
|
import {
|
||||||
|
mikroORMEntityBuilder,
|
||||||
|
toMikroOrmEntities,
|
||||||
|
} from "../../helpers/create-mikro-orm-entity"
|
||||||
import { createDatabase, dropDatabase } from "pg-god"
|
import { createDatabase, dropDatabase } from "pg-god"
|
||||||
import { CustomTsMigrationGenerator, mikroOrmSerializer } from "../../../dal"
|
import { CustomTsMigrationGenerator, mikroOrmSerializer } from "../../../dal"
|
||||||
import { EntityConstructor } from "@medusajs/types"
|
import { EntityConstructor } from "@medusajs/types"
|
||||||
@@ -28,6 +31,7 @@ describe("EntityBuilder | enum", () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
MetadataStorage.clear()
|
MetadataStorage.clear()
|
||||||
|
mikroORMEntityBuilder.clear()
|
||||||
|
|
||||||
const user = model.define("user", {
|
const user = model.define("user", {
|
||||||
id: model.id().primaryKey(),
|
id: model.id().primaryKey(),
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { MetadataStorage, MikroORM } from "@mikro-orm/core"
|
import { MetadataStorage, MikroORM } from "@mikro-orm/core"
|
||||||
import { model } from "../../entity-builder"
|
import { model } from "../../entity-builder"
|
||||||
import { toMikroOrmEntities } from "../../helpers/create-mikro-orm-entity"
|
import {
|
||||||
|
mikroORMEntityBuilder,
|
||||||
|
toMikroOrmEntities,
|
||||||
|
} from "../../helpers/create-mikro-orm-entity"
|
||||||
import { createDatabase, dropDatabase } from "pg-god"
|
import { createDatabase, dropDatabase } from "pg-god"
|
||||||
import { CustomTsMigrationGenerator, mikroOrmSerializer } from "../../../dal"
|
import { CustomTsMigrationGenerator, mikroOrmSerializer } from "../../../dal"
|
||||||
import { EntityConstructor } from "@medusajs/types"
|
import { EntityConstructor } from "@medusajs/types"
|
||||||
@@ -24,6 +27,7 @@ describe("hasOne - belongTo", () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
MetadataStorage.clear()
|
MetadataStorage.clear()
|
||||||
|
mikroORMEntityBuilder.clear()
|
||||||
|
|
||||||
const team = model.define("team", {
|
const team = model.define("team", {
|
||||||
id: model.id().primaryKey(),
|
id: model.id().primaryKey(),
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
|
import { join } from "path"
|
||||||
import { MetadataStorage, MikroORM } from "@mikro-orm/core"
|
import { MetadataStorage, MikroORM } from "@mikro-orm/core"
|
||||||
import { model } from "../../entity-builder"
|
import { model } from "../../entity-builder"
|
||||||
import { toMikroOrmEntities } from "../../helpers/create-mikro-orm-entity"
|
import {
|
||||||
|
mikroORMEntityBuilder,
|
||||||
|
toMikroOrmEntities,
|
||||||
|
} from "../../helpers/create-mikro-orm-entity"
|
||||||
import { createDatabase, dropDatabase } from "pg-god"
|
import { createDatabase, dropDatabase } from "pg-god"
|
||||||
import { CustomTsMigrationGenerator, mikroOrmSerializer } from "../../../dal"
|
import { CustomTsMigrationGenerator, mikroOrmSerializer } from "../../../dal"
|
||||||
import { EntityConstructor } from "@medusajs/types"
|
import { EntityConstructor } from "@medusajs/types"
|
||||||
import { pgGodCredentials } from "../utils"
|
import { pgGodCredentials } from "../utils"
|
||||||
import { FileSystem } from "../../../common"
|
import { FileSystem } from "../../../common"
|
||||||
import { join } from "path"
|
|
||||||
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
export const fileSystem = new FileSystem(
|
export const fileSystem = new FileSystem(
|
||||||
join(__dirname, "../../integration-tests-migrations-many-to-many")
|
join(__dirname, "../../integration-tests-migrations-many-to-many")
|
||||||
@@ -27,6 +32,7 @@ describe("manyToMany - manyToMany", () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
MetadataStorage.clear()
|
MetadataStorage.clear()
|
||||||
|
mikroORMEntityBuilder.clear()
|
||||||
|
|
||||||
const team = model.define("team", {
|
const team = model.define("team", {
|
||||||
id: model.id().primaryKey(),
|
id: model.id().primaryKey(),
|
||||||
@@ -191,43 +197,108 @@ describe("manyToMany - manyToMany", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it(`should fail to load the dml's if both side of the relation are missing the mappedBy options`, () => {
|
it(`should not fail to load the dml's if both side of the relation are missing the mappedBy options`, () => {
|
||||||
|
mikroORMEntityBuilder.clear()
|
||||||
|
|
||||||
const team = model.define("team", {
|
const team = model.define("team", {
|
||||||
id: model.id().primaryKey(),
|
id: model.id().primaryKey(),
|
||||||
name: model.text(),
|
name: model.text(),
|
||||||
users: model.manyToMany(() => user, {
|
users: model.manyToMany(() => user, {
|
||||||
pivotEntity: () => squad,
|
pivot_table: "team_users",
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const squad = model.define("teamUsers", {
|
const user = model.define("user", {
|
||||||
id: model.id().primaryKey(),
|
id: model.id().primaryKey(),
|
||||||
user: model.belongsTo(() => user, { mappedBy: "squads" }),
|
username: model.text(),
|
||||||
squad: model.belongsTo(() => team, { mappedBy: "users" }),
|
squads: model.manyToMany(() => team),
|
||||||
|
})
|
||||||
|
|
||||||
|
;[User, Team] = toMikroOrmEntities([user, team])
|
||||||
|
|
||||||
|
const teamMetaData = MetadataStorage.getMetadataFromDecorator(Team)
|
||||||
|
expect((teamMetaData.properties as any).users.mappedBy).toBe("squads")
|
||||||
|
expect((teamMetaData.properties as any).users.owner).toBe(false)
|
||||||
|
|
||||||
|
const userMetaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||||
|
expect((userMetaData.properties as any).squads.mappedBy).not.toBeDefined()
|
||||||
|
expect((userMetaData.properties as any).squads.inversedBy).toBe("users")
|
||||||
|
expect((userMetaData.properties as any).squads.owner).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should load the dml's correclty when both side of the relation are specifying the mappedBy options without pivot table`, () => {
|
||||||
|
mikroORMEntityBuilder.clear()
|
||||||
|
|
||||||
|
const team = model.define("team", {
|
||||||
|
id: model.id().primaryKey(),
|
||||||
|
name: model.text(),
|
||||||
|
users: model.manyToMany(() => user, {
|
||||||
|
mappedBy: "squads",
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const user = model.define("user", {
|
const user = model.define("user", {
|
||||||
id: model.id().primaryKey(),
|
id: model.id().primaryKey(),
|
||||||
username: model.text(),
|
username: model.text(),
|
||||||
squads: model.manyToMany(() => team, {
|
squads: model.manyToMany(() => team, {
|
||||||
pivotEntity: () => squad,
|
mappedBy: "users",
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let [User, Team] = toMikroOrmEntities([user, team])
|
||||||
|
|
||||||
|
const teamMetaData = MetadataStorage.getMetadataFromDecorator(Team)
|
||||||
|
expect((teamMetaData.properties as any).users.mappedBy).toBe("squads")
|
||||||
|
expect((teamMetaData.properties as any).users.owner).toBe(false)
|
||||||
|
expect((teamMetaData.properties as any).users.pivotTable).toBe("team_users")
|
||||||
|
|
||||||
|
const userMetaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||||
|
expect((userMetaData.properties as any).squads.mappedBy).not.toBeDefined()
|
||||||
|
expect((userMetaData.properties as any).squads.inversedBy).toBe("users")
|
||||||
|
expect((userMetaData.properties as any).squads.owner).toBe(true)
|
||||||
|
expect((userMetaData.properties as any).squads.pivotTable).toBe(
|
||||||
|
"team_users"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should fail to load the dml's if both side of the relation are missing the mappedBy options and multiple relations points to the same entity`, () => {
|
||||||
|
mikroORMEntityBuilder.clear()
|
||||||
|
|
||||||
|
const team = model.define("team", {
|
||||||
|
id: model.id().primaryKey(),
|
||||||
|
name: model.text(),
|
||||||
|
users: model.manyToMany(() => user, {
|
||||||
|
pivot_table: "team_users",
|
||||||
|
}),
|
||||||
|
users2: model.manyToMany(() => user, {
|
||||||
|
pivot_table: "team_users2",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const user = model.define("user", {
|
||||||
|
id: model.id().primaryKey(),
|
||||||
|
username: model.text(),
|
||||||
|
squads: model.manyToMany(() => team),
|
||||||
|
squads2: model.manyToMany(() => team),
|
||||||
|
})
|
||||||
|
|
||||||
let error!: Error
|
let error!: Error
|
||||||
|
|
||||||
try {
|
try {
|
||||||
;[User, Squad, Team] = toMikroOrmEntities([user, squad, team])
|
;[User, Team] = toMikroOrmEntities([user, team])
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e
|
error = e
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(error).toBeTruthy()
|
expect(error).toBeDefined()
|
||||||
expect(error.message).toEqual(
|
expect(error?.message).toEqual(
|
||||||
'Invalid relationship reference for "User.squads". "mappedBy" should be defined on one side or the other.'
|
'Invalid relationship reference for "user.squads". Make sure to set the mappedBy property on one side or the other or both.'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it(`should fail to load the dml's if the relation is defined only on one side`, () => {
|
it(`should fail to load the dml's if the relation is defined only on one side`, () => {
|
||||||
|
mikroORMEntityBuilder.clear()
|
||||||
|
|
||||||
const team = model.define("team", {
|
const team = model.define("team", {
|
||||||
id: model.id().primaryKey(),
|
id: model.id().primaryKey(),
|
||||||
name: model.text(),
|
name: model.text(),
|
||||||
@@ -248,7 +319,7 @@ describe("manyToMany - manyToMany", () => {
|
|||||||
|
|
||||||
expect(error).toBeTruthy()
|
expect(error).toBeTruthy()
|
||||||
expect(error.message).toEqual(
|
expect(error.message).toEqual(
|
||||||
'Invalid relationship reference for "Team.users". The other side of the relationship is missing.'
|
'Invalid relationship reference for "Team.users". "mappedBy" should be defined on one side or the other.'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import { MetadataStorage, MikroORM } from "@mikro-orm/core"
|
import { MetadataStorage, MikroORM } from "@mikro-orm/core"
|
||||||
import { model } from "../../entity-builder"
|
import { model } from "../../entity-builder"
|
||||||
import { toMikroOrmEntities } from "../../helpers/create-mikro-orm-entity"
|
import {
|
||||||
|
mikroORMEntityBuilder,
|
||||||
|
toMikroOrmEntities,
|
||||||
|
} from "../../helpers/create-mikro-orm-entity"
|
||||||
import { createDatabase, dropDatabase } from "pg-god"
|
import { createDatabase, dropDatabase } from "pg-god"
|
||||||
import {
|
import {
|
||||||
CustomTsMigrationGenerator,
|
CustomTsMigrationGenerator,
|
||||||
@@ -30,6 +33,7 @@ describe("manyToOne - belongTo", () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
MetadataStorage.clear()
|
MetadataStorage.clear()
|
||||||
|
mikroORMEntityBuilder.clear()
|
||||||
|
|
||||||
const team = model.define("team", {
|
const team = model.define("team", {
|
||||||
id: model.id().primaryKey(),
|
id: model.id().primaryKey(),
|
||||||
|
|||||||
+5
-2
@@ -4,9 +4,11 @@ import { MetadataStorage } from "@mikro-orm/core"
|
|||||||
|
|
||||||
import { Migrations } from "../../index"
|
import { Migrations } from "../../index"
|
||||||
import { FileSystem } from "../../../common"
|
import { FileSystem } from "../../../common"
|
||||||
import { DmlEntity, model } from "../../../dml"
|
import { DmlEntity, mikroORMEntityBuilder, model } from "../../../dml"
|
||||||
import { defineMikroOrmCliConfig } from "../../../modules-sdk"
|
import { defineMikroOrmCliConfig } from "../../../modules-sdk"
|
||||||
|
|
||||||
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
const DB_HOST = process.env.DB_HOST ?? "localhost"
|
const DB_HOST = process.env.DB_HOST ?? "localhost"
|
||||||
const DB_USERNAME = process.env.DB_USERNAME ?? ""
|
const DB_USERNAME = process.env.DB_USERNAME ?? ""
|
||||||
const DB_PASSWORD = process.env.DB_PASSWORD ?? " "
|
const DB_PASSWORD = process.env.DB_PASSWORD ?? " "
|
||||||
@@ -29,7 +31,8 @@ describe("Generate migrations", () => {
|
|||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await fs.cleanup()
|
await fs.cleanup()
|
||||||
MetadataStorage.clear()
|
MetadataStorage.clear()
|
||||||
}, 300 * 1000)
|
mikroORMEntityBuilder.clear()
|
||||||
|
})
|
||||||
|
|
||||||
test("generate migrations for a single entity", async () => {
|
test("generate migrations for a single entity", async () => {
|
||||||
const User = model.define("User", {
|
const User = model.define("User", {
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ describe("joiner-config-builder", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it.only("should return a full joiner configuration with custom aliases overriding defaults", () => {
|
it("should return a full joiner configuration with custom aliases overriding defaults", () => {
|
||||||
const joinerConfig = defineJoinerConfig(Modules.FULFILLMENT, {
|
const joinerConfig = defineJoinerConfig(Modules.FULFILLMENT, {
|
||||||
models: [FulfillmentSet],
|
models: [FulfillmentSet],
|
||||||
alias: [
|
alias: [
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DAL, FindConfig } from "@medusajs/types"
|
import { DAL, FindConfig, InferRepositoryReturnType } from "@medusajs/types"
|
||||||
import { deduplicate, isObject } from "../common"
|
import { deduplicate, isObject } from "../common"
|
||||||
|
|
||||||
import { SoftDeletableFilterKey } from "../dal/mikro-orm/mikro-orm-soft-deletable-filter"
|
import { SoftDeletableFilterKey } from "../dal/mikro-orm/mikro-orm-soft-deletable-filter"
|
||||||
@@ -10,17 +10,19 @@ type FilterFlags = {
|
|||||||
withDeleted?: boolean
|
withDeleted?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildQuery<T = any, TDto = any>(
|
export function buildQuery<const T = any>(
|
||||||
filters: Record<string, any> = {},
|
filters: Record<string, any> = {},
|
||||||
config: FindConfig<TDto> & { primaryKeyFields?: string | string[] } = {}
|
config: FindConfig<InferRepositoryReturnType<T>> & {
|
||||||
|
primaryKeyFields?: string | string[]
|
||||||
|
} = {}
|
||||||
): Required<DAL.FindOptions<T>> {
|
): Required<DAL.FindOptions<T>> {
|
||||||
const where: DAL.FilterQuery<T> = {}
|
const where = {} as DAL.FilterQuery<T>
|
||||||
const filterFlags: FilterFlags = {}
|
const filterFlags: FilterFlags = {}
|
||||||
buildWhere(filters, where, filterFlags)
|
buildWhere(filters, where, filterFlags)
|
||||||
|
|
||||||
delete config.primaryKeyFields
|
delete config.primaryKeyFields
|
||||||
|
|
||||||
const findOptions: DAL.OptionsQuery<T, any> = {
|
const findOptions: DAL.FindOptions<T>["options"] = {
|
||||||
populate: deduplicate(config.relations ?? []),
|
populate: deduplicate(config.relations ?? []),
|
||||||
fields: config.select as string[],
|
fields: config.select as string[],
|
||||||
limit: (Number.isSafeInteger(config.take) && config.take) || undefined,
|
limit: (Number.isSafeInteger(config.take) && config.take) || undefined,
|
||||||
@@ -28,7 +30,9 @@ export function buildQuery<T = any, TDto = any>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.order) {
|
if (config.order) {
|
||||||
findOptions.orderBy = config.order as DAL.OptionsQuery<T>["orderBy"]
|
findOptions.orderBy = config.order as Required<
|
||||||
|
DAL.FindOptions<T>
|
||||||
|
>["options"]["orderBy"]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.withDeleted || filterFlags.withDeleted) {
|
if (config.withDeleted || filterFlags.withDeleted) {
|
||||||
@@ -50,7 +54,7 @@ export function buildQuery<T = any, TDto = any>(
|
|||||||
Object.assign(findOptions, config.options)
|
Object.assign(findOptions, config.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { where, options: findOptions }
|
return { where, options: findOptions } as Required<DAL.FindOptions<T>>
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildWhere(
|
function buildWhere(
|
||||||
|
|||||||
@@ -314,6 +314,7 @@ ${serviceBObj.module}: {
|
|||||||
|
|
||||||
const isModuleAPrimaryKeyValid =
|
const isModuleAPrimaryKeyValid =
|
||||||
moduleAPrimaryKeys.includes(serviceAPrimaryKey)
|
moduleAPrimaryKeys.includes(serviceAPrimaryKey)
|
||||||
|
|
||||||
if (!isModuleAPrimaryKeyValid) {
|
if (!isModuleAPrimaryKeyValid) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Primary key ${serviceAPrimaryKey} is not defined on service ${serviceAObj.module}`
|
`Primary key ${serviceAPrimaryKey} is not defined on service ${serviceAObj.module}`
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import * as path from "path"
|
|||||||
import { dirname, join, normalize } from "path"
|
import { dirname, join, normalize } from "path"
|
||||||
import {
|
import {
|
||||||
camelToSnakeCase,
|
camelToSnakeCase,
|
||||||
deduplicate,
|
|
||||||
getCallerFilePath,
|
getCallerFilePath,
|
||||||
isObject,
|
isObject,
|
||||||
lowerCaseFirst,
|
lowerCaseFirst,
|
||||||
@@ -165,29 +164,40 @@ export function defineJoinerConfig(
|
|||||||
}
|
}
|
||||||
linkableKeys = mergedLinkableKeys
|
linkableKeys = mergedLinkableKeys
|
||||||
|
|
||||||
if (!primaryKeys && modelDefinitions.size) {
|
/**
|
||||||
|
* Merge custom primary keys from the joiner config with the infered primary keys
|
||||||
|
* from the models.
|
||||||
|
*
|
||||||
|
* TODO: Maybe worth looking into the real needs for primary keys.
|
||||||
|
* It can happen that we could just remove that but we need to investigate (looking at the
|
||||||
|
* lookups from the remote joiner to identify which entity a property refers to)
|
||||||
|
*/
|
||||||
|
primaryKeys ??= []
|
||||||
|
const finalPrimaryKeys = new Set(primaryKeys)
|
||||||
|
if (modelDefinitions.size) {
|
||||||
const linkConfig = buildLinkConfigFromModelObjects(
|
const linkConfig = buildLinkConfigFromModelObjects(
|
||||||
serviceName,
|
serviceName,
|
||||||
Object.fromEntries(modelDefinitions)
|
Object.fromEntries(modelDefinitions)
|
||||||
)
|
)
|
||||||
|
|
||||||
primaryKeys = deduplicate(
|
Object.values(linkConfig).flatMap((entityLinkConfig) => {
|
||||||
Object.values(linkConfig).flatMap((entityLinkConfig) => {
|
return Object.values(
|
||||||
return (Object.values(entityLinkConfig as any) as any[])
|
entityLinkConfig as Record<string, { primaryKey: string }>
|
||||||
.filter((linkableConfig) => isObject(linkableConfig))
|
)
|
||||||
.map((linkableConfig) => {
|
.filter((linkableConfig) => isObject(linkableConfig))
|
||||||
// @ts-ignore
|
.forEach((linkableConfig) => {
|
||||||
return linkableConfig.primaryKey
|
finalPrimaryKeys.add(linkableConfig.primaryKey)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
primaryKeys = Array.from(finalPrimaryKeys.add("id"))
|
||||||
|
|
||||||
// TODO: In the context of DML add a validation on primary keys and linkable keys if the consumer provide them manually. follow up pr
|
// TODO: In the context of DML add a validation on primary keys and linkable keys if the consumer provide them manually. follow up pr
|
||||||
|
|
||||||
return {
|
return {
|
||||||
serviceName,
|
serviceName,
|
||||||
primaryKeys: primaryKeys ?? ["id"],
|
primaryKeys,
|
||||||
schema,
|
schema,
|
||||||
linkableKeys: linkableKeys,
|
linkableKeys: linkableKeys,
|
||||||
alias: [
|
alias: [
|
||||||
@@ -342,26 +352,44 @@ export function buildLinkableKeysFromMikroOrmObjects(
|
|||||||
export function buildLinkConfigFromModelObjects<
|
export function buildLinkConfigFromModelObjects<
|
||||||
const ServiceName extends string,
|
const ServiceName extends string,
|
||||||
const T extends Record<string, IDmlEntity<any, any>>
|
const T extends Record<string, IDmlEntity<any, any>>
|
||||||
>(serviceName: ServiceName, models: T): InfersLinksConfig<ServiceName, T> {
|
>(
|
||||||
|
serviceName: ServiceName,
|
||||||
|
models: T,
|
||||||
|
linkableKeys: Record<string, string> = {}
|
||||||
|
): InfersLinksConfig<ServiceName, T> {
|
||||||
|
// In case some models have been provided to a custom joiner config, the linkable will be limited
|
||||||
|
// to that set of models. We dont want to expose models that should not be linkable.
|
||||||
|
const linkableModels = Object.values(linkableKeys)
|
||||||
const linkConfig = {} as InfersLinksConfig<ServiceName, T>
|
const linkConfig = {} as InfersLinksConfig<ServiceName, T>
|
||||||
|
|
||||||
for (const model of Object.values(models) ?? []) {
|
for (const model of Object.values(models) ?? []) {
|
||||||
if (!DmlEntity.isDmlEntity(model)) {
|
const classLikeModelName = upperCaseFirst(model.name)
|
||||||
|
|
||||||
|
if (
|
||||||
|
!DmlEntity.isDmlEntity(model) ||
|
||||||
|
(linkableModels.length && !linkableModels.includes(classLikeModelName))
|
||||||
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const schema = model.schema
|
const schema = model.schema
|
||||||
// @ts-ignore
|
|
||||||
|
/**
|
||||||
|
* When using a linkable, if a specific linkable property is not specified, the toJSON
|
||||||
|
* function will be called and return the first linkable available for this model.
|
||||||
|
*/
|
||||||
const modelLinkConfig = (linkConfig[lowerCaseFirst(model.name)] ??= {
|
const modelLinkConfig = (linkConfig[lowerCaseFirst(model.name)] ??= {
|
||||||
toJSON: function () {
|
toJSON: function () {
|
||||||
const linkables = Object.entries(this)
|
const linkables = Object.entries(this)
|
||||||
.filter(([name]) => name !== "toJSON")
|
.filter(([name]) => name !== "toJSON")
|
||||||
.map(([, object]) => object)
|
.map(([, object]) => object)
|
||||||
const lastIndex = linkables.length - 1
|
return linkables[0]
|
||||||
return linkables[lastIndex]
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build all linkable properties for the model
|
||||||
|
*/
|
||||||
for (const [property, value] of Object.entries(schema)) {
|
for (const [property, value] of Object.entries(schema)) {
|
||||||
if (BaseRelationship.isRelationship(value)) {
|
if (BaseRelationship.isRelationship(value)) {
|
||||||
continue
|
continue
|
||||||
@@ -378,10 +406,46 @@ export function buildLinkConfigFromModelObjects<
|
|||||||
primaryKey: property,
|
primaryKey: property,
|
||||||
serviceName,
|
serviceName,
|
||||||
field: lowerCaseFirst(model.name),
|
field: lowerCaseFirst(model.name),
|
||||||
entity: upperCaseFirst(model.name),
|
entity: classLikeModelName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the joiner config specify some custom linkable keys, we merge them with the
|
||||||
|
* existing linkable keys infered from the model above.
|
||||||
|
*/
|
||||||
|
const linkableKeysPerModel = Object.entries(linkableKeys).reduce(
|
||||||
|
(acc, [key, entityName]) => {
|
||||||
|
acc[entityName] ??= []
|
||||||
|
acc[entityName].push(key)
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const linkableKey of linkableKeysPerModel[classLikeModelName] ?? []) {
|
||||||
|
const snakeCasedModelName = camelToSnakeCase(toCamelCase(model.name))
|
||||||
|
|
||||||
|
// Linkable keys by default are prepared with snake cased model name _id
|
||||||
|
// So to be able to compare only the property we have to remove the first part
|
||||||
|
const inferredReferenceProperty = linkableKey.replace(
|
||||||
|
`${snakeCasedModelName}_`,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
|
||||||
|
if (modelLinkConfig[inferredReferenceProperty]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
modelLinkConfig[linkableKey] = {
|
||||||
|
linkable: linkableKey,
|
||||||
|
primaryKey: linkableKey,
|
||||||
|
serviceName,
|
||||||
|
field: lowerCaseFirst(model.name),
|
||||||
|
entity: upperCaseFirst(model.name),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return linkConfig as InfersLinksConfig<ServiceName, T>
|
return linkConfig as InfersLinksConfig<ServiceName, T>
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import {
|
|||||||
BaseFilterable,
|
BaseFilterable,
|
||||||
Context,
|
Context,
|
||||||
FilterQuery,
|
FilterQuery,
|
||||||
|
FilterQuery as InternalFilterQuery,
|
||||||
FindConfig,
|
FindConfig,
|
||||||
InferEntityType,
|
InferEntityType,
|
||||||
FilterQuery as InternalFilterQuery,
|
|
||||||
ModulesSdkTypes,
|
ModulesSdkTypes,
|
||||||
PerformedActions,
|
PerformedActions,
|
||||||
UpsertWithReplaceConfig,
|
UpsertWithReplaceConfig,
|
||||||
@@ -62,7 +62,7 @@ export function MedusaInternalService<
|
|||||||
}
|
}
|
||||||
|
|
||||||
static applyFreeTextSearchFilter(
|
static applyFreeTextSearchFilter(
|
||||||
filters: FilterQuery,
|
filters: FilterQuery & { q?: string },
|
||||||
config: FindConfig<any>
|
config: FindConfig<any>
|
||||||
): void {
|
): void {
|
||||||
if (isDefined(filters?.q)) {
|
if (isDefined(filters?.q)) {
|
||||||
|
|||||||
@@ -49,15 +49,22 @@ export function Module<
|
|||||||
DmlEntity.isDmlEntity(model)
|
DmlEntity.isDmlEntity(model)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: Custom joiner config should take precedence over the DML auto generated linkable
|
||||||
|
// Thats in the case of manually providing models in custom joiner config.
|
||||||
|
// TODO: Add support for non linkable modifier DML object to be skipped from the linkable generation
|
||||||
|
|
||||||
|
const linkableKeys = service.prototype.__joinerConfig().linkableKeys
|
||||||
|
|
||||||
if (dmlObjects.length) {
|
if (dmlObjects.length) {
|
||||||
linkable = buildLinkConfigFromModelObjects<ServiceName, ModelObjects>(
|
linkable = buildLinkConfigFromModelObjects<ServiceName, ModelObjects>(
|
||||||
serviceName,
|
serviceName,
|
||||||
modelObjects
|
modelObjects,
|
||||||
|
linkableKeys
|
||||||
) as Linkable
|
) as Linkable
|
||||||
} else {
|
} else {
|
||||||
linkable = buildLinkConfigFromLinkableKeys(
|
linkable = buildLinkConfigFromLinkableKeys(
|
||||||
serviceName,
|
serviceName,
|
||||||
service.prototype.__joinerConfig().linkableKeys
|
linkableKeys
|
||||||
) as Linkable
|
) as Linkable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { DALUtils } from "@medusajs/framework/utils"
|
|||||||
import { OrderClaim } from "@models"
|
import { OrderClaim } from "@models"
|
||||||
import { setFindMethods } from "../utils/base-repository-find"
|
import { setFindMethods } from "../utils/base-repository-find"
|
||||||
|
|
||||||
export class OrderClaimRepository extends DALUtils.mikroOrmBaseRepositoryFactory<OrderClaim>(
|
export class OrderClaimRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
||||||
OrderClaim
|
OrderClaim
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { DALUtils } from "@medusajs/framework/utils"
|
|||||||
import { OrderExchange } from "@models"
|
import { OrderExchange } from "@models"
|
||||||
import { setFindMethods } from "../utils/base-repository-find"
|
import { setFindMethods } from "../utils/base-repository-find"
|
||||||
|
|
||||||
export class OrderExchangeRepository extends DALUtils.mikroOrmBaseRepositoryFactory<OrderExchange>(
|
export class OrderExchangeRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
||||||
OrderExchange
|
OrderExchange
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { DALUtils } from "@medusajs/framework/utils"
|
|||||||
import { Order } from "@models"
|
import { Order } from "@models"
|
||||||
import { setFindMethods } from "../utils/base-repository-find"
|
import { setFindMethods } from "../utils/base-repository-find"
|
||||||
|
|
||||||
export class OrderRepository extends DALUtils.mikroOrmBaseRepositoryFactory<Order>(
|
export class OrderRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
||||||
Order
|
Order
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { DALUtils } from "@medusajs/framework/utils"
|
|||||||
import { Return } from "@models"
|
import { Return } from "@models"
|
||||||
import { setFindMethods } from "../utils/base-repository-find"
|
import { setFindMethods } from "../utils/base-repository-find"
|
||||||
|
|
||||||
export class ReturnRepository extends DALUtils.mikroOrmBaseRepositoryFactory<Return>(
|
export class ReturnRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
||||||
Return
|
Return
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ export function setFindMethods<T>(klass: Constructor<T>, entity: any) {
|
|||||||
|
|
||||||
klass.prototype.findAndCount = async function findAndCount(
|
klass.prototype.findAndCount = async function findAndCount(
|
||||||
this: any,
|
this: any,
|
||||||
findOptions: DAL.FindOptions<T> = { where: {} },
|
findOptions: DAL.FindOptions<T> = { where: {} } as DAL.FindOptions<T>,
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
): Promise<[T[], number]> {
|
): Promise<[T[], number]> {
|
||||||
const manager = this.getActiveManager(context)
|
const manager = this.getActiveManager(context)
|
||||||
|
|||||||
+20
-3
@@ -1,3 +1,5 @@
|
|||||||
|
import { kebabCase } from "@medusajs/framework/utils"
|
||||||
|
|
||||||
export const productCategoriesData = [
|
export const productCategoriesData = [
|
||||||
{
|
{
|
||||||
id: "category-0",
|
id: "category-0",
|
||||||
@@ -25,7 +27,12 @@ export const productCategoriesData = [
|
|||||||
name: "category 1 b 1",
|
name: "category 1 b 1",
|
||||||
parent_category_id: "category-1-b",
|
parent_category_id: "category-1-b",
|
||||||
},
|
},
|
||||||
]
|
].map((entry) => {
|
||||||
|
return {
|
||||||
|
handle: kebabCase(entry.name),
|
||||||
|
...entry,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export const productCategoriesRankData = [
|
export const productCategoriesRankData = [
|
||||||
{
|
{
|
||||||
@@ -64,7 +71,12 @@ export const productCategoriesRankData = [
|
|||||||
parent_category_id: "category-0-0",
|
parent_category_id: "category-0-0",
|
||||||
rank: 2,
|
rank: 2,
|
||||||
},
|
},
|
||||||
]
|
].map((entry) => {
|
||||||
|
return {
|
||||||
|
handle: kebabCase(entry.name),
|
||||||
|
...entry,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export const eletronicsCategoriesData = [
|
export const eletronicsCategoriesData = [
|
||||||
{
|
{
|
||||||
@@ -188,4 +200,9 @@ export const eletronicsCategoriesData = [
|
|||||||
name: "Mini Models",
|
name: "Mini Models",
|
||||||
parent_category_id: "iphones",
|
parent_category_id: "iphones",
|
||||||
},
|
},
|
||||||
]
|
].map((entry) => {
|
||||||
|
return {
|
||||||
|
handle: kebabCase(entry.name),
|
||||||
|
...entry,
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -2,16 +2,19 @@ export const categoriesData = [
|
|||||||
{
|
{
|
||||||
id: "category-0",
|
id: "category-0",
|
||||||
name: "category 0",
|
name: "category 0",
|
||||||
|
handle: "category-0",
|
||||||
parent_category_id: null,
|
parent_category_id: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "category-1",
|
id: "category-1",
|
||||||
name: "category 1",
|
name: "category 1",
|
||||||
|
handle: "category-1",
|
||||||
parent_category_id: "category-0",
|
parent_category_id: "category-0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "category-1-a",
|
id: "category-1-a",
|
||||||
name: "category 1 a",
|
name: "category 1 a",
|
||||||
|
handle: "category-1-a",
|
||||||
parent_category_id: "category-1",
|
parent_category_id: "category-1",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
+10
-6
@@ -1,10 +1,11 @@
|
|||||||
import { ProductTypes } from "@medusajs/framework/types"
|
import { ProductTypes } from "@medusajs/framework/types"
|
||||||
import { ProductStatus } from "@medusajs/framework/utils"
|
import { ProductStatus, toHandle } from "@medusajs/framework/utils"
|
||||||
import { Image } from "@models"
|
import { ProductImage } from "@models"
|
||||||
import faker from "faker"
|
import faker from "faker"
|
||||||
|
|
||||||
export const buildProductOnlyData = ({
|
export const buildProductOnlyData = ({
|
||||||
title,
|
title,
|
||||||
|
handle,
|
||||||
description,
|
description,
|
||||||
subtitle,
|
subtitle,
|
||||||
is_giftcard,
|
is_giftcard,
|
||||||
@@ -14,6 +15,7 @@ export const buildProductOnlyData = ({
|
|||||||
status,
|
status,
|
||||||
}: {
|
}: {
|
||||||
title?: string
|
title?: string
|
||||||
|
handle?: string
|
||||||
description?: string
|
description?: string
|
||||||
subtitle?: string
|
subtitle?: string
|
||||||
is_giftcard?: boolean
|
is_giftcard?: boolean
|
||||||
@@ -22,15 +24,17 @@ export const buildProductOnlyData = ({
|
|||||||
images?: { id?: string; url: string }[]
|
images?: { id?: string; url: string }[]
|
||||||
status?: ProductStatus
|
status?: ProductStatus
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
|
title ??= faker.commerce.productName()
|
||||||
return {
|
return {
|
||||||
title: title ?? faker.commerce.productName(),
|
title: title as string,
|
||||||
|
handle: handle ?? toHandle(title!),
|
||||||
description: description ?? faker.commerce.productName(),
|
description: description ?? faker.commerce.productName(),
|
||||||
subtitle: subtitle ?? faker.commerce.productName(),
|
subtitle: subtitle ?? faker.commerce.productName(),
|
||||||
is_giftcard: is_giftcard ?? false,
|
is_giftcard: is_giftcard ?? false,
|
||||||
discountable: discountable ?? true,
|
discountable: discountable ?? true,
|
||||||
thumbnail: thumbnail as string,
|
thumbnail: thumbnail as string,
|
||||||
status: status ?? ProductStatus.PUBLISHED,
|
status: status ?? ProductStatus.PUBLISHED,
|
||||||
images: (images ?? []) as Image[],
|
images: (images ?? []) as ProductImage[],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +52,7 @@ export const buildProductAndRelationsData = ({
|
|||||||
options,
|
options,
|
||||||
variants,
|
variants,
|
||||||
collection_id,
|
collection_id,
|
||||||
}: Partial<ProductTypes.CreateProductDTO>) => {
|
}: Partial<ProductTypes.CreateProductDTO> & { tags: { value: string }[] }) => {
|
||||||
const defaultOptionTitle = "test-option"
|
const defaultOptionTitle = "test-option"
|
||||||
const defaultOptionValue = "test-value"
|
const defaultOptionValue = "test-value"
|
||||||
|
|
||||||
@@ -60,7 +64,7 @@ export const buildProductAndRelationsData = ({
|
|||||||
discountable: discountable ?? true,
|
discountable: discountable ?? true,
|
||||||
thumbnail: thumbnail as string,
|
thumbnail: thumbnail as string,
|
||||||
status: status ?? ProductStatus.PUBLISHED,
|
status: status ?? ProductStatus.PUBLISHED,
|
||||||
images: (images ?? []) as Image[],
|
images: (images ?? []) as ProductImage[],
|
||||||
type_id,
|
type_id,
|
||||||
tags: tags ?? [{ value: "tag-1" }],
|
tags: tags ?? [{ value: "tag-1" }],
|
||||||
collection_id,
|
collection_id,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export const productsData = [
|
|||||||
{
|
{
|
||||||
id: "test-1",
|
id: "test-1",
|
||||||
title: "product 1",
|
title: "product 1",
|
||||||
|
handle: "product-1",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
tags: [
|
tags: [
|
||||||
{
|
{
|
||||||
@@ -15,6 +16,7 @@ export const productsData = [
|
|||||||
{
|
{
|
||||||
id: "test-2",
|
id: "test-2",
|
||||||
title: "product",
|
title: "product",
|
||||||
|
handle: "product",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
tags: [
|
tags: [
|
||||||
{
|
{
|
||||||
@@ -26,6 +28,7 @@ export const productsData = [
|
|||||||
{
|
{
|
||||||
id: "test-3",
|
id: "test-3",
|
||||||
title: "product 3",
|
title: "product 3",
|
||||||
|
handle: "product-3",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
tags: [
|
tags: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { ProductStatus } from "@medusajs/framework/utils"
|
import {
|
||||||
|
kebabCase,
|
||||||
|
ProductStatus,
|
||||||
|
toMikroORMEntity,
|
||||||
|
} from "@medusajs/framework/utils"
|
||||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||||
import {
|
import {
|
||||||
Image,
|
Image,
|
||||||
@@ -10,6 +14,7 @@ import {
|
|||||||
} from "@models"
|
} from "@models"
|
||||||
|
|
||||||
import ProductOption from "../../../src/models/product-option"
|
import ProductOption from "../../../src/models/product-option"
|
||||||
|
import { InferEntityType } from "@medusajs/types"
|
||||||
|
|
||||||
export * from "./data/create-product"
|
export * from "./data/create-product"
|
||||||
|
|
||||||
@@ -24,7 +29,7 @@ export async function createProductAndTags(
|
|||||||
}[]
|
}[]
|
||||||
) {
|
) {
|
||||||
const products: any[] = data.map((productData) => {
|
const products: any[] = data.map((productData) => {
|
||||||
return manager.create(Product, productData)
|
return manager.create(toMikroORMEntity(Product), productData)
|
||||||
})
|
})
|
||||||
|
|
||||||
await manager.persistAndFlush(products)
|
await manager.persistAndFlush(products)
|
||||||
@@ -42,7 +47,7 @@ export async function createProductAndTypes(
|
|||||||
}[]
|
}[]
|
||||||
) {
|
) {
|
||||||
const products: any[] = data.map((productData) => {
|
const products: any[] = data.map((productData) => {
|
||||||
return manager.create(Product, productData)
|
return manager.create(toMikroORMEntity(Product), productData)
|
||||||
})
|
})
|
||||||
|
|
||||||
await manager.persistAndFlush(products)
|
await manager.persistAndFlush(products)
|
||||||
@@ -55,7 +60,7 @@ export async function createProductVariants(
|
|||||||
data: any[]
|
data: any[]
|
||||||
) {
|
) {
|
||||||
const variants: any[] = data.map((variantsData) => {
|
const variants: any[] = data.map((variantsData) => {
|
||||||
return manager.create(ProductVariant, variantsData)
|
return manager.create(toMikroORMEntity(ProductVariant), variantsData)
|
||||||
})
|
})
|
||||||
|
|
||||||
await manager.persistAndFlush(variants)
|
await manager.persistAndFlush(variants)
|
||||||
@@ -72,7 +77,10 @@ export async function createCollections(
|
|||||||
}[]
|
}[]
|
||||||
) {
|
) {
|
||||||
const collections: any[] = collectionData.map((collectionData) => {
|
const collections: any[] = collectionData.map((collectionData) => {
|
||||||
return manager.create(ProductCollection, collectionData)
|
if (!collectionData.handle && collectionData.title) {
|
||||||
|
collectionData.handle = kebabCase(collectionData.title)
|
||||||
|
}
|
||||||
|
return manager.create(toMikroORMEntity(ProductCollection), collectionData)
|
||||||
})
|
})
|
||||||
|
|
||||||
await manager.persistAndFlush(collections)
|
await manager.persistAndFlush(collections)
|
||||||
@@ -88,7 +96,7 @@ export async function createTypes(
|
|||||||
}[]
|
}[]
|
||||||
) {
|
) {
|
||||||
const types: any[] = typesData.map((typesData) => {
|
const types: any[] = typesData.map((typesData) => {
|
||||||
return manager.create(ProductType, typesData)
|
return manager.create(toMikroORMEntity(ProductType), typesData)
|
||||||
})
|
})
|
||||||
|
|
||||||
await manager.persistAndFlush(types)
|
await manager.persistAndFlush(types)
|
||||||
@@ -112,7 +120,7 @@ export async function createOptions(
|
|||||||
}[]
|
}[]
|
||||||
) {
|
) {
|
||||||
const options: any[] = optionsData.map((option) => {
|
const options: any[] = optionsData.map((option) => {
|
||||||
return manager.create(ProductOption, option)
|
return manager.create(toMikroORMEntity(ProductOption), option)
|
||||||
})
|
})
|
||||||
|
|
||||||
await manager.persistAndFlush(options)
|
await manager.persistAndFlush(options)
|
||||||
@@ -125,7 +133,7 @@ export async function createImages(
|
|||||||
imagesData: string[]
|
imagesData: string[]
|
||||||
) {
|
) {
|
||||||
const images: any[] = imagesData.map((img) => {
|
const images: any[] = imagesData.map((img) => {
|
||||||
return manager.create(Image, { url: img })
|
return manager.create(toMikroORMEntity(Image), { url: img })
|
||||||
})
|
})
|
||||||
|
|
||||||
await manager.persistAndFlush(images)
|
await manager.persistAndFlush(images)
|
||||||
@@ -135,8 +143,8 @@ export async function createImages(
|
|||||||
|
|
||||||
export async function assignCategoriesToProduct(
|
export async function assignCategoriesToProduct(
|
||||||
manager: SqlEntityManager,
|
manager: SqlEntityManager,
|
||||||
product: Product,
|
product: InferEntityType<Product>,
|
||||||
categories: ProductCategory[]
|
categories: InferEntityType<ProductCategory>[]
|
||||||
) {
|
) {
|
||||||
product.categories.add(categories)
|
product.categories.add(categories)
|
||||||
|
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
include_descendants_tree: true,
|
include_descendants_tree: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
select: ["id", "handle"],
|
select: ["id", "handle", "rank"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -190,12 +190,14 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
mpath: "category-0.category-1.category-1-a",
|
mpath: "category-0.category-1.category-1-a",
|
||||||
parent_category_id: "category-1",
|
parent_category_id: "category-1",
|
||||||
category_children: [],
|
category_children: [],
|
||||||
|
rank: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "category-1-b",
|
id: "category-1-b",
|
||||||
handle: "category-1-b",
|
handle: "category-1-b",
|
||||||
mpath: "category-0.category-1.category-1-b",
|
mpath: "category-0.category-1.category-1-b",
|
||||||
parent_category_id: "category-1",
|
parent_category_id: "category-1",
|
||||||
|
rank: 1,
|
||||||
category_children: [
|
category_children: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: "category-1-b-1",
|
id: "category-1-b-1",
|
||||||
@@ -220,7 +222,7 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
include_ancestors_tree: true,
|
include_ancestors_tree: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
select: ["id", "handle"],
|
select: ["id", "handle", "rank"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -235,8 +237,10 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
mpath:
|
mpath:
|
||||||
"electronics.computers.laptops.gaming-laptops.high-performance.4k-gaming",
|
"electronics.computers.laptops.gaming-laptops.high-performance.4k-gaming",
|
||||||
parent_category_id: "high-performance",
|
parent_category_id: "high-performance",
|
||||||
|
rank: 1,
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: "high-performance",
|
id: "high-performance",
|
||||||
|
rank: 1,
|
||||||
parent_category_id: "gaming-laptops",
|
parent_category_id: "gaming-laptops",
|
||||||
handle: "high-performance-gaming-laptops",
|
handle: "high-performance-gaming-laptops",
|
||||||
mpath:
|
mpath:
|
||||||
@@ -245,6 +249,7 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
id: "gaming-laptops",
|
id: "gaming-laptops",
|
||||||
handle: "gaming-laptops",
|
handle: "gaming-laptops",
|
||||||
mpath: "electronics.computers.laptops.gaming-laptops",
|
mpath: "electronics.computers.laptops.gaming-laptops",
|
||||||
|
rank: 0,
|
||||||
parent_category_id: "laptops",
|
parent_category_id: "laptops",
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: "laptops",
|
id: "laptops",
|
||||||
@@ -282,7 +287,7 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
include_descendants_tree: true,
|
include_descendants_tree: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
select: ["id", "handle"],
|
select: ["id", "handle", "rank"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -296,6 +301,7 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
handle: "gaming-laptops",
|
handle: "gaming-laptops",
|
||||||
mpath: "electronics.computers.laptops.gaming-laptops",
|
mpath: "electronics.computers.laptops.gaming-laptops",
|
||||||
parent_category_id: "laptops",
|
parent_category_id: "laptops",
|
||||||
|
rank: 0,
|
||||||
category_children: [
|
category_children: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: "budget-gaming",
|
id: "budget-gaming",
|
||||||
@@ -304,6 +310,7 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
"electronics.computers.laptops.gaming-laptops.budget-gaming",
|
"electronics.computers.laptops.gaming-laptops.budget-gaming",
|
||||||
parent_category_id: "gaming-laptops",
|
parent_category_id: "gaming-laptops",
|
||||||
category_children: [],
|
category_children: [],
|
||||||
|
rank: 0,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: "high-performance",
|
id: "high-performance",
|
||||||
@@ -311,6 +318,7 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
mpath:
|
mpath:
|
||||||
"electronics.computers.laptops.gaming-laptops.high-performance",
|
"electronics.computers.laptops.gaming-laptops.high-performance",
|
||||||
parent_category_id: "gaming-laptops",
|
parent_category_id: "gaming-laptops",
|
||||||
|
rank: 1,
|
||||||
category_children: expect.arrayContaining([
|
category_children: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: "vr-ready",
|
id: "vr-ready",
|
||||||
@@ -319,6 +327,7 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
"electronics.computers.laptops.gaming-laptops.high-performance.vr-ready",
|
"electronics.computers.laptops.gaming-laptops.high-performance.vr-ready",
|
||||||
parent_category_id: "high-performance",
|
parent_category_id: "high-performance",
|
||||||
category_children: [],
|
category_children: [],
|
||||||
|
rank: 0,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: "4k-gaming",
|
id: "4k-gaming",
|
||||||
@@ -327,6 +336,7 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
"electronics.computers.laptops.gaming-laptops.high-performance.4k-gaming",
|
"electronics.computers.laptops.gaming-laptops.high-performance.4k-gaming",
|
||||||
parent_category_id: "high-performance",
|
parent_category_id: "high-performance",
|
||||||
category_children: [],
|
category_children: [],
|
||||||
|
rank: 1,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
@@ -407,7 +417,7 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
include_ancestors_tree: true,
|
include_ancestors_tree: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
select: ["id", "handle"],
|
select: ["id", "handle", "rank"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -421,12 +431,15 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
handle: "category-1-a",
|
handle: "category-1-a",
|
||||||
mpath: "category-0.category-1.category-1-a",
|
mpath: "category-0.category-1.category-1-a",
|
||||||
parent_category_id: "category-1",
|
parent_category_id: "category-1",
|
||||||
|
rank: 0,
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: "category-1",
|
id: "category-1",
|
||||||
handle: "category-1",
|
handle: "category-1",
|
||||||
mpath: "category-0.category-1",
|
mpath: "category-0.category-1",
|
||||||
parent_category_id: "category-0",
|
parent_category_id: "category-0",
|
||||||
|
rank: 0,
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
|
rank: 0,
|
||||||
id: "category-0",
|
id: "category-0",
|
||||||
handle: "category-0",
|
handle: "category-0",
|
||||||
mpath: "category-0",
|
mpath: "category-0",
|
||||||
@@ -440,17 +453,20 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
handle: "category-1-b",
|
handle: "category-1-b",
|
||||||
mpath: "category-0.category-1.category-1-b",
|
mpath: "category-0.category-1.category-1-b",
|
||||||
parent_category_id: "category-1",
|
parent_category_id: "category-1",
|
||||||
|
rank: 1,
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: "category-1",
|
id: "category-1",
|
||||||
handle: "category-1",
|
handle: "category-1",
|
||||||
mpath: "category-0.category-1",
|
mpath: "category-0.category-1",
|
||||||
parent_category_id: "category-0",
|
parent_category_id: "category-0",
|
||||||
|
rank: 0,
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: "category-0",
|
id: "category-0",
|
||||||
handle: "category-0",
|
handle: "category-0",
|
||||||
mpath: "category-0",
|
mpath: "category-0",
|
||||||
parent_category_id: null,
|
parent_category_id: null,
|
||||||
parent_category: null,
|
parent_category: null,
|
||||||
|
rank: 0,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -465,7 +481,7 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
include_ancestors_tree: true,
|
include_ancestors_tree: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
select: ["id", "handle"],
|
select: ["id", "handle", "rank"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -475,17 +491,20 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
handle: "category-1-a",
|
handle: "category-1-a",
|
||||||
mpath: "category-0.category-1.category-1-a",
|
mpath: "category-0.category-1.category-1-a",
|
||||||
parent_category_id: "category-1",
|
parent_category_id: "category-1",
|
||||||
|
rank: 0,
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: "category-1",
|
id: "category-1",
|
||||||
handle: "category-1",
|
handle: "category-1",
|
||||||
mpath: "category-0.category-1",
|
mpath: "category-0.category-1",
|
||||||
parent_category_id: "category-0",
|
parent_category_id: "category-0",
|
||||||
|
rank: 0,
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: "category-0",
|
id: "category-0",
|
||||||
handle: "category-0",
|
handle: "category-0",
|
||||||
mpath: "category-0",
|
mpath: "category-0",
|
||||||
parent_category_id: null,
|
parent_category_id: null,
|
||||||
parent_category: null,
|
parent_category: null,
|
||||||
|
rank: 0,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
category_children: [],
|
category_children: [],
|
||||||
@@ -495,17 +514,20 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
handle: "category-1-b",
|
handle: "category-1-b",
|
||||||
mpath: "category-0.category-1.category-1-b",
|
mpath: "category-0.category-1.category-1-b",
|
||||||
parent_category_id: "category-1",
|
parent_category_id: "category-1",
|
||||||
|
rank: 1,
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: "category-1",
|
id: "category-1",
|
||||||
handle: "category-1",
|
handle: "category-1",
|
||||||
mpath: "category-0.category-1",
|
mpath: "category-0.category-1",
|
||||||
parent_category_id: "category-0",
|
parent_category_id: "category-0",
|
||||||
|
rank: 0,
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: "category-0",
|
id: "category-0",
|
||||||
handle: "category-0",
|
handle: "category-0",
|
||||||
mpath: "category-0",
|
mpath: "category-0",
|
||||||
parent_category_id: null,
|
parent_category_id: null,
|
||||||
parent_category: null,
|
parent_category: null,
|
||||||
|
rank: 0,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
category_children: [
|
category_children: [
|
||||||
@@ -514,6 +536,7 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
handle: "category-1-b-1",
|
handle: "category-1-b-1",
|
||||||
mpath: "category-0.category-1.category-1-b.category-1-b-1",
|
mpath: "category-0.category-1.category-1-b.category-1-b-1",
|
||||||
parent_category_id: "category-1-b",
|
parent_category_id: "category-1-b",
|
||||||
|
rank: 0,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -879,6 +902,7 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
await service.create([
|
await service.create([
|
||||||
{
|
{
|
||||||
name: "New Category",
|
name: "New Category",
|
||||||
|
handle: "new-category",
|
||||||
parent_category_id: null,
|
parent_category_id: null,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@@ -904,11 +928,13 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
await service.create([
|
await service.create([
|
||||||
{
|
{
|
||||||
name: "New Category",
|
name: "New Category",
|
||||||
|
handle: "new-category",
|
||||||
parent_category_id: null,
|
parent_category_id: null,
|
||||||
rank: 0,
|
rank: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "New Category 2",
|
name: "New Category 2",
|
||||||
|
handle: "new-category-2",
|
||||||
parent_category_id: null,
|
parent_category_id: null,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@@ -932,6 +958,7 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
await service.create([
|
await service.create([
|
||||||
{
|
{
|
||||||
name: "New Category 2.1",
|
name: "New Category 2.1",
|
||||||
|
handle: "new-category-2-1",
|
||||||
parent_category_id: productCategoryNew.id,
|
parent_category_id: productCategoryNew.id,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|||||||
+5
-2
@@ -5,6 +5,7 @@ import {
|
|||||||
Modules,
|
Modules,
|
||||||
ProductEvents,
|
ProductEvents,
|
||||||
ProductStatus,
|
ProductStatus,
|
||||||
|
toMikroORMEntity,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import { Product, ProductCategory } from "@models"
|
import { Product, ProductCategory } from "@models"
|
||||||
import {
|
import {
|
||||||
@@ -31,15 +32,17 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const testManager = await MikroOrmWrapper.forkManager()
|
const testManager = await MikroOrmWrapper.forkManager()
|
||||||
|
|
||||||
productOne = testManager.create(Product, {
|
productOne = testManager.create(toMikroORMEntity(Product), {
|
||||||
id: "product-1",
|
id: "product-1",
|
||||||
title: "product 1",
|
title: "product 1",
|
||||||
|
handle: "product-1",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
})
|
})
|
||||||
|
|
||||||
productTwo = testManager.create(Product, {
|
productTwo = testManager.create(toMikroORMEntity(Product), {
|
||||||
id: "product-2",
|
id: "product-2",
|
||||||
title: "product 2",
|
title: "product 2",
|
||||||
|
handle: "product-2",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -5,6 +5,7 @@ import {
|
|||||||
Modules,
|
Modules,
|
||||||
ProductEvents,
|
ProductEvents,
|
||||||
ProductStatus,
|
ProductStatus,
|
||||||
|
toMikroORMEntity,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import { Product, ProductCollection } from "@models"
|
import { Product, ProductCollection } from "@models"
|
||||||
import {
|
import {
|
||||||
@@ -31,15 +32,17 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const testManager = await MikroOrmWrapper.forkManager()
|
const testManager = await MikroOrmWrapper.forkManager()
|
||||||
|
|
||||||
productOne = testManager.create(Product, {
|
productOne = testManager.create(toMikroORMEntity(Product), {
|
||||||
id: "product-1",
|
id: "product-1",
|
||||||
title: "product 1",
|
title: "product 1",
|
||||||
|
handle: "product-1",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
})
|
})
|
||||||
|
|
||||||
productTwo = testManager.create(Product, {
|
productTwo = testManager.create(toMikroORMEntity(Product), {
|
||||||
id: "product-2",
|
id: "product-2",
|
||||||
title: "product 2",
|
title: "product 2",
|
||||||
|
handle: "product-2",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -47,11 +50,13 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
|||||||
{
|
{
|
||||||
id: "test-1",
|
id: "test-1",
|
||||||
title: "collection 1",
|
title: "collection 1",
|
||||||
|
handle: "collection-1",
|
||||||
products: [productOne],
|
products: [productOne],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "test-2",
|
id: "test-2",
|
||||||
title: "collection",
|
title: "collection",
|
||||||
|
handle: "collection",
|
||||||
products: [productTwo],
|
products: [productTwo],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
+12
-6
@@ -1,5 +1,9 @@
|
|||||||
import { IProductModuleService } from "@medusajs/framework/types"
|
import { IProductModuleService } from "@medusajs/framework/types"
|
||||||
import { Modules, ProductStatus } from "@medusajs/framework/utils"
|
import {
|
||||||
|
Modules,
|
||||||
|
ProductStatus,
|
||||||
|
toMikroORMEntity,
|
||||||
|
} from "@medusajs/framework/utils"
|
||||||
import { Product, ProductOption } from "@models"
|
import { Product, ProductOption } from "@models"
|
||||||
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
|
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
|
||||||
|
|
||||||
@@ -16,25 +20,27 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const testManager = await MikroOrmWrapper.forkManager()
|
const testManager = await MikroOrmWrapper.forkManager()
|
||||||
productOne = testManager.create(Product, {
|
productOne = testManager.create(toMikroORMEntity(Product), {
|
||||||
id: "product-1",
|
id: "product-1",
|
||||||
title: "product 1",
|
title: "product 1",
|
||||||
|
handle: "product-1",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
})
|
})
|
||||||
|
|
||||||
productTwo = testManager.create(Product, {
|
productTwo = testManager.create(toMikroORMEntity(Product), {
|
||||||
id: "product-2",
|
id: "product-2",
|
||||||
title: "product 2",
|
title: "product 2",
|
||||||
|
handle: "product-2",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
})
|
})
|
||||||
|
|
||||||
optionOne = testManager.create(ProductOption, {
|
optionOne = testManager.create(toMikroORMEntity(ProductOption), {
|
||||||
id: "option-1",
|
id: "option-1",
|
||||||
title: "option 1",
|
title: "option 1",
|
||||||
product: productOne,
|
product: productOne,
|
||||||
})
|
})
|
||||||
|
|
||||||
optionTwo = testManager.create(ProductOption, {
|
optionTwo = testManager.create(toMikroORMEntity(ProductOption), {
|
||||||
id: "option-2",
|
id: "option-2",
|
||||||
title: "option 1",
|
title: "option 1",
|
||||||
product: productTwo,
|
product: productTwo,
|
||||||
@@ -198,7 +204,7 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
|||||||
|
|
||||||
it("should return requested attributes when requested through config", async () => {
|
it("should return requested attributes when requested through config", async () => {
|
||||||
const option = await service.retrieveProductOption(optionOne.id, {
|
const option = await service.retrieveProductOption(optionOne.id, {
|
||||||
select: ["id", "product.title"],
|
select: ["id", "product.handle", "product.title"],
|
||||||
relations: ["product"],
|
relations: ["product"],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+7
-4
@@ -5,6 +5,7 @@ import {
|
|||||||
Modules,
|
Modules,
|
||||||
ProductEvents,
|
ProductEvents,
|
||||||
ProductStatus,
|
ProductStatus,
|
||||||
|
toMikroORMEntity,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import { Product, ProductTag } from "@models"
|
import { Product, ProductTag } from "@models"
|
||||||
import {
|
import {
|
||||||
@@ -35,25 +36,27 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const testManager = await MikroOrmWrapper.forkManager()
|
const testManager = await MikroOrmWrapper.forkManager()
|
||||||
productOne = testManager.create(Product, {
|
productOne = testManager.create(toMikroORMEntity(Product), {
|
||||||
id: "product-1",
|
id: "product-1",
|
||||||
title: "product 1",
|
title: "product 1",
|
||||||
|
handle: "product-1",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
})
|
})
|
||||||
|
|
||||||
productTwo = testManager.create(Product, {
|
productTwo = testManager.create(toMikroORMEntity(Product), {
|
||||||
id: "product-2",
|
id: "product-2",
|
||||||
title: "product 2",
|
title: "product 2",
|
||||||
|
handle: "product-2",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
})
|
})
|
||||||
|
|
||||||
tagOne = testManager.create(ProductTag, {
|
tagOne = testManager.create(toMikroORMEntity(ProductTag), {
|
||||||
id: "tag-1",
|
id: "tag-1",
|
||||||
value: "tag 1",
|
value: "tag 1",
|
||||||
products: [productOne],
|
products: [productOne],
|
||||||
})
|
})
|
||||||
|
|
||||||
tagTwo = testManager.create(ProductTag, {
|
tagTwo = testManager.create(toMikroORMEntity(ProductTag), {
|
||||||
id: "tag-2",
|
id: "tag-2",
|
||||||
value: "tag",
|
value: "tag",
|
||||||
products: [productTwo],
|
products: [productTwo],
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
import { IProductModuleService } from "@medusajs/framework/types"
|
import { IProductModuleService } from "@medusajs/framework/types"
|
||||||
import { ProductType } from "@models"
|
import { ProductType } from "@models"
|
||||||
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
|
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
import { Modules, toMikroORMEntity } from "@medusajs/framework/utils"
|
||||||
|
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
@@ -15,12 +15,12 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const testManager = await MikroOrmWrapper.forkManager()
|
const testManager = await MikroOrmWrapper.forkManager()
|
||||||
|
|
||||||
typeOne = testManager.create(ProductType, {
|
typeOne = testManager.create(toMikroORMEntity(ProductType), {
|
||||||
id: "type-1",
|
id: "type-1",
|
||||||
value: "type 1",
|
value: "type 1",
|
||||||
})
|
})
|
||||||
|
|
||||||
typeTwo = testManager.create(ProductType, {
|
typeTwo = testManager.create(toMikroORMEntity(ProductType), {
|
||||||
id: "type-2",
|
id: "type-2",
|
||||||
value: "type",
|
value: "type",
|
||||||
})
|
})
|
||||||
|
|||||||
+9
-10
@@ -125,7 +125,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
|||||||
productCategoryTwo = categories[1]
|
productCategoryTwo = categories[1]
|
||||||
|
|
||||||
productOne = service.createProducts({
|
productOne = service.createProducts({
|
||||||
id: "product-1",
|
|
||||||
title: "product 1",
|
title: "product 1",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
options: [
|
options: [
|
||||||
@@ -136,7 +135,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
|||||||
],
|
],
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
id: "variant-1",
|
|
||||||
title: "variant 1",
|
title: "variant 1",
|
||||||
options: { "opt-title": "val-1" },
|
options: { "opt-title": "val-1" },
|
||||||
},
|
},
|
||||||
@@ -144,12 +142,11 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
|||||||
})
|
})
|
||||||
|
|
||||||
productTwo = service.createProducts({
|
productTwo = service.createProducts({
|
||||||
id: "product-2",
|
|
||||||
title: "product 2",
|
title: "product 2",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
categories: [{ id: productCategoryOne.id }],
|
|
||||||
collection_id: productCollectionOne.id,
|
collection_id: productCollectionOne.id,
|
||||||
tags: [{ id: tags[0].id }],
|
category_ids: [productCategoryOne.id],
|
||||||
|
tag_ids: [tags[0].id],
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
title: "size",
|
title: "size",
|
||||||
@@ -162,7 +159,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
|||||||
],
|
],
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
id: "variant-2",
|
|
||||||
title: "variant 2",
|
title: "variant 2",
|
||||||
options: {
|
options: {
|
||||||
size: "large",
|
size: "large",
|
||||||
@@ -170,7 +166,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "variant-3",
|
|
||||||
title: "variant 3",
|
title: "variant 3",
|
||||||
options: {
|
options: {
|
||||||
size: "small",
|
size: "small",
|
||||||
@@ -389,11 +384,11 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
|||||||
|
|
||||||
const existingVariant1 = product.variants.find(
|
const existingVariant1 = product.variants.find(
|
||||||
(v) => v.title === "new variant 1"
|
(v) => v.title === "new variant 1"
|
||||||
)
|
)!
|
||||||
|
|
||||||
const existingVariant2 = product.variants.find(
|
const existingVariant2 = product.variants.find(
|
||||||
(v) => v.title === "new variant 2"
|
(v) => v.title === "new variant 2"
|
||||||
)
|
)!
|
||||||
|
|
||||||
await service.upsertProductVariants([
|
await service.upsertProductVariants([
|
||||||
{
|
{
|
||||||
@@ -793,10 +788,14 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should do a partial update on the options of a variant successfully", async () => {
|
it("should do a partial update on the options of a variant successfully", async () => {
|
||||||
|
const variantToUpdate = productTwo.variants.find(
|
||||||
|
(variant) => variant.title === "variant 3"
|
||||||
|
)!
|
||||||
|
|
||||||
await service.updateProducts(productTwo.id, {
|
await service.updateProducts(productTwo.id, {
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
id: "variant-3",
|
id: variantToUpdate.id,
|
||||||
options: { size: "small", color: "blue" },
|
options: { size: "small", color: "blue" },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -7,12 +7,17 @@ import {
|
|||||||
createProductVariants,
|
createProductVariants,
|
||||||
} from "../__fixtures__/product"
|
} from "../__fixtures__/product"
|
||||||
|
|
||||||
import { IProductModuleService, ProductDTO } from "@medusajs/framework/types"
|
import {
|
||||||
|
InferEntityType,
|
||||||
|
IProductModuleService,
|
||||||
|
ProductDTO,
|
||||||
|
} from "@medusajs/framework/types"
|
||||||
import {
|
import {
|
||||||
Module,
|
Module,
|
||||||
Modules,
|
Modules,
|
||||||
ProductStatus,
|
ProductStatus,
|
||||||
kebabCase,
|
kebabCase,
|
||||||
|
toMikroORMEntity,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
|
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
|
||||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||||
@@ -27,7 +32,7 @@ import {
|
|||||||
variantsData,
|
variantsData,
|
||||||
} from "../__fixtures__/product/data"
|
} from "../__fixtures__/product/data"
|
||||||
|
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(300000)
|
||||||
|
|
||||||
type Service = IProductModuleService & {
|
type Service = IProductModuleService & {
|
||||||
productService_: ProductService
|
productService_: ProductService
|
||||||
@@ -50,17 +55,19 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
service: ProductModuleService,
|
service: ProductModuleService,
|
||||||
}).linkable
|
}).linkable
|
||||||
|
|
||||||
expect(Object.keys(linkable)).toEqual([
|
expect(Object.keys(linkable)).toHaveLength(8)
|
||||||
"product",
|
expect(Object.keys(linkable)).toEqual(
|
||||||
"productVariant",
|
expect.arrayContaining([
|
||||||
"productOption",
|
"product",
|
||||||
"productOptionValue",
|
"productVariant",
|
||||||
"productType",
|
"productOption",
|
||||||
"productImage",
|
"productOptionValue",
|
||||||
"productTag",
|
"productType",
|
||||||
"productCollection",
|
"productTag",
|
||||||
"productCategory",
|
"productCollection",
|
||||||
])
|
"productCategory",
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
Object.keys(linkable).forEach((key) => {
|
Object.keys(linkable).forEach((key) => {
|
||||||
delete linkable[key].toJSON
|
delete linkable[key].toJSON
|
||||||
@@ -119,15 +126,6 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
field: "productType",
|
field: "productType",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
productImage: {
|
|
||||||
id: {
|
|
||||||
linkable: "product_image_id",
|
|
||||||
entity: "ProductImage",
|
|
||||||
primaryKey: "id",
|
|
||||||
serviceName: "product",
|
|
||||||
field: "productImage",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
productTag: {
|
productTag: {
|
||||||
id: {
|
id: {
|
||||||
linkable: "product_tag_id",
|
linkable: "product_tag_id",
|
||||||
@@ -160,16 +158,17 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
|
|
||||||
describe("Product Service", () => {
|
describe("Product Service", () => {
|
||||||
let testManager: SqlEntityManager
|
let testManager: SqlEntityManager
|
||||||
let products!: Product[]
|
let products!: InferEntityType<typeof Product>[]
|
||||||
let productOne: Product
|
let productOne: InferEntityType<typeof Product>
|
||||||
let categories!: ProductCategory[]
|
let categories!: InferEntityType<typeof ProductCategory>[]
|
||||||
|
|
||||||
describe("retrieve", () => {
|
describe("retrieve", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
testManager = await MikroOrmWrapper.forkManager()
|
testManager = await MikroOrmWrapper.forkManager()
|
||||||
productOne = testManager.create(Product, {
|
productOne = testManager.create(toMikroORMEntity(Product), {
|
||||||
id: "product-1",
|
id: "product-1",
|
||||||
title: "product 1",
|
title: "product 1",
|
||||||
|
handle: "product-1",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -243,9 +242,10 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
testManager = await MikroOrmWrapper.forkManager()
|
testManager = await MikroOrmWrapper.forkManager()
|
||||||
|
|
||||||
productOne = testManager.create(Product, {
|
productOne = testManager.create(toMikroORMEntity(Product), {
|
||||||
id: "product-1",
|
id: "product-1",
|
||||||
title: "product 1",
|
title: "product 1",
|
||||||
|
handle: "product-1",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -435,23 +435,23 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("relation: categories", () => {
|
describe("relation: categories", () => {
|
||||||
let workingProduct: Product
|
let workingProduct: InferEntityType<typeof Product>
|
||||||
let workingCategory: ProductCategory
|
let workingCategory: InferEntityType<typeof ProductCategory>
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
testManager = await MikroOrmWrapper.forkManager()
|
testManager = await MikroOrmWrapper.forkManager()
|
||||||
|
|
||||||
products = await createProductAndTags(testManager, productsData)
|
products = await createProductAndTags(testManager, productsData)
|
||||||
workingProduct = products.find((p) => p.id === "test-1") as Product
|
workingProduct = products.find((p) => p.id === "test-1")!
|
||||||
categories = []
|
categories = []
|
||||||
for (const entry of categoriesData) {
|
for (const entry of categoriesData) {
|
||||||
categories.push((await categoryService.create([entry]))[0])
|
categories.push((await categoryService.create([entry]))[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
workingCategory = (await testManager.findOne(
|
workingCategory = (await testManager.findOne(
|
||||||
ProductCategory,
|
toMikroORMEntity(ProductCategory),
|
||||||
"category-1"
|
"category-1"
|
||||||
)) as ProductCategory
|
))!
|
||||||
|
|
||||||
workingProduct = await assignCategoriesToProduct(
|
workingProduct = await assignCategoriesToProduct(
|
||||||
testManager,
|
testManager,
|
||||||
@@ -477,9 +477,7 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const product = products.find(
|
const product = products.find((p) => p.id === workingProduct.id)!
|
||||||
(p) => p.id === workingProduct.id
|
|
||||||
) as unknown as Product
|
|
||||||
|
|
||||||
expect(product).toEqual(
|
expect(product).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@@ -534,32 +532,34 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("relation: collections", () => {
|
describe("relation: collections", () => {
|
||||||
let workingProduct: Product
|
let workingProduct: InferEntityType<typeof Product>
|
||||||
let workingProductTwo: Product
|
let workingProductTwo: InferEntityType<typeof Product>
|
||||||
let workingCollection: ProductCollection
|
let workingCollection: InferEntityType<typeof ProductCollection>
|
||||||
let workingCollectionTwo: ProductCollection
|
let workingCollectionTwo: InferEntityType<typeof ProductCollection>
|
||||||
const collectionData = [
|
const collectionData = [
|
||||||
{
|
{
|
||||||
id: "test-1",
|
id: "test-1",
|
||||||
title: "col 1",
|
title: "col 1",
|
||||||
|
handle: "col-1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "test-2",
|
id: "test-2",
|
||||||
title: "col 2",
|
title: "col 2",
|
||||||
|
handle: "col-2",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
testManager = await MikroOrmWrapper.forkManager()
|
testManager = await MikroOrmWrapper.forkManager()
|
||||||
await createCollections(testManager, collectionData)
|
await createCollections(testManager, collectionData)
|
||||||
workingCollection = (await testManager.findOne(
|
workingCollection = await testManager.findOne(
|
||||||
ProductCollection,
|
toMikroORMEntity(ProductCollection),
|
||||||
"test-1"
|
"test-1"
|
||||||
)) as ProductCollection
|
)
|
||||||
workingCollectionTwo = (await testManager.findOne(
|
workingCollectionTwo = (await testManager.findOne(
|
||||||
ProductCollection,
|
toMikroORMEntity(ProductCollection),
|
||||||
"test-2"
|
"test-2"
|
||||||
)) as ProductCollection
|
))!
|
||||||
|
|
||||||
products = await createProductAndTags(testManager, [
|
products = await createProductAndTags(testManager, [
|
||||||
{
|
{
|
||||||
@@ -575,10 +575,8 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
workingProduct = products.find((p) => p.id === "test-1") as Product
|
workingProduct = products.find((p) => p.id === "test-1")!
|
||||||
workingProductTwo = products.find(
|
workingProductTwo = products.find((p) => p.id === "test-2")!
|
||||||
(p) => p.id === "test-2"
|
|
||||||
) as Product
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should filter by collection relation and scope fields", async () => {
|
it("should filter by collection relation and scope fields", async () => {
|
||||||
@@ -588,7 +586,12 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
collection_id: workingCollection.id,
|
collection_id: workingCollection.id,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
select: ["title", "collection.title"],
|
select: [
|
||||||
|
"title",
|
||||||
|
"handle",
|
||||||
|
"collection.title",
|
||||||
|
"collection.handle",
|
||||||
|
],
|
||||||
relations: ["collection"],
|
relations: ["collection"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -618,7 +621,12 @@ moduleIntegrationTestRunner<Service>({
|
|||||||
collection_id: [workingCollection.id, workingCollectionTwo.id],
|
collection_id: [workingCollection.id, workingCollectionTwo.id],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
select: ["title", "collection.title"],
|
select: [
|
||||||
|
"title",
|
||||||
|
"handle",
|
||||||
|
"collection.title",
|
||||||
|
"collection.handle",
|
||||||
|
],
|
||||||
relations: ["collection"],
|
relations: ["collection"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
ProductType,
|
ProductType,
|
||||||
ProductVariant,
|
ProductVariant,
|
||||||
} from "@models"
|
} from "@models"
|
||||||
import ProductImage from "./models/product-image"
|
|
||||||
import { default as schema } from "./schema"
|
import { default as schema } from "./schema"
|
||||||
|
|
||||||
export const joinerConfig = defineJoinerConfig(Modules.PRODUCT, {
|
export const joinerConfig = defineJoinerConfig(Modules.PRODUCT, {
|
||||||
@@ -20,7 +19,6 @@ export const joinerConfig = defineJoinerConfig(Modules.PRODUCT, {
|
|||||||
ProductOption,
|
ProductOption,
|
||||||
ProductOptionValue,
|
ProductOptionValue,
|
||||||
ProductType,
|
ProductType,
|
||||||
ProductImage,
|
|
||||||
ProductTag,
|
ProductTag,
|
||||||
ProductCollection,
|
ProductCollection,
|
||||||
ProductCategory,
|
ProductCategory,
|
||||||
|
|||||||
@@ -82,6 +82,15 @@
|
|||||||
"default": "0",
|
"default": "0",
|
||||||
"mappedType": "integer"
|
"mappedType": "integer"
|
||||||
},
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "metadata",
|
||||||
|
"type": "jsonb",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": true,
|
||||||
|
"mappedType": "json"
|
||||||
|
},
|
||||||
"parent_category_id": {
|
"parent_category_id": {
|
||||||
"name": "parent_category_id",
|
"name": "parent_category_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -122,28 +131,42 @@
|
|||||||
"nullable": true,
|
"nullable": true,
|
||||||
"length": 6,
|
"length": 6,
|
||||||
"mappedType": "datetime"
|
"mappedType": "datetime"
|
||||||
},
|
|
||||||
"metadata": {
|
|
||||||
"name": "metadata",
|
|
||||||
"type": "jsonb",
|
|
||||||
"unsigned": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"primary": false,
|
|
||||||
"nullable": true,
|
|
||||||
"mappedType": "json"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "product_category",
|
"name": "product_category",
|
||||||
"schema": "public",
|
"schema": "public",
|
||||||
"indexes": [
|
"indexes": [
|
||||||
{
|
{
|
||||||
"columnNames": [
|
"keyName": "IDX_product_category_parent_category_id",
|
||||||
"deleted_at"
|
"columnNames": [],
|
||||||
],
|
|
||||||
"composite": false,
|
"composite": false,
|
||||||
"keyName": "IDX_product_category_deleted_at",
|
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_category_parent_category_id\" ON \"product_category\" (parent_category_id) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_product_category_deleted_at",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_category_deleted_at\" ON \"product_category\" (deleted_at) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_product_category_path",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_category_path\" ON \"product_category\" (mpath) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_category_handle_unique",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_category_handle_unique\" ON \"product_category\" (handle) WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "product_category_pkey",
|
"keyName": "product_category_pkey",
|
||||||
@@ -247,13 +270,20 @@
|
|||||||
"schema": "public",
|
"schema": "public",
|
||||||
"indexes": [
|
"indexes": [
|
||||||
{
|
{
|
||||||
"columnNames": [
|
|
||||||
"deleted_at"
|
|
||||||
],
|
|
||||||
"composite": false,
|
|
||||||
"keyName": "IDX_product_collection_deleted_at",
|
"keyName": "IDX_product_collection_deleted_at",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_collection_deleted_at\" ON \"product_collection\" (deleted_at) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_collection_handle_unique",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_collection_handle_unique\" ON \"product_collection\" (handle) WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "product_collection_pkey",
|
"keyName": "product_collection_pkey",
|
||||||
@@ -334,13 +364,20 @@
|
|||||||
"schema": "public",
|
"schema": "public",
|
||||||
"indexes": [
|
"indexes": [
|
||||||
{
|
{
|
||||||
"columnNames": [
|
|
||||||
"deleted_at"
|
|
||||||
],
|
|
||||||
"composite": false,
|
|
||||||
"keyName": "IDX_product_tag_deleted_at",
|
"keyName": "IDX_product_tag_deleted_at",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_tag_deleted_at\" ON \"product_tag\" (deleted_at) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_tag_value_unique",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_tag_value_unique\" ON \"product_tag\" (value) WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "product_tag_pkey",
|
"keyName": "product_tag_pkey",
|
||||||
@@ -377,7 +414,7 @@
|
|||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "metadata",
|
"name": "metadata",
|
||||||
"type": "json",
|
"type": "jsonb",
|
||||||
"unsigned": false,
|
"unsigned": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
@@ -421,13 +458,20 @@
|
|||||||
"schema": "public",
|
"schema": "public",
|
||||||
"indexes": [
|
"indexes": [
|
||||||
{
|
{
|
||||||
"columnNames": [
|
|
||||||
"deleted_at"
|
|
||||||
],
|
|
||||||
"composite": false,
|
|
||||||
"keyName": "IDX_product_type_deleted_at",
|
"keyName": "IDX_product_type_deleted_at",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_type_deleted_at\" ON \"product_type\" (deleted_at) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_type_value_unique",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_type_value_unique\" ON \"product_type\" (value) WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "product_type_pkey",
|
"keyName": "product_type_pkey",
|
||||||
@@ -506,6 +550,7 @@
|
|||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
|
"default": "'draft'",
|
||||||
"enumItems": [
|
"enumItems": [
|
||||||
"draft",
|
"draft",
|
||||||
"proposed",
|
"proposed",
|
||||||
@@ -595,24 +640,6 @@
|
|||||||
"nullable": true,
|
"nullable": true,
|
||||||
"mappedType": "text"
|
"mappedType": "text"
|
||||||
},
|
},
|
||||||
"collection_id": {
|
|
||||||
"name": "collection_id",
|
|
||||||
"type": "text",
|
|
||||||
"unsigned": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"primary": false,
|
|
||||||
"nullable": true,
|
|
||||||
"mappedType": "text"
|
|
||||||
},
|
|
||||||
"type_id": {
|
|
||||||
"name": "type_id",
|
|
||||||
"type": "text",
|
|
||||||
"unsigned": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"primary": false,
|
|
||||||
"nullable": true,
|
|
||||||
"mappedType": "text"
|
|
||||||
},
|
|
||||||
"discountable": {
|
"discountable": {
|
||||||
"name": "discountable",
|
"name": "discountable",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
@@ -632,6 +659,33 @@
|
|||||||
"nullable": true,
|
"nullable": true,
|
||||||
"mappedType": "text"
|
"mappedType": "text"
|
||||||
},
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "metadata",
|
||||||
|
"type": "jsonb",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": true,
|
||||||
|
"mappedType": "json"
|
||||||
|
},
|
||||||
|
"type_id": {
|
||||||
|
"name": "type_id",
|
||||||
|
"type": "text",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": true,
|
||||||
|
"mappedType": "text"
|
||||||
|
},
|
||||||
|
"collection_id": {
|
||||||
|
"name": "collection_id",
|
||||||
|
"type": "text",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": true,
|
||||||
|
"mappedType": "text"
|
||||||
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"name": "created_at",
|
"name": "created_at",
|
||||||
"type": "timestamptz",
|
"type": "timestamptz",
|
||||||
@@ -663,28 +717,42 @@
|
|||||||
"nullable": true,
|
"nullable": true,
|
||||||
"length": 6,
|
"length": 6,
|
||||||
"mappedType": "datetime"
|
"mappedType": "datetime"
|
||||||
},
|
|
||||||
"metadata": {
|
|
||||||
"name": "metadata",
|
|
||||||
"type": "jsonb",
|
|
||||||
"unsigned": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"primary": false,
|
|
||||||
"nullable": true,
|
|
||||||
"mappedType": "json"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "product",
|
"name": "product",
|
||||||
"schema": "public",
|
"schema": "public",
|
||||||
"indexes": [
|
"indexes": [
|
||||||
{
|
{
|
||||||
"columnNames": [
|
"keyName": "IDX_product_type_id",
|
||||||
"deleted_at"
|
"columnNames": [],
|
||||||
],
|
|
||||||
"composite": false,
|
"composite": false,
|
||||||
"keyName": "IDX_product_deleted_at",
|
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_type_id\" ON \"product\" (type_id) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_product_collection_id",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_collection_id\" ON \"product\" (collection_id) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_product_deleted_at",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_deleted_at\" ON \"product\" (deleted_at) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_product_handle_unique",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_product_handle_unique\" ON \"product\" (handle) WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "product_pkey",
|
"keyName": "product_pkey",
|
||||||
@@ -698,19 +766,6 @@
|
|||||||
],
|
],
|
||||||
"checks": [],
|
"checks": [],
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"product_collection_id_foreign": {
|
|
||||||
"constraintName": "product_collection_id_foreign",
|
|
||||||
"columnNames": [
|
|
||||||
"collection_id"
|
|
||||||
],
|
|
||||||
"localTableName": "public.product",
|
|
||||||
"referencedColumnNames": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"referencedTableName": "public.product_collection",
|
|
||||||
"deleteRule": "set null",
|
|
||||||
"updateRule": "cascade"
|
|
||||||
},
|
|
||||||
"product_type_id_foreign": {
|
"product_type_id_foreign": {
|
||||||
"constraintName": "product_type_id_foreign",
|
"constraintName": "product_type_id_foreign",
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
@@ -723,6 +778,19 @@
|
|||||||
"referencedTableName": "public.product_type",
|
"referencedTableName": "public.product_type",
|
||||||
"deleteRule": "set null",
|
"deleteRule": "set null",
|
||||||
"updateRule": "cascade"
|
"updateRule": "cascade"
|
||||||
|
},
|
||||||
|
"product_collection_id_foreign": {
|
||||||
|
"constraintName": "product_collection_id_foreign",
|
||||||
|
"columnNames": [
|
||||||
|
"collection_id"
|
||||||
|
],
|
||||||
|
"localTableName": "public.product",
|
||||||
|
"referencedColumnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"referencedTableName": "public.product_collection",
|
||||||
|
"deleteRule": "set null",
|
||||||
|
"updateRule": "cascade"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -746,15 +814,6 @@
|
|||||||
"nullable": false,
|
"nullable": false,
|
||||||
"mappedType": "text"
|
"mappedType": "text"
|
||||||
},
|
},
|
||||||
"product_id": {
|
|
||||||
"name": "product_id",
|
|
||||||
"type": "text",
|
|
||||||
"unsigned": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"primary": false,
|
|
||||||
"nullable": true,
|
|
||||||
"mappedType": "text"
|
|
||||||
},
|
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "metadata",
|
"name": "metadata",
|
||||||
"type": "jsonb",
|
"type": "jsonb",
|
||||||
@@ -764,6 +823,15 @@
|
|||||||
"nullable": true,
|
"nullable": true,
|
||||||
"mappedType": "json"
|
"mappedType": "json"
|
||||||
},
|
},
|
||||||
|
"product_id": {
|
||||||
|
"name": "product_id",
|
||||||
|
"type": "text",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": false,
|
||||||
|
"mappedType": "text"
|
||||||
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"name": "created_at",
|
"name": "created_at",
|
||||||
"type": "timestamptz",
|
"type": "timestamptz",
|
||||||
@@ -801,13 +869,28 @@
|
|||||||
"schema": "public",
|
"schema": "public",
|
||||||
"indexes": [
|
"indexes": [
|
||||||
{
|
{
|
||||||
"columnNames": [
|
"keyName": "IDX_product_option_product_id",
|
||||||
"deleted_at"
|
"columnNames": [],
|
||||||
],
|
|
||||||
"composite": false,
|
"composite": false,
|
||||||
"keyName": "IDX_product_option_deleted_at",
|
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_option_product_id\" ON \"product_option\" (product_id) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_product_option_deleted_at",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_option_deleted_at\" ON \"product_option\" (deleted_at) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_option_product_id_title_unique",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_option_product_id_title_unique\" ON \"product_option\" (product_id, title) WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "product_option_pkey",
|
"keyName": "product_option_pkey",
|
||||||
@@ -856,15 +939,6 @@
|
|||||||
"nullable": false,
|
"nullable": false,
|
||||||
"mappedType": "text"
|
"mappedType": "text"
|
||||||
},
|
},
|
||||||
"option_id": {
|
|
||||||
"name": "option_id",
|
|
||||||
"type": "text",
|
|
||||||
"unsigned": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"primary": false,
|
|
||||||
"nullable": true,
|
|
||||||
"mappedType": "text"
|
|
||||||
},
|
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "metadata",
|
"name": "metadata",
|
||||||
"type": "jsonb",
|
"type": "jsonb",
|
||||||
@@ -874,6 +948,15 @@
|
|||||||
"nullable": true,
|
"nullable": true,
|
||||||
"mappedType": "json"
|
"mappedType": "json"
|
||||||
},
|
},
|
||||||
|
"option_id": {
|
||||||
|
"name": "option_id",
|
||||||
|
"type": "text",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": true,
|
||||||
|
"mappedType": "text"
|
||||||
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"name": "created_at",
|
"name": "created_at",
|
||||||
"type": "timestamptz",
|
"type": "timestamptz",
|
||||||
@@ -911,13 +994,28 @@
|
|||||||
"schema": "public",
|
"schema": "public",
|
||||||
"indexes": [
|
"indexes": [
|
||||||
{
|
{
|
||||||
"columnNames": [
|
"keyName": "IDX_product_option_value_option_id",
|
||||||
"deleted_at"
|
"columnNames": [],
|
||||||
],
|
|
||||||
"composite": false,
|
"composite": false,
|
||||||
"keyName": "IDX_product_option_value_deleted_at",
|
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_option_value_option_id\" ON \"product_option_value\" (option_id) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_product_option_value_deleted_at",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_option_value_deleted_at\" ON \"product_option_value\" (deleted_at) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_option_value_option_id_unique",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_option_value_option_id_unique\" ON \"product_option_value\" (option_id, value) WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "product_option_value_pkey",
|
"keyName": "product_option_value_pkey",
|
||||||
@@ -975,6 +1073,25 @@
|
|||||||
"nullable": true,
|
"nullable": true,
|
||||||
"mappedType": "json"
|
"mappedType": "json"
|
||||||
},
|
},
|
||||||
|
"rank": {
|
||||||
|
"name": "rank",
|
||||||
|
"type": "integer",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": false,
|
||||||
|
"default": "0",
|
||||||
|
"mappedType": "integer"
|
||||||
|
},
|
||||||
|
"product_id": {
|
||||||
|
"name": "product_id",
|
||||||
|
"type": "text",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": false,
|
||||||
|
"mappedType": "text"
|
||||||
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"name": "created_at",
|
"name": "created_at",
|
||||||
"type": "timestamptz",
|
"type": "timestamptz",
|
||||||
@@ -1006,38 +1123,34 @@
|
|||||||
"nullable": true,
|
"nullable": true,
|
||||||
"length": 6,
|
"length": 6,
|
||||||
"mappedType": "datetime"
|
"mappedType": "datetime"
|
||||||
},
|
|
||||||
"rank": {
|
|
||||||
"name": "rank",
|
|
||||||
"type": "integer",
|
|
||||||
"unsigned": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"primary": false,
|
|
||||||
"nullable": false,
|
|
||||||
"default": "0",
|
|
||||||
"mappedType": "integer"
|
|
||||||
},
|
|
||||||
"product_id": {
|
|
||||||
"name": "product_id",
|
|
||||||
"type": "text",
|
|
||||||
"unsigned": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"primary": false,
|
|
||||||
"nullable": false,
|
|
||||||
"mappedType": "text"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "image",
|
"name": "image",
|
||||||
"schema": "public",
|
"schema": "public",
|
||||||
"indexes": [
|
"indexes": [
|
||||||
{
|
{
|
||||||
"columnNames": [
|
"keyName": "IDX_image_product_id",
|
||||||
"deleted_at"
|
"columnNames": [],
|
||||||
],
|
|
||||||
"composite": false,
|
"composite": false,
|
||||||
"keyName": "IDX_product_image_deleted_at",
|
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_image_product_id\" ON \"image\" (product_id) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_image_deleted_at",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_image_deleted_at\" ON \"image\" (deleted_at) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_product_image_url",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_image_url\" ON \"image\" (url) WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "image_pkey",
|
"keyName": "image_pkey",
|
||||||
@@ -1310,39 +1423,39 @@
|
|||||||
},
|
},
|
||||||
"weight": {
|
"weight": {
|
||||||
"name": "weight",
|
"name": "weight",
|
||||||
"type": "numeric",
|
"type": "integer",
|
||||||
"unsigned": false,
|
"unsigned": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"mappedType": "decimal"
|
"mappedType": "integer"
|
||||||
},
|
},
|
||||||
"length": {
|
"length": {
|
||||||
"name": "length",
|
"name": "length",
|
||||||
"type": "numeric",
|
"type": "integer",
|
||||||
"unsigned": false,
|
"unsigned": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"mappedType": "decimal"
|
"mappedType": "integer"
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
"name": "height",
|
"name": "height",
|
||||||
"type": "numeric",
|
"type": "integer",
|
||||||
"unsigned": false,
|
"unsigned": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"mappedType": "decimal"
|
"mappedType": "integer"
|
||||||
},
|
},
|
||||||
"width": {
|
"width": {
|
||||||
"name": "width",
|
"name": "width",
|
||||||
"type": "numeric",
|
"type": "integer",
|
||||||
"unsigned": false,
|
"unsigned": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"mappedType": "decimal"
|
"mappedType": "integer"
|
||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "metadata",
|
"name": "metadata",
|
||||||
@@ -1409,13 +1522,52 @@
|
|||||||
"schema": "public",
|
"schema": "public",
|
||||||
"indexes": [
|
"indexes": [
|
||||||
{
|
{
|
||||||
"columnNames": [
|
"keyName": "IDX_product_variant_product_id",
|
||||||
"deleted_at"
|
"columnNames": [],
|
||||||
],
|
|
||||||
"composite": false,
|
"composite": false,
|
||||||
"keyName": "IDX_product_variant_deleted_at",
|
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_variant_product_id\" ON \"product_variant\" (product_id) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_product_variant_deleted_at",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_variant_deleted_at\" ON \"product_variant\" (deleted_at) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_product_variant_sku_unique",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_product_variant_sku_unique\" ON \"product_variant\" (sku) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_product_variant_barcode_unique",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_product_variant_barcode_unique\" ON \"product_variant\" (barcode) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_product_variant_ean_unique",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_product_variant_ean_unique\" ON \"product_variant\" (ean) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_product_variant_upc_unique",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_product_variant_upc_unique\" ON \"product_variant\" (upc) WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "product_variant_pkey",
|
"keyName": "product_variant_pkey",
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
import { Migration } from "@mikro-orm/migrations"
|
||||||
|
|
||||||
|
export class Migration20241125090957 extends Migration {
|
||||||
|
async up(): Promise<void> {
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product" drop constraint if exists "product_status_check";'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_product_category_parent_category_id" ON "product_category" (parent_category_id) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_product_category_path" ON "product_category" (mpath) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_category_handle_unique" ON "product_category" (handle) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_collection_handle_unique" ON "product_collection" (handle) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql('drop index if exists "IDX_product_image_deleted_at";')
|
||||||
|
this.addSql(
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_image_deleted_at" ON "image" (deleted_at) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_product_image_url" ON "image" (url) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_tag_value_unique" ON "product_tag" (value) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_type_value_unique" ON "product_type" (value) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product" alter column "status" type text using ("status"::text);'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
"alter table if exists \"product\" add constraint \"product_status_check\" check (\"status\" in ('draft', 'proposed', 'published', 'rejected'));"
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product" alter column "status" set default \'draft\';'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_product_type_id" ON "product" (type_id) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_product_collection_id" ON "product" (collection_id) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_product_handle_unique" ON "product" (handle) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product_option" alter column "product_id" type text using ("product_id"::text);'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product_option" alter column "product_id" set not null;'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_product_option_product_id" ON "product_option" (product_id) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_option_product_id_title_unique" ON "product_option" (product_id, title) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_product_option_value_option_id" ON "product_option_value" (option_id) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_option_value_option_id_unique" ON "product_option_value" (option_id, value) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product_variant" alter column "weight" type integer using ("weight"::integer);'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product_variant" alter column "length" type integer using ("length"::integer);'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product_variant" alter column "height" type integer using ("height"::integer);'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product_variant" alter column "width" type integer using ("width"::integer);'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_product_variant_product_id" ON "product_variant" (product_id) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_product_variant_sku_unique" ON "product_variant" (sku) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_product_variant_barcode_unique" ON "product_variant" (barcode) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_product_variant_ean_unique" ON "product_variant" (ean) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_product_variant_upc_unique" ON "product_variant" (upc) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(): Promise<void> {
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product" drop constraint if exists "product_status_check";'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'drop index if exists "IDX_product_category_parent_category_id";'
|
||||||
|
)
|
||||||
|
this.addSql('drop index if exists "IDX_product_category_path";')
|
||||||
|
this.addSql('drop index if exists "IDX_category_handle_unique";')
|
||||||
|
|
||||||
|
this.addSql('drop index if exists "IDX_collection_handle_unique";')
|
||||||
|
|
||||||
|
this.addSql('drop index if exists "IDX_image_deleted_at";')
|
||||||
|
this.addSql('drop index if exists "IDX_product_image_url";')
|
||||||
|
this.addSql(
|
||||||
|
'create index if not exists "IDX_product_image_deleted_at" on "image" ("deleted_at");'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql('drop index if exists "IDX_tag_value_unique";')
|
||||||
|
|
||||||
|
this.addSql('drop index if exists "IDX_type_value_unique";')
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product" alter column "status" drop default;'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product" alter column "status" type text using ("status"::text);'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
"alter table if exists \"product\" add constraint \"product_status_check\" check (\"status\" in ('draft', 'proposed', 'published', 'rejected'));"
|
||||||
|
)
|
||||||
|
this.addSql('drop index if exists "IDX_product_type_id";')
|
||||||
|
this.addSql('drop index if exists "IDX_product_collection_id";')
|
||||||
|
this.addSql('drop index if exists "IDX_product_handle_unique";')
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product_option" alter column "product_id" type text using ("product_id"::text);'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product_option" alter column "product_id" drop not null;'
|
||||||
|
)
|
||||||
|
this.addSql('drop index if exists "IDX_product_option_product_id";')
|
||||||
|
this.addSql('drop index if exists "IDX_option_product_id_title_unique";')
|
||||||
|
|
||||||
|
this.addSql('drop index if exists "IDX_product_option_value_option_id";')
|
||||||
|
this.addSql('drop index if exists "IDX_option_value_option_id_unique";')
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product_variant" alter column "weight" type numeric using ("weight"::numeric);'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product_variant" alter column "length" type numeric using ("length"::numeric);'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product_variant" alter column "height" type numeric using ("height"::numeric);'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "product_variant" alter column "width" type numeric using ("width"::numeric);'
|
||||||
|
)
|
||||||
|
this.addSql('drop index if exists "IDX_product_variant_product_id";')
|
||||||
|
this.addSql('drop index if exists "IDX_product_variant_sku_unique";')
|
||||||
|
this.addSql('drop index if exists "IDX_product_variant_barcode_unique";')
|
||||||
|
this.addSql('drop index if exists "IDX_product_variant_ean_unique";')
|
||||||
|
this.addSql('drop index if exists "IDX_product_variant_upc_unique";')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,140 +1,45 @@
|
|||||||
import {
|
import { model } from "@medusajs/framework/utils"
|
||||||
DALUtils,
|
|
||||||
Searchable,
|
|
||||||
createPsqlIndexStatementHelper,
|
|
||||||
generateEntityId,
|
|
||||||
kebabCase,
|
|
||||||
} from "@medusajs/framework/utils"
|
|
||||||
import {
|
|
||||||
BeforeCreate,
|
|
||||||
Collection,
|
|
||||||
Entity,
|
|
||||||
EventArgs,
|
|
||||||
Filter,
|
|
||||||
Index,
|
|
||||||
ManyToMany,
|
|
||||||
ManyToOne,
|
|
||||||
OnInit,
|
|
||||||
OneToMany,
|
|
||||||
PrimaryKey,
|
|
||||||
Property,
|
|
||||||
} from "@mikro-orm/core"
|
|
||||||
import Product from "./product"
|
import Product from "./product"
|
||||||
|
|
||||||
const categoryHandleIndexName = "IDX_category_handle_unique"
|
const ProductCategory = model
|
||||||
const categoryHandleIndexStatement = createPsqlIndexStatementHelper({
|
.define("ProductCategory", {
|
||||||
name: categoryHandleIndexName,
|
id: model.id({ prefix: "pcat" }).primaryKey(),
|
||||||
tableName: "product_category",
|
name: model.text().searchable(),
|
||||||
columns: ["handle"],
|
description: model.text().searchable().default(""),
|
||||||
unique: true,
|
handle: model.text().searchable(),
|
||||||
where: "deleted_at IS NULL",
|
mpath: model.text(),
|
||||||
})
|
is_active: model.boolean().default(false),
|
||||||
|
is_internal: model.boolean().default(false),
|
||||||
const categoryMpathIndexName = "IDX_product_category_path"
|
rank: model.number().default(0),
|
||||||
const categoryMpathIndexStatement = createPsqlIndexStatementHelper({
|
metadata: model.json().nullable(),
|
||||||
name: categoryMpathIndexName,
|
parent_category: model
|
||||||
tableName: "product_category",
|
.belongsTo(() => ProductCategory, {
|
||||||
columns: ["mpath"],
|
mappedBy: "category_children",
|
||||||
unique: false,
|
})
|
||||||
where: "deleted_at IS NULL",
|
.nullable(),
|
||||||
})
|
category_children: model.hasMany(() => ProductCategory, {
|
||||||
|
mappedBy: "parent_category",
|
||||||
categoryMpathIndexStatement.MikroORMIndex()
|
}),
|
||||||
categoryHandleIndexStatement.MikroORMIndex()
|
products: model.manyToMany(() => Product, {
|
||||||
@Entity({ tableName: "product_category" })
|
mappedBy: "categories",
|
||||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
}),
|
||||||
class ProductCategory {
|
|
||||||
@PrimaryKey({ columnType: "text" })
|
|
||||||
id!: string
|
|
||||||
|
|
||||||
@Searchable()
|
|
||||||
@Property({ columnType: "text", nullable: false })
|
|
||||||
name?: string
|
|
||||||
|
|
||||||
@Searchable()
|
|
||||||
@Property({ columnType: "text", default: "", nullable: false })
|
|
||||||
description?: string
|
|
||||||
|
|
||||||
@Searchable()
|
|
||||||
@Property({ columnType: "text", nullable: false })
|
|
||||||
handle?: string
|
|
||||||
|
|
||||||
@Property({ columnType: "text", nullable: false })
|
|
||||||
mpath?: string
|
|
||||||
|
|
||||||
@Property({ columnType: "boolean", default: false })
|
|
||||||
is_active?: boolean
|
|
||||||
|
|
||||||
@Property({ columnType: "boolean", default: false })
|
|
||||||
is_internal?: boolean
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
columnType: "integer",
|
|
||||||
nullable: false,
|
|
||||||
default: 0,
|
|
||||||
})
|
})
|
||||||
rank: number
|
.cascades({
|
||||||
|
delete: ["category_children"],
|
||||||
@ManyToOne(() => ProductCategory, {
|
|
||||||
columnType: "text",
|
|
||||||
fieldName: "parent_category_id",
|
|
||||||
nullable: true,
|
|
||||||
mapToPk: true,
|
|
||||||
onDelete: "cascade",
|
|
||||||
})
|
})
|
||||||
parent_category_id?: string | null
|
.indexes([
|
||||||
|
{
|
||||||
@ManyToOne(() => ProductCategory, { nullable: true, persist: false })
|
name: "IDX_product_category_path",
|
||||||
parent_category?: ProductCategory
|
on: ["mpath"],
|
||||||
|
unique: false,
|
||||||
@OneToMany({
|
where: "deleted_at IS NULL",
|
||||||
entity: () => ProductCategory,
|
},
|
||||||
mappedBy: (productCategory) => productCategory.parent_category,
|
{
|
||||||
})
|
name: "IDX_category_handle_unique",
|
||||||
category_children = new Collection<ProductCategory>(this)
|
on: ["handle"],
|
||||||
|
unique: true,
|
||||||
@Property({
|
where: "deleted_at IS NULL",
|
||||||
onCreate: () => new Date(),
|
},
|
||||||
columnType: "timestamptz",
|
])
|
||||||
defaultRaw: "now()",
|
|
||||||
})
|
|
||||||
created_at?: Date
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
onCreate: () => new Date(),
|
|
||||||
onUpdate: () => new Date(),
|
|
||||||
columnType: "timestamptz",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
})
|
|
||||||
updated_at?: Date
|
|
||||||
|
|
||||||
@Index({ name: "IDX_product_category_deleted_at" })
|
|
||||||
@Property({ columnType: "timestamptz", nullable: true })
|
|
||||||
deleted_at?: Date
|
|
||||||
|
|
||||||
@Property({ columnType: "jsonb", nullable: true })
|
|
||||||
metadata: Record<string, unknown> | null = null
|
|
||||||
|
|
||||||
@ManyToMany(() => Product, (product) => product.categories)
|
|
||||||
products = new Collection<Product>(this)
|
|
||||||
|
|
||||||
@OnInit()
|
|
||||||
async onInit() {
|
|
||||||
this.id = generateEntityId(this.id, "pcat")
|
|
||||||
this.parent_category_id ??= this.parent_category?.id ?? null
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeCreate()
|
|
||||||
async onCreate(args: EventArgs<ProductCategory>) {
|
|
||||||
this.id = generateEntityId(this.id, "pcat")
|
|
||||||
this.parent_category_id ??= this.parent_category?.id ?? null
|
|
||||||
|
|
||||||
if (!this.handle && this.name) {
|
|
||||||
this.handle = kebabCase(this.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.mpath = `${this.mpath ? this.mpath + "." : ""}${this.id}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProductCategory
|
export default ProductCategory
|
||||||
@@ -1,81 +1,23 @@
|
|||||||
import {
|
import { model } from "@medusajs/framework/utils"
|
||||||
BeforeCreate,
|
|
||||||
Collection,
|
|
||||||
Entity,
|
|
||||||
Filter,
|
|
||||||
Index,
|
|
||||||
OneToMany,
|
|
||||||
OnInit,
|
|
||||||
PrimaryKey,
|
|
||||||
Property,
|
|
||||||
} from "@mikro-orm/core"
|
|
||||||
|
|
||||||
import {
|
|
||||||
createPsqlIndexStatementHelper,
|
|
||||||
DALUtils,
|
|
||||||
generateEntityId,
|
|
||||||
kebabCase,
|
|
||||||
Searchable,
|
|
||||||
} from "@medusajs/framework/utils"
|
|
||||||
import Product from "./product"
|
import Product from "./product"
|
||||||
|
|
||||||
const collectionHandleIndexName = "IDX_collection_handle_unique"
|
const ProductCollection = model
|
||||||
const collectionHandleIndexStatement = createPsqlIndexStatementHelper({
|
.define("ProductCollection", {
|
||||||
name: collectionHandleIndexName,
|
id: model.id({ prefix: "pcol" }).primaryKey(),
|
||||||
tableName: "product_collection",
|
title: model.text().searchable(),
|
||||||
columns: ["handle"],
|
handle: model.text(),
|
||||||
unique: true,
|
metadata: model.json().nullable(),
|
||||||
where: "deleted_at IS NULL",
|
products: model.hasMany(() => Product, {
|
||||||
})
|
mappedBy: "collection",
|
||||||
|
}),
|
||||||
collectionHandleIndexStatement.MikroORMIndex()
|
|
||||||
@Entity({ tableName: "product_collection" })
|
|
||||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
|
||||||
class ProductCollection {
|
|
||||||
@PrimaryKey({ columnType: "text" })
|
|
||||||
id!: string
|
|
||||||
|
|
||||||
@Searchable()
|
|
||||||
@Property({ columnType: "text" })
|
|
||||||
title: string
|
|
||||||
|
|
||||||
@Property({ columnType: "text" })
|
|
||||||
handle?: string
|
|
||||||
|
|
||||||
@OneToMany(() => Product, (product) => product.collection)
|
|
||||||
products = new Collection<Product>(this)
|
|
||||||
|
|
||||||
@Property({ columnType: "jsonb", nullable: true })
|
|
||||||
metadata?: Record<string, unknown> | null
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
onCreate: () => new Date(),
|
|
||||||
columnType: "timestamptz",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
})
|
})
|
||||||
created_at: Date
|
.indexes([
|
||||||
|
{
|
||||||
@Property({
|
name: "IDX_collection_handle_unique",
|
||||||
onCreate: () => new Date(),
|
on: ["handle"],
|
||||||
onUpdate: () => new Date(),
|
unique: true,
|
||||||
columnType: "timestamptz",
|
where: "deleted_at IS NULL",
|
||||||
defaultRaw: "now()",
|
},
|
||||||
})
|
])
|
||||||
updated_at: Date
|
|
||||||
|
|
||||||
@Index({ name: "IDX_product_collection_deleted_at" })
|
|
||||||
@Property({ columnType: "timestamptz", nullable: true })
|
|
||||||
deleted_at?: Date
|
|
||||||
|
|
||||||
@OnInit()
|
|
||||||
@BeforeCreate()
|
|
||||||
onInit() {
|
|
||||||
this.id = generateEntityId(this.id, "pcol")
|
|
||||||
|
|
||||||
if (!this.handle && this.title) {
|
|
||||||
this.handle = kebabCase(this.title)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProductCollection
|
export default ProductCollection
|
||||||
@@ -1,88 +1,26 @@
|
|||||||
import {
|
import { model } from "@medusajs/framework/utils"
|
||||||
BeforeCreate,
|
|
||||||
Entity,
|
|
||||||
Filter,
|
|
||||||
Index,
|
|
||||||
ManyToOne,
|
|
||||||
OnInit,
|
|
||||||
PrimaryKey,
|
|
||||||
Property,
|
|
||||||
Rel,
|
|
||||||
} from "@mikro-orm/core"
|
|
||||||
|
|
||||||
import {
|
|
||||||
DALUtils,
|
|
||||||
createPsqlIndexStatementHelper,
|
|
||||||
generateEntityId,
|
|
||||||
} from "@medusajs/framework/utils"
|
|
||||||
import Product from "./product"
|
import Product from "./product"
|
||||||
|
|
||||||
const imageUrlIndexName = "IDX_product_image_url"
|
const ProductImage = model
|
||||||
const imageUrlIndexStatement = createPsqlIndexStatementHelper({
|
.define(
|
||||||
name: imageUrlIndexName,
|
{ tableName: "image", name: "ProductImage" },
|
||||||
tableName: "image",
|
{
|
||||||
columns: ["url"],
|
id: model.id({ prefix: "img" }).primaryKey(),
|
||||||
unique: false,
|
url: model.text(),
|
||||||
where: "deleted_at IS NULL",
|
metadata: model.json().nullable(),
|
||||||
})
|
rank: model.number().default(0),
|
||||||
|
product: model.belongsTo(() => Product, {
|
||||||
imageUrlIndexStatement.MikroORMIndex()
|
mappedBy: "images",
|
||||||
@Entity({ tableName: "image" })
|
}),
|
||||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
}
|
||||||
class ProductImage {
|
)
|
||||||
@PrimaryKey({ columnType: "text" })
|
.indexes([
|
||||||
id!: string
|
{
|
||||||
|
name: "IDX_product_image_url",
|
||||||
@Property({ columnType: "text" })
|
on: ["url"],
|
||||||
url: string
|
unique: false,
|
||||||
|
where: "deleted_at IS NULL",
|
||||||
@Property({ columnType: "jsonb", nullable: true })
|
},
|
||||||
metadata?: Record<string, unknown> | null
|
])
|
||||||
|
|
||||||
@Property({
|
|
||||||
onCreate: () => new Date(),
|
|
||||||
columnType: "timestamptz",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
})
|
|
||||||
created_at: Date
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
onCreate: () => new Date(),
|
|
||||||
onUpdate: () => new Date(),
|
|
||||||
columnType: "timestamptz",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
})
|
|
||||||
updated_at: Date
|
|
||||||
|
|
||||||
@Index({ name: "IDX_product_image_deleted_at" })
|
|
||||||
@Property({ columnType: "timestamptz", nullable: true })
|
|
||||||
deleted_at?: Date
|
|
||||||
|
|
||||||
@Property({ columnType: "integer", default: 0 })
|
|
||||||
rank: number
|
|
||||||
|
|
||||||
@ManyToOne(() => Product, {
|
|
||||||
columnType: "text",
|
|
||||||
onDelete: "cascade",
|
|
||||||
fieldName: "product_id",
|
|
||||||
mapToPk: true,
|
|
||||||
})
|
|
||||||
product_id: string
|
|
||||||
|
|
||||||
@ManyToOne(() => Product, {
|
|
||||||
persist: false,
|
|
||||||
})
|
|
||||||
product: Rel<Product>
|
|
||||||
|
|
||||||
@OnInit()
|
|
||||||
onInit() {
|
|
||||||
this.id = generateEntityId(this.id, "img")
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeCreate()
|
|
||||||
onCreate() {
|
|
||||||
this.id = generateEntityId(this.id, "img")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProductImage
|
export default ProductImage
|
||||||
@@ -1,87 +1,27 @@
|
|||||||
import {
|
import { model } from "@medusajs/framework/utils"
|
||||||
DALUtils,
|
|
||||||
createPsqlIndexStatementHelper,
|
|
||||||
generateEntityId,
|
|
||||||
} from "@medusajs/framework/utils"
|
|
||||||
import {
|
|
||||||
BeforeCreate,
|
|
||||||
Collection,
|
|
||||||
Entity,
|
|
||||||
Filter,
|
|
||||||
Index,
|
|
||||||
ManyToMany,
|
|
||||||
ManyToOne,
|
|
||||||
OnInit,
|
|
||||||
PrimaryKey,
|
|
||||||
Property,
|
|
||||||
} from "@mikro-orm/core"
|
|
||||||
import { ProductOption, ProductVariant } from "./index"
|
import { ProductOption, ProductVariant } from "./index"
|
||||||
|
|
||||||
const optionValueOptionIdIndexName = "IDX_option_value_option_id_unique"
|
const ProductOptionValue = model
|
||||||
const optionValueOptionIdIndexStatement = createPsqlIndexStatementHelper({
|
.define("ProductOptionValue", {
|
||||||
name: optionValueOptionIdIndexName,
|
id: model.id({ prefix: "optval" }).primaryKey(),
|
||||||
tableName: "product_option_value",
|
value: model.text(),
|
||||||
columns: ["option_id", "value"],
|
metadata: model.json().nullable(),
|
||||||
unique: true,
|
option: model
|
||||||
where: "deleted_at IS NULL",
|
.belongsTo(() => ProductOption, {
|
||||||
})
|
mappedBy: "values",
|
||||||
|
})
|
||||||
optionValueOptionIdIndexStatement.MikroORMIndex()
|
.nullable(),
|
||||||
@Entity({ tableName: "product_option_value" })
|
variants: model.manyToMany(() => ProductVariant, {
|
||||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
mappedBy: "options",
|
||||||
class ProductOptionValue {
|
}),
|
||||||
@PrimaryKey({ columnType: "text" })
|
|
||||||
id!: string
|
|
||||||
|
|
||||||
@Property({ columnType: "text" })
|
|
||||||
value: string
|
|
||||||
|
|
||||||
@ManyToOne(() => ProductOption, {
|
|
||||||
columnType: "text",
|
|
||||||
fieldName: "option_id",
|
|
||||||
mapToPk: true,
|
|
||||||
nullable: true,
|
|
||||||
onDelete: "cascade",
|
|
||||||
})
|
})
|
||||||
option_id: string | null
|
.indexes([
|
||||||
|
{
|
||||||
@ManyToOne(() => ProductOption, {
|
name: "IDX_option_value_option_id_unique",
|
||||||
nullable: true,
|
on: ["option_id", "value"],
|
||||||
persist: false,
|
unique: true,
|
||||||
})
|
where: "deleted_at IS NULL",
|
||||||
option: ProductOption | null
|
},
|
||||||
|
])
|
||||||
@ManyToMany(() => ProductVariant, (variant) => variant.options)
|
|
||||||
variants = new Collection<ProductVariant>(this)
|
|
||||||
|
|
||||||
@Property({ columnType: "jsonb", nullable: true })
|
|
||||||
metadata?: Record<string, unknown> | null
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
onCreate: () => new Date(),
|
|
||||||
columnType: "timestamptz",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
})
|
|
||||||
created_at: Date
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
onCreate: () => new Date(),
|
|
||||||
onUpdate: () => new Date(),
|
|
||||||
columnType: "timestamptz",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
})
|
|
||||||
updated_at: Date
|
|
||||||
|
|
||||||
@Index({ name: "IDX_product_option_value_deleted_at" })
|
|
||||||
@Property({ columnType: "timestamptz", nullable: true })
|
|
||||||
deleted_at?: Date
|
|
||||||
|
|
||||||
@OnInit()
|
|
||||||
@BeforeCreate()
|
|
||||||
onInit() {
|
|
||||||
this.id = generateEntityId(this.id, "optval")
|
|
||||||
this.option_id ??= this.option?.id ?? null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProductOptionValue
|
export default ProductOptionValue
|
||||||
@@ -1,93 +1,29 @@
|
|||||||
import {
|
import { model } from "@medusajs/framework/utils"
|
||||||
DALUtils,
|
|
||||||
Searchable,
|
|
||||||
createPsqlIndexStatementHelper,
|
|
||||||
generateEntityId,
|
|
||||||
} from "@medusajs/framework/utils"
|
|
||||||
import {
|
|
||||||
BeforeCreate,
|
|
||||||
Cascade,
|
|
||||||
Collection,
|
|
||||||
Entity,
|
|
||||||
Filter,
|
|
||||||
Index,
|
|
||||||
ManyToOne,
|
|
||||||
OnInit,
|
|
||||||
OneToMany,
|
|
||||||
PrimaryKey,
|
|
||||||
Property,
|
|
||||||
} from "@mikro-orm/core"
|
|
||||||
import { Product } from "./index"
|
import { Product } from "./index"
|
||||||
import ProductOptionValue from "./product-option-value"
|
import ProductOptionValue from "./product-option-value"
|
||||||
|
|
||||||
const optionProductIdTitleIndexName = "IDX_option_product_id_title_unique"
|
const ProductOption = model
|
||||||
const optionProductIdTitleIndexStatement = createPsqlIndexStatementHelper({
|
.define("ProductOption", {
|
||||||
name: optionProductIdTitleIndexName,
|
id: model.id({ prefix: "opt" }).primaryKey(),
|
||||||
tableName: "product_option",
|
title: model.text().searchable(),
|
||||||
columns: ["product_id", "title"],
|
metadata: model.json().nullable(),
|
||||||
unique: true,
|
product: model.belongsTo(() => Product, {
|
||||||
where: "deleted_at IS NULL",
|
mappedBy: "options",
|
||||||
})
|
}),
|
||||||
|
values: model.hasMany(() => ProductOptionValue, {
|
||||||
optionProductIdTitleIndexStatement.MikroORMIndex()
|
mappedBy: "option",
|
||||||
@Entity({ tableName: "product_option" })
|
}),
|
||||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
|
||||||
class ProductOption {
|
|
||||||
@PrimaryKey({ columnType: "text" })
|
|
||||||
id!: string
|
|
||||||
|
|
||||||
@Searchable()
|
|
||||||
@Property({ columnType: "text" })
|
|
||||||
title: string
|
|
||||||
|
|
||||||
@ManyToOne(() => Product, {
|
|
||||||
columnType: "text",
|
|
||||||
fieldName: "product_id",
|
|
||||||
mapToPk: true,
|
|
||||||
nullable: true,
|
|
||||||
onDelete: "cascade",
|
|
||||||
})
|
})
|
||||||
product_id: string | null
|
.cascades({
|
||||||
|
delete: ["values"],
|
||||||
@ManyToOne(() => Product, {
|
|
||||||
persist: false,
|
|
||||||
nullable: true,
|
|
||||||
})
|
})
|
||||||
product: Product | null
|
.indexes([
|
||||||
|
{
|
||||||
@OneToMany(() => ProductOptionValue, (value) => value.option, {
|
name: "IDX_option_product_id_title_unique",
|
||||||
cascade: [Cascade.PERSIST, "soft-remove" as any],
|
on: ["product_id", "title"],
|
||||||
})
|
unique: true,
|
||||||
values = new Collection<ProductOptionValue>(this)
|
where: "deleted_at IS NULL",
|
||||||
|
},
|
||||||
@Property({ columnType: "jsonb", nullable: true })
|
])
|
||||||
metadata?: Record<string, unknown> | null
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
onCreate: () => new Date(),
|
|
||||||
columnType: "timestamptz",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
})
|
|
||||||
created_at: Date
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
onCreate: () => new Date(),
|
|
||||||
onUpdate: () => new Date(),
|
|
||||||
columnType: "timestamptz",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
})
|
|
||||||
updated_at: Date
|
|
||||||
|
|
||||||
@Index({ name: "IDX_product_option_deleted_at" })
|
|
||||||
@Property({ columnType: "timestamptz", nullable: true })
|
|
||||||
deleted_at?: Date
|
|
||||||
|
|
||||||
@OnInit()
|
|
||||||
@BeforeCreate()
|
|
||||||
onInit() {
|
|
||||||
this.id = generateEntityId(this.id, "opt")
|
|
||||||
this.product_id ??= this.product?.id ?? null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProductOption
|
export default ProductOption
|
||||||
@@ -1,73 +1,25 @@
|
|||||||
import {
|
import { model } from "@medusajs/framework/utils"
|
||||||
BeforeCreate,
|
|
||||||
Collection,
|
|
||||||
Entity,
|
|
||||||
Filter,
|
|
||||||
Index,
|
|
||||||
ManyToMany,
|
|
||||||
OnInit,
|
|
||||||
PrimaryKey,
|
|
||||||
Property,
|
|
||||||
} from "@mikro-orm/core"
|
|
||||||
|
|
||||||
import {
|
|
||||||
DALUtils,
|
|
||||||
Searchable,
|
|
||||||
createPsqlIndexStatementHelper,
|
|
||||||
generateEntityId,
|
|
||||||
} from "@medusajs/framework/utils"
|
|
||||||
import Product from "./product"
|
import Product from "./product"
|
||||||
|
|
||||||
const tagValueIndexName = "IDX_tag_value_unique"
|
const ProductTag = model
|
||||||
const tagValueIndexStatement = createPsqlIndexStatementHelper({
|
.define(
|
||||||
name: tagValueIndexName,
|
{ tableName: "product_tag", name: "ProductTag" },
|
||||||
tableName: "product_tag",
|
{
|
||||||
columns: ["value"],
|
id: model.id({ prefix: "ptag" }).primaryKey(),
|
||||||
unique: true,
|
value: model.text().searchable(),
|
||||||
where: "deleted_at IS NULL",
|
metadata: model.json().nullable(),
|
||||||
})
|
products: model.manyToMany(() => Product, {
|
||||||
|
mappedBy: "tags",
|
||||||
tagValueIndexStatement.MikroORMIndex()
|
}),
|
||||||
@Entity({ tableName: "product_tag" })
|
}
|
||||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
)
|
||||||
class ProductTag {
|
.indexes([
|
||||||
@PrimaryKey({ columnType: "text" })
|
{
|
||||||
id!: string
|
name: "IDX_tag_value_unique",
|
||||||
|
on: ["value"],
|
||||||
@Searchable()
|
unique: true,
|
||||||
@Property({ columnType: "text" })
|
where: "deleted_at IS NULL",
|
||||||
value: string
|
},
|
||||||
|
])
|
||||||
@Property({ columnType: "jsonb", nullable: true })
|
|
||||||
metadata?: Record<string, unknown> | null
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
onCreate: () => new Date(),
|
|
||||||
columnType: "timestamptz",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
})
|
|
||||||
created_at: Date
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
onCreate: () => new Date(),
|
|
||||||
onUpdate: () => new Date(),
|
|
||||||
columnType: "timestamptz",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
})
|
|
||||||
updated_at: Date
|
|
||||||
|
|
||||||
@Index({ name: "IDX_product_tag_deleted_at" })
|
|
||||||
@Property({ columnType: "timestamptz", nullable: true })
|
|
||||||
deleted_at?: Date
|
|
||||||
|
|
||||||
@ManyToMany(() => Product, (product) => product.tags)
|
|
||||||
products = new Collection<Product>(this)
|
|
||||||
|
|
||||||
@OnInit()
|
|
||||||
@BeforeCreate()
|
|
||||||
onInit() {
|
|
||||||
this.id = generateEntityId(this.id, "ptag")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProductTag
|
export default ProductTag
|
||||||
@@ -1,67 +1,22 @@
|
|||||||
import {
|
import { model } from "@medusajs/framework/utils"
|
||||||
BeforeCreate,
|
import { Product } from "@models"
|
||||||
Entity,
|
|
||||||
Filter,
|
|
||||||
Index,
|
|
||||||
OnInit,
|
|
||||||
PrimaryKey,
|
|
||||||
Property,
|
|
||||||
} from "@mikro-orm/core"
|
|
||||||
|
|
||||||
import {
|
const ProductType = model
|
||||||
DALUtils,
|
.define("ProductType", {
|
||||||
Searchable,
|
id: model.id({ prefix: "ptyp" }).primaryKey(),
|
||||||
createPsqlIndexStatementHelper,
|
value: model.text().searchable(),
|
||||||
generateEntityId,
|
metadata: model.json().nullable(),
|
||||||
} from "@medusajs/framework/utils"
|
product: model.hasMany(() => Product, {
|
||||||
|
mappedBy: "type",
|
||||||
const typeValueIndexName = "IDX_type_value_unique"
|
}),
|
||||||
const typeValueIndexStatement = createPsqlIndexStatementHelper({
|
|
||||||
name: typeValueIndexName,
|
|
||||||
tableName: "product_type",
|
|
||||||
columns: ["value"],
|
|
||||||
unique: true,
|
|
||||||
where: "deleted_at IS NULL",
|
|
||||||
})
|
|
||||||
|
|
||||||
typeValueIndexStatement.MikroORMIndex()
|
|
||||||
@Entity({ tableName: "product_type" })
|
|
||||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
|
||||||
class ProductType {
|
|
||||||
@PrimaryKey({ columnType: "text" })
|
|
||||||
id!: string
|
|
||||||
|
|
||||||
@Searchable()
|
|
||||||
@Property({ columnType: "text" })
|
|
||||||
value: string
|
|
||||||
|
|
||||||
@Property({ columnType: "json", nullable: true })
|
|
||||||
metadata?: Record<string, unknown> | null
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
onCreate: () => new Date(),
|
|
||||||
columnType: "timestamptz",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
})
|
})
|
||||||
created_at: Date
|
.indexes([
|
||||||
|
{
|
||||||
@Property({
|
name: "IDX_type_value_unique",
|
||||||
onCreate: () => new Date(),
|
on: ["value"],
|
||||||
onUpdate: () => new Date(),
|
unique: true,
|
||||||
columnType: "timestamptz",
|
where: "deleted_at IS NULL",
|
||||||
defaultRaw: "now()",
|
},
|
||||||
})
|
])
|
||||||
updated_at: Date
|
|
||||||
|
|
||||||
@Index({ name: "IDX_product_type_deleted_at" })
|
|
||||||
@Property({ columnType: "timestamptz", nullable: true })
|
|
||||||
deleted_at?: Date
|
|
||||||
|
|
||||||
@OnInit()
|
|
||||||
@BeforeCreate()
|
|
||||||
onInit() {
|
|
||||||
this.id = generateEntityId(this.id, "ptyp")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProductType
|
export default ProductType
|
||||||
@@ -1,187 +1,69 @@
|
|||||||
import {
|
import { model } from "@medusajs/framework/utils"
|
||||||
createPsqlIndexStatementHelper,
|
|
||||||
DALUtils,
|
|
||||||
generateEntityId,
|
|
||||||
Searchable,
|
|
||||||
} from "@medusajs/framework/utils"
|
|
||||||
import {
|
|
||||||
BeforeCreate,
|
|
||||||
Collection,
|
|
||||||
Entity,
|
|
||||||
Filter,
|
|
||||||
Index,
|
|
||||||
ManyToMany,
|
|
||||||
ManyToOne,
|
|
||||||
OnInit,
|
|
||||||
PrimaryKey,
|
|
||||||
Property,
|
|
||||||
} from "@mikro-orm/core"
|
|
||||||
import { Product, ProductOptionValue } from "@models"
|
import { Product, ProductOptionValue } from "@models"
|
||||||
|
|
||||||
const variantSkuIndexName = "IDX_product_variant_sku_unique"
|
const ProductVariant = model
|
||||||
const variantSkuIndexStatement = createPsqlIndexStatementHelper({
|
.define("ProductVariant", {
|
||||||
name: variantSkuIndexName,
|
id: model.id({ prefix: "variant" }).primaryKey(),
|
||||||
tableName: "product_variant",
|
title: model.text().searchable(),
|
||||||
columns: ["sku"],
|
sku: model.text().searchable().nullable(),
|
||||||
unique: true,
|
barcode: model.text().searchable().nullable(),
|
||||||
where: "deleted_at IS NULL",
|
ean: model.text().searchable().nullable(),
|
||||||
})
|
upc: model.text().searchable().nullable(),
|
||||||
|
allow_backorder: model.boolean().default(false),
|
||||||
const variantBarcodeIndexName = "IDX_product_variant_barcode_unique"
|
manage_inventory: model.boolean().default(true),
|
||||||
const variantBarcodeIndexStatement = createPsqlIndexStatementHelper({
|
hs_code: model.text().nullable(),
|
||||||
name: variantBarcodeIndexName,
|
origin_country: model.text().nullable(),
|
||||||
tableName: "product_variant",
|
mid_code: model.text().nullable(),
|
||||||
columns: ["barcode"],
|
material: model.text().nullable(),
|
||||||
unique: true,
|
weight: model.number().nullable(),
|
||||||
where: "deleted_at IS NULL",
|
length: model.number().nullable(),
|
||||||
})
|
height: model.number().nullable(),
|
||||||
|
width: model.number().nullable(),
|
||||||
const variantEanIndexName = "IDX_product_variant_ean_unique"
|
metadata: model.json().nullable(),
|
||||||
const variantEanIndexStatement = createPsqlIndexStatementHelper({
|
variant_rank: model.number().default(0).nullable(),
|
||||||
name: variantEanIndexName,
|
product: model
|
||||||
tableName: "product_variant",
|
.belongsTo(() => Product, {
|
||||||
columns: ["ean"],
|
mappedBy: "variants",
|
||||||
unique: true,
|
})
|
||||||
where: "deleted_at IS NULL",
|
.nullable(),
|
||||||
})
|
options: model.manyToMany(() => ProductOptionValue, {
|
||||||
|
pivotTable: "product_variant_option",
|
||||||
const variantUpcIndexName = "IDX_product_variant_upc_unique"
|
mappedBy: "variants",
|
||||||
const variantUpcIndexStatement = createPsqlIndexStatementHelper({
|
joinColumn: "variant_id",
|
||||||
name: variantUpcIndexName,
|
inverseJoinColumn: "option_value_id",
|
||||||
tableName: "product_variant",
|
}),
|
||||||
columns: ["upc"],
|
|
||||||
unique: true,
|
|
||||||
where: "deleted_at IS NULL",
|
|
||||||
})
|
|
||||||
|
|
||||||
const variantProductIdIndexName = "IDX_product_variant_product_id"
|
|
||||||
const variantProductIdIndexStatement = createPsqlIndexStatementHelper({
|
|
||||||
name: variantProductIdIndexName,
|
|
||||||
tableName: "product_variant",
|
|
||||||
columns: ["product_id"],
|
|
||||||
unique: false,
|
|
||||||
where: "deleted_at IS NULL",
|
|
||||||
})
|
|
||||||
|
|
||||||
variantProductIdIndexStatement.MikroORMIndex()
|
|
||||||
variantSkuIndexStatement.MikroORMIndex()
|
|
||||||
variantBarcodeIndexStatement.MikroORMIndex()
|
|
||||||
variantEanIndexStatement.MikroORMIndex()
|
|
||||||
variantUpcIndexStatement.MikroORMIndex()
|
|
||||||
@Entity({ tableName: "product_variant" })
|
|
||||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
|
||||||
class ProductVariant {
|
|
||||||
@PrimaryKey({ columnType: "text" })
|
|
||||||
id!: string
|
|
||||||
|
|
||||||
@Searchable()
|
|
||||||
@Property({ columnType: "text" })
|
|
||||||
title: string
|
|
||||||
|
|
||||||
@Searchable()
|
|
||||||
@Property({ columnType: "text", nullable: true })
|
|
||||||
sku?: string | null
|
|
||||||
|
|
||||||
@Searchable()
|
|
||||||
@Property({ columnType: "text", nullable: true })
|
|
||||||
barcode?: string | null
|
|
||||||
|
|
||||||
@Searchable()
|
|
||||||
@Property({ columnType: "text", nullable: true })
|
|
||||||
ean?: string | null
|
|
||||||
|
|
||||||
@Searchable()
|
|
||||||
@Property({ columnType: "text", nullable: true })
|
|
||||||
upc?: string | null
|
|
||||||
|
|
||||||
@Property({ columnType: "boolean", default: false })
|
|
||||||
allow_backorder?: boolean = false
|
|
||||||
|
|
||||||
@Property({ columnType: "boolean", default: true })
|
|
||||||
manage_inventory?: boolean = true
|
|
||||||
|
|
||||||
@Property({ columnType: "text", nullable: true })
|
|
||||||
hs_code?: string | null
|
|
||||||
|
|
||||||
@Property({ columnType: "text", nullable: true })
|
|
||||||
origin_country?: string | null
|
|
||||||
|
|
||||||
@Property({ columnType: "text", nullable: true })
|
|
||||||
mid_code?: string | null
|
|
||||||
|
|
||||||
@Property({ columnType: "text", nullable: true })
|
|
||||||
material?: string | null
|
|
||||||
|
|
||||||
@Property({ columnType: "numeric", nullable: true })
|
|
||||||
weight?: number | null
|
|
||||||
|
|
||||||
@Property({ columnType: "numeric", nullable: true })
|
|
||||||
length?: number | null
|
|
||||||
|
|
||||||
@Property({ columnType: "numeric", nullable: true })
|
|
||||||
height?: number | null
|
|
||||||
|
|
||||||
@Property({ columnType: "numeric", nullable: true })
|
|
||||||
width?: number | null
|
|
||||||
|
|
||||||
@Property({ columnType: "jsonb", nullable: true })
|
|
||||||
metadata?: Record<string, unknown> | null
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
columnType: "integer",
|
|
||||||
nullable: true,
|
|
||||||
default: 0,
|
|
||||||
})
|
})
|
||||||
variant_rank?: number | null
|
.indexes([
|
||||||
|
{
|
||||||
@ManyToOne(() => Product, {
|
name: "IDX_product_variant_product_id",
|
||||||
columnType: "text",
|
on: ["product_id"],
|
||||||
nullable: true,
|
unique: false,
|
||||||
onDelete: "cascade",
|
where: "deleted_at IS NULL",
|
||||||
fieldName: "product_id",
|
},
|
||||||
mapToPk: true,
|
{
|
||||||
})
|
name: "IDX_product_variant_sku_unique",
|
||||||
product_id: string | null
|
on: ["sku"],
|
||||||
|
unique: true,
|
||||||
@ManyToOne(() => Product, {
|
where: "deleted_at IS NULL",
|
||||||
persist: false,
|
},
|
||||||
nullable: true,
|
{
|
||||||
})
|
name: "IDX_product_variant_barcode_unique",
|
||||||
product: Product | null
|
on: ["barcode"],
|
||||||
|
unique: true,
|
||||||
@ManyToMany(() => ProductOptionValue, "variants", {
|
where: "deleted_at IS NULL",
|
||||||
owner: true,
|
},
|
||||||
pivotTable: "product_variant_option",
|
{
|
||||||
joinColumn: "variant_id",
|
name: "IDX_product_variant_ean_unique",
|
||||||
inverseJoinColumn: "option_value_id",
|
on: ["ean"],
|
||||||
})
|
unique: true,
|
||||||
options = new Collection<ProductOptionValue>(this)
|
where: "deleted_at IS NULL",
|
||||||
|
},
|
||||||
@Property({
|
{
|
||||||
onCreate: () => new Date(),
|
name: "IDX_product_variant_upc_unique",
|
||||||
columnType: "timestamptz",
|
on: ["upc"],
|
||||||
defaultRaw: "now()",
|
unique: true,
|
||||||
})
|
where: "deleted_at IS NULL",
|
||||||
created_at: Date
|
},
|
||||||
|
])
|
||||||
@Property({
|
|
||||||
onCreate: () => new Date(),
|
|
||||||
onUpdate: () => new Date(),
|
|
||||||
columnType: "timestamptz",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
})
|
|
||||||
updated_at: Date
|
|
||||||
|
|
||||||
@Index({ name: "IDX_product_variant_deleted_at" })
|
|
||||||
@Property({ columnType: "timestamptz", nullable: true })
|
|
||||||
deleted_at?: Date
|
|
||||||
|
|
||||||
@OnInit()
|
|
||||||
@BeforeCreate()
|
|
||||||
onInit() {
|
|
||||||
this.id = generateEntityId(this.id, "variant")
|
|
||||||
this.product_id ??= this.product?.id ?? null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProductVariant
|
export default ProductVariant
|
||||||
@@ -1,222 +1,86 @@
|
|||||||
import {
|
import { model, ProductUtils } from "@medusajs/framework/utils"
|
||||||
BeforeCreate,
|
|
||||||
Cascade,
|
|
||||||
Collection,
|
|
||||||
Entity,
|
|
||||||
Enum,
|
|
||||||
Filter,
|
|
||||||
Index,
|
|
||||||
ManyToMany,
|
|
||||||
ManyToOne,
|
|
||||||
OneToMany,
|
|
||||||
OnInit,
|
|
||||||
PrimaryKey,
|
|
||||||
Property,
|
|
||||||
} from "@mikro-orm/core"
|
|
||||||
|
|
||||||
import {
|
|
||||||
createPsqlIndexStatementHelper,
|
|
||||||
DALUtils,
|
|
||||||
generateEntityId,
|
|
||||||
ProductUtils,
|
|
||||||
Searchable,
|
|
||||||
toHandle,
|
|
||||||
} from "@medusajs/framework/utils"
|
|
||||||
import ProductCategory from "./product-category"
|
|
||||||
import ProductCollection from "./product-collection"
|
|
||||||
import ProductImage from "./product-image"
|
|
||||||
import ProductOption from "./product-option"
|
|
||||||
import ProductTag from "./product-tag"
|
import ProductTag from "./product-tag"
|
||||||
import ProductType from "./product-type"
|
import ProductType from "./product-type"
|
||||||
|
import ProductImage from "./product-image"
|
||||||
|
import ProductOption from "./product-option"
|
||||||
import ProductVariant from "./product-variant"
|
import ProductVariant from "./product-variant"
|
||||||
|
import ProductCategory from "./product-category"
|
||||||
|
import ProductCollection from "./product-collection"
|
||||||
|
|
||||||
const productHandleIndexName = "IDX_product_handle_unique"
|
const Product = model
|
||||||
const productHandleIndexStatement = createPsqlIndexStatementHelper({
|
.define("Product", {
|
||||||
name: productHandleIndexName,
|
id: model.id({ prefix: "prod" }).primaryKey(),
|
||||||
tableName: "product",
|
title: model.text().searchable(),
|
||||||
columns: ["handle"],
|
handle: model.text(),
|
||||||
unique: true,
|
subtitle: model.text().searchable().nullable(),
|
||||||
where: "deleted_at IS NULL",
|
description: model.text().searchable().nullable(),
|
||||||
})
|
is_giftcard: model.boolean().default(false),
|
||||||
|
status: model
|
||||||
const productTypeIndexName = "IDX_product_type_id"
|
.enum(ProductUtils.ProductStatus)
|
||||||
const productTypeIndexStatement = createPsqlIndexStatementHelper({
|
.default(ProductUtils.ProductStatus.DRAFT),
|
||||||
name: productTypeIndexName,
|
thumbnail: model.text().nullable(),
|
||||||
tableName: "product",
|
weight: model.text().nullable(),
|
||||||
columns: ["type_id"],
|
length: model.text().nullable(),
|
||||||
unique: false,
|
height: model.text().nullable(),
|
||||||
where: "deleted_at IS NULL",
|
width: model.text().nullable(),
|
||||||
})
|
origin_country: model.text().nullable(),
|
||||||
|
hs_code: model.text().nullable(),
|
||||||
const productCollectionIndexName = "IDX_product_collection_id"
|
mid_code: model.text().nullable(),
|
||||||
const productCollectionIndexStatement = createPsqlIndexStatementHelper({
|
material: model.text().nullable(),
|
||||||
name: productCollectionIndexName,
|
discountable: model.boolean().default(true),
|
||||||
tableName: "product",
|
external_id: model.text().nullable(),
|
||||||
columns: ["collection_id"],
|
metadata: model.json().nullable(),
|
||||||
unique: false,
|
variants: model.hasMany(() => ProductVariant, {
|
||||||
where: "deleted_at IS NULL",
|
mappedBy: "product",
|
||||||
})
|
}),
|
||||||
|
type: model
|
||||||
productTypeIndexStatement.MikroORMIndex()
|
.belongsTo(() => ProductType, {
|
||||||
productCollectionIndexStatement.MikroORMIndex()
|
mappedBy: "product",
|
||||||
productHandleIndexStatement.MikroORMIndex()
|
})
|
||||||
@Entity({ tableName: "product" })
|
.nullable(),
|
||||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
tags: model.manyToMany(() => ProductTag, {
|
||||||
class Product {
|
mappedBy: "products",
|
||||||
@PrimaryKey({ columnType: "text" })
|
pivotTable: "product_tags",
|
||||||
id!: string
|
}),
|
||||||
|
options: model.hasMany(() => ProductOption, {
|
||||||
@Searchable()
|
mappedBy: "product",
|
||||||
@Property({ columnType: "text" })
|
}),
|
||||||
title: string
|
images: model.hasMany(() => ProductImage, {
|
||||||
|
mappedBy: "product",
|
||||||
@Property({ columnType: "text" })
|
}),
|
||||||
handle?: string
|
collection: model
|
||||||
|
.belongsTo(() => ProductCollection, {
|
||||||
@Searchable()
|
mappedBy: "products",
|
||||||
@Property({ columnType: "text", nullable: true })
|
})
|
||||||
subtitle?: string | null
|
.nullable(),
|
||||||
|
categories: model.manyToMany(() => ProductCategory, {
|
||||||
@Searchable()
|
pivotTable: "product_category_product",
|
||||||
@Property({
|
mappedBy: "products",
|
||||||
columnType: "text",
|
}),
|
||||||
nullable: true,
|
|
||||||
})
|
})
|
||||||
description?: string | null
|
.cascades({
|
||||||
|
delete: ["variants", "options", "images"],
|
||||||
@Property({ columnType: "boolean", default: false })
|
|
||||||
is_giftcard!: boolean
|
|
||||||
|
|
||||||
@Enum(() => ProductUtils.ProductStatus)
|
|
||||||
@Property({ default: ProductUtils.ProductStatus.DRAFT })
|
|
||||||
status!: ProductUtils.ProductStatus
|
|
||||||
|
|
||||||
@Property({ columnType: "text", nullable: true })
|
|
||||||
thumbnail?: string | null
|
|
||||||
|
|
||||||
@OneToMany(() => ProductOption, (o) => o.product, {
|
|
||||||
cascade: ["soft-remove"] as any,
|
|
||||||
})
|
})
|
||||||
options = new Collection<ProductOption>(this)
|
.indexes([
|
||||||
|
{
|
||||||
@Searchable()
|
name: "IDX_product_handle_unique",
|
||||||
@OneToMany(() => ProductVariant, (variant) => variant.product, {
|
on: ["handle"],
|
||||||
cascade: ["soft-remove"] as any,
|
unique: true,
|
||||||
})
|
where: "deleted_at IS NULL",
|
||||||
variants = new Collection<ProductVariant>(this)
|
},
|
||||||
|
{
|
||||||
@Property({ columnType: "text", nullable: true })
|
name: "IDX_product_type_id",
|
||||||
weight?: number | null
|
on: ["type_id"],
|
||||||
|
unique: false,
|
||||||
@Property({ columnType: "text", nullable: true })
|
where: "deleted_at IS NULL",
|
||||||
length?: number | null
|
},
|
||||||
|
{
|
||||||
@Property({ columnType: "text", nullable: true })
|
name: "IDX_product_collection_id",
|
||||||
height?: number | null
|
on: ["collection_id"],
|
||||||
|
unique: false,
|
||||||
@Property({ columnType: "text", nullable: true })
|
where: "deleted_at IS NULL",
|
||||||
width?: number | null
|
},
|
||||||
|
])
|
||||||
@Property({ columnType: "text", nullable: true })
|
|
||||||
origin_country?: string | null
|
|
||||||
|
|
||||||
@Property({ columnType: "text", nullable: true })
|
|
||||||
hs_code?: string | null
|
|
||||||
|
|
||||||
@Property({ columnType: "text", nullable: true })
|
|
||||||
mid_code?: string | null
|
|
||||||
|
|
||||||
@Property({ columnType: "text", nullable: true })
|
|
||||||
material?: string | null
|
|
||||||
|
|
||||||
@Searchable()
|
|
||||||
@ManyToOne(() => ProductCollection, {
|
|
||||||
columnType: "text",
|
|
||||||
nullable: true,
|
|
||||||
fieldName: "collection_id",
|
|
||||||
mapToPk: true,
|
|
||||||
onDelete: "set null",
|
|
||||||
})
|
|
||||||
collection_id: string | null
|
|
||||||
|
|
||||||
@ManyToOne(() => ProductCollection, {
|
|
||||||
nullable: true,
|
|
||||||
persist: false,
|
|
||||||
})
|
|
||||||
collection: ProductCollection | null
|
|
||||||
|
|
||||||
@ManyToOne(() => ProductType, {
|
|
||||||
columnType: "text",
|
|
||||||
nullable: true,
|
|
||||||
fieldName: "type_id",
|
|
||||||
mapToPk: true,
|
|
||||||
onDelete: "set null",
|
|
||||||
})
|
|
||||||
type_id: string | null
|
|
||||||
|
|
||||||
@ManyToOne(() => ProductType, {
|
|
||||||
nullable: true,
|
|
||||||
persist: false,
|
|
||||||
})
|
|
||||||
type: ProductType | null
|
|
||||||
|
|
||||||
@ManyToMany(() => ProductTag, "products", {
|
|
||||||
owner: true,
|
|
||||||
pivotTable: "product_tags",
|
|
||||||
index: "IDX_product_tag_id",
|
|
||||||
})
|
|
||||||
tags = new Collection<ProductTag>(this)
|
|
||||||
|
|
||||||
@OneToMany(() => ProductImage, (image) => image.product_id, {
|
|
||||||
cascade: [Cascade.PERSIST, Cascade.REMOVE],
|
|
||||||
})
|
|
||||||
images = new Collection<ProductImage>(this)
|
|
||||||
|
|
||||||
@ManyToMany(() => ProductCategory, "products", {
|
|
||||||
owner: true,
|
|
||||||
pivotTable: "product_category_product",
|
|
||||||
})
|
|
||||||
categories = new Collection<ProductCategory>(this)
|
|
||||||
|
|
||||||
@Property({ columnType: "boolean", default: true })
|
|
||||||
discountable: boolean
|
|
||||||
|
|
||||||
@Property({ columnType: "text", nullable: true })
|
|
||||||
external_id?: string | null
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
onCreate: () => new Date(),
|
|
||||||
columnType: "timestamptz",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
})
|
|
||||||
created_at: Date
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
onCreate: () => new Date(),
|
|
||||||
onUpdate: () => new Date(),
|
|
||||||
columnType: "timestamptz",
|
|
||||||
defaultRaw: "now()",
|
|
||||||
})
|
|
||||||
updated_at: Date
|
|
||||||
|
|
||||||
@Index({ name: "IDX_product_deleted_at" })
|
|
||||||
@Property({ columnType: "timestamptz", nullable: true })
|
|
||||||
deleted_at?: Date
|
|
||||||
|
|
||||||
@Property({ columnType: "jsonb", nullable: true })
|
|
||||||
metadata?: Record<string, unknown> | null
|
|
||||||
|
|
||||||
@OnInit()
|
|
||||||
@BeforeCreate()
|
|
||||||
onInit() {
|
|
||||||
this.id = generateEntityId(this.id, "prod")
|
|
||||||
this.type_id ??= this.type?.id ?? null
|
|
||||||
this.collection_id ??= this.collection?.id ?? null
|
|
||||||
|
|
||||||
if (!this.handle && this.title) {
|
|
||||||
this.handle = toHandle(this.title)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Product
|
export default Product
|
||||||
@@ -1,30 +1,30 @@
|
|||||||
import {
|
import {
|
||||||
Context,
|
Context,
|
||||||
DAL,
|
DAL,
|
||||||
|
InferEntityType,
|
||||||
ProductCategoryTransformOptions,
|
ProductCategoryTransformOptions,
|
||||||
ProductTypes,
|
ProductTypes,
|
||||||
} from "@medusajs/framework/types"
|
} from "@medusajs/framework/types"
|
||||||
import { DALUtils, isDefined, MedusaError } from "@medusajs/framework/utils"
|
import { DALUtils, isDefined, MedusaError } from "@medusajs/framework/utils"
|
||||||
import {
|
import { LoadStrategy, FindOptions as MikroOptions } from "@mikro-orm/core"
|
||||||
EntityDTO,
|
|
||||||
LoadStrategy,
|
|
||||||
FilterQuery as MikroFilterQuery,
|
|
||||||
FindOptions as MikroOptions,
|
|
||||||
RequiredEntityData,
|
|
||||||
} from "@mikro-orm/core"
|
|
||||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||||
import { ProductCategory } from "@models"
|
import { ProductCategory } from "@models"
|
||||||
import { UpdateCategoryInput } from "@types"
|
import { UpdateCategoryInput } from "@types"
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeRepository<ProductCategory> {
|
export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeRepository<
|
||||||
|
typeof ProductCategory
|
||||||
|
> {
|
||||||
buildFindOptions(
|
buildFindOptions(
|
||||||
findOptions: DAL.FindOptions<ProductCategory> = { where: {} },
|
findOptions: DAL.FindOptions<typeof ProductCategory> = { where: {} },
|
||||||
familyOptions: ProductCategoryTransformOptions = {}
|
familyOptions: ProductCategoryTransformOptions = {}
|
||||||
) {
|
) {
|
||||||
const findOptions_ = { ...findOptions }
|
const findOptions_ = { ...findOptions }
|
||||||
findOptions_.options ??= {
|
findOptions_.options ??= {}
|
||||||
orderBy: { rank: "ASC" },
|
findOptions_.options.orderBy = {
|
||||||
|
id: "ASC",
|
||||||
|
rank: "ASC",
|
||||||
|
...findOptions_.options.orderBy,
|
||||||
}
|
}
|
||||||
|
|
||||||
const fields = (findOptions_.options.fields ??= [])
|
const fields = (findOptions_.options.fields ??= [])
|
||||||
@@ -69,17 +69,19 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
}
|
}
|
||||||
|
|
||||||
async find(
|
async find(
|
||||||
findOptions: DAL.FindOptions<ProductCategory> = { where: {} },
|
findOptions: DAL.FindOptions<typeof ProductCategory> = { where: {} },
|
||||||
transformOptions: ProductCategoryTransformOptions = {},
|
transformOptions: ProductCategoryTransformOptions = {},
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
): Promise<ProductCategory[]> {
|
): Promise<InferEntityType<typeof ProductCategory>[]> {
|
||||||
const manager = super.getActiveManager<SqlEntityManager>(context)
|
const manager = super.getActiveManager<SqlEntityManager>(context)
|
||||||
const findOptions_ = this.buildFindOptions(findOptions, transformOptions)
|
const findOptions_ = this.buildFindOptions(findOptions, transformOptions)
|
||||||
|
|
||||||
const productCategories = await manager.find(
|
const productCategories = await manager.find<
|
||||||
ProductCategory,
|
InferEntityType<typeof ProductCategory>
|
||||||
findOptions_.where as MikroFilterQuery<ProductCategory>,
|
>(
|
||||||
{ ...findOptions_.options } as MikroOptions<ProductCategory>
|
ProductCategory.name,
|
||||||
|
findOptions_.where,
|
||||||
|
{ ...findOptions_.options } as any // TODO
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -102,7 +104,9 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
return this.sortCategoriesByRank(categoriesTree)
|
return this.sortCategoriesByRank(categoriesTree)
|
||||||
}
|
}
|
||||||
|
|
||||||
sortCategoriesByRank(categories: ProductCategory[]): ProductCategory[] {
|
sortCategoriesByRank(
|
||||||
|
categories: InferEntityType<typeof ProductCategory>[]
|
||||||
|
): InferEntityType<typeof ProductCategory>[] {
|
||||||
const sortedCategories = categories.sort((a, b) => a.rank - b.rank)
|
const sortedCategories = categories.sort((a, b) => a.rank - b.rank)
|
||||||
|
|
||||||
for (const category of sortedCategories) {
|
for (const category of sortedCategories) {
|
||||||
@@ -122,12 +126,12 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
descendants?: boolean
|
descendants?: boolean
|
||||||
ancestors?: boolean
|
ancestors?: boolean
|
||||||
},
|
},
|
||||||
productCategories: ProductCategory[],
|
productCategories: InferEntityType<typeof ProductCategory>[],
|
||||||
findOptions: DAL.FindOptions<ProductCategory> & {
|
findOptions: DAL.FindOptions<typeof ProductCategory> & {
|
||||||
serialize?: boolean
|
serialize?: boolean
|
||||||
} = { where: {} },
|
} = { where: {} },
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
): Promise<ProductCategory[]> {
|
): Promise<InferEntityType<typeof ProductCategory>[]> {
|
||||||
const { serialize = true } = findOptions
|
const { serialize = true } = findOptions
|
||||||
delete findOptions.serialize
|
delete findOptions.serialize
|
||||||
|
|
||||||
@@ -173,17 +177,17 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
...findOptions.options,
|
...findOptions.options,
|
||||||
limit: undefined,
|
limit: undefined,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
} as MikroOptions<ProductCategory>
|
} as MikroOptions<any>
|
||||||
|
|
||||||
delete where.id
|
delete where.id
|
||||||
delete where.mpath
|
delete where.mpath
|
||||||
delete where.parent_category_id
|
delete where.parent_category_id
|
||||||
|
|
||||||
const categoriesInTree = serialize
|
const categoriesInTree = serialize
|
||||||
? await this.serialize<ProductCategory[]>(
|
? await this.serialize<InferEntityType<typeof ProductCategory>[]>(
|
||||||
await manager.find(ProductCategory, where, options)
|
await manager.find(ProductCategory.name, where, options)
|
||||||
)
|
)
|
||||||
: await manager.find(ProductCategory, where, options)
|
: await manager.find(ProductCategory.name, where, options)
|
||||||
|
|
||||||
const categoriesById = new Map(categoriesInTree.map((cat) => [cat.id, cat]))
|
const categoriesById = new Map(categoriesInTree.map((cat) => [cat.id, cat]))
|
||||||
|
|
||||||
@@ -235,18 +239,18 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
}
|
}
|
||||||
|
|
||||||
async findAndCount(
|
async findAndCount(
|
||||||
findOptions: DAL.FindOptions<ProductCategory> = { where: {} },
|
findOptions: DAL.FindOptions<typeof ProductCategory> = { where: {} },
|
||||||
transformOptions: ProductCategoryTransformOptions = {},
|
transformOptions: ProductCategoryTransformOptions = {},
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
): Promise<[ProductCategory[], number]> {
|
): Promise<[InferEntityType<typeof ProductCategory>[], number]> {
|
||||||
const manager = super.getActiveManager<SqlEntityManager>(context)
|
const manager = super.getActiveManager<SqlEntityManager>(context)
|
||||||
const findOptions_ = this.buildFindOptions(findOptions, transformOptions)
|
const findOptions_ = this.buildFindOptions(findOptions, transformOptions)
|
||||||
|
|
||||||
const [productCategories, count] = await manager.findAndCount(
|
const [productCategories, count] = (await manager.findAndCount(
|
||||||
ProductCategory,
|
ProductCategory.name,
|
||||||
findOptions_.where as MikroFilterQuery<ProductCategory>,
|
findOptions_.where,
|
||||||
findOptions_.options as MikroOptions<ProductCategory>
|
findOptions_.options as any
|
||||||
)
|
)) as unknown as [InferEntityType<typeof ProductCategory>[], number]
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!transformOptions.includeDescendantsTree &&
|
!transformOptions.includeDescendantsTree &&
|
||||||
@@ -271,19 +275,23 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
async delete(ids: string[], context: Context = {}): Promise<void> {
|
async delete(ids: string[], context: Context = {}): Promise<void> {
|
||||||
const manager = super.getActiveManager<SqlEntityManager>(context)
|
const manager = super.getActiveManager<SqlEntityManager>(context)
|
||||||
await this.baseDelete(ids, context)
|
await this.baseDelete(ids, context)
|
||||||
await manager.nativeDelete(ProductCategory, { id: ids }, {})
|
await manager.nativeDelete(ProductCategory.name, { id: ids }, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
async softDelete(
|
async softDelete(
|
||||||
ids: string[],
|
ids: string[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
): Promise<[ProductCategory[], Record<string, unknown[]>]> {
|
): Promise<
|
||||||
|
[InferEntityType<typeof ProductCategory>[], Record<string, unknown[]>]
|
||||||
|
> {
|
||||||
const manager = super.getActiveManager<SqlEntityManager>(context)
|
const manager = super.getActiveManager<SqlEntityManager>(context)
|
||||||
await this.baseDelete(ids, context)
|
await this.baseDelete(ids, context)
|
||||||
|
|
||||||
const categories = await Promise.all(
|
const categories = await Promise.all(
|
||||||
ids.map(async (id) => {
|
ids.map(async (id) => {
|
||||||
const productCategory = await manager.findOne(ProductCategory, {
|
const productCategory = await manager.findOne<
|
||||||
|
InferEntityType<typeof ProductCategory>
|
||||||
|
>(ProductCategory.name, {
|
||||||
id,
|
id,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -306,11 +314,15 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
async restore(
|
async restore(
|
||||||
ids: string[],
|
ids: string[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
): Promise<[ProductCategory[], Record<string, unknown[]>]> {
|
): Promise<
|
||||||
|
[InferEntityType<typeof ProductCategory>[], Record<string, unknown[]>]
|
||||||
|
> {
|
||||||
const manager = super.getActiveManager<SqlEntityManager>(context)
|
const manager = super.getActiveManager<SqlEntityManager>(context)
|
||||||
const categories = await Promise.all(
|
const categories = await Promise.all(
|
||||||
ids.map(async (id) => {
|
ids.map(async (id) => {
|
||||||
const productCategory = await manager.findOneOrFail(ProductCategory, {
|
const productCategory = await manager.findOneOrFail<
|
||||||
|
InferEntityType<typeof ProductCategory>
|
||||||
|
>(ProductCategory.name, {
|
||||||
id,
|
id,
|
||||||
})
|
})
|
||||||
manager.assign(productCategory, { deleted_at: null })
|
manager.assign(productCategory, { deleted_at: null })
|
||||||
@@ -327,12 +339,14 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
ids.map(async (id) => {
|
ids.map(async (id) => {
|
||||||
const productCategory = await manager.findOne(
|
const productCategory = await manager.findOne<
|
||||||
ProductCategory,
|
InferEntityType<typeof ProductCategory>
|
||||||
|
>(
|
||||||
|
ProductCategory.name,
|
||||||
{ id },
|
{ id },
|
||||||
{
|
{
|
||||||
populate: ["category_children"],
|
populate: ["category_children"],
|
||||||
}
|
} as any // TODO
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!productCategory) {
|
if (!productCategory) {
|
||||||
@@ -357,13 +371,15 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
async create(
|
async create(
|
||||||
data: ProductTypes.CreateProductCategoryDTO[],
|
data: ProductTypes.CreateProductCategoryDTO[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
): Promise<ProductCategory[]> {
|
): Promise<InferEntityType<typeof ProductCategory>[]> {
|
||||||
const manager = super.getActiveManager<SqlEntityManager>(context)
|
const manager = super.getActiveManager<SqlEntityManager>(context)
|
||||||
|
|
||||||
const categories = await Promise.all(
|
const categories = await Promise.all(
|
||||||
data.map(async (entry, i) => {
|
data.map(async (entry, i) => {
|
||||||
const categoryData: Partial<EntityDTO<ProductCategory>> = { ...entry }
|
const categoryData: Partial<InferEntityType<typeof ProductCategory>> = {
|
||||||
const siblingsCount = await manager.count(ProductCategory, {
|
...entry,
|
||||||
|
}
|
||||||
|
const siblingsCount = await manager.count(ProductCategory.name, {
|
||||||
parent_category_id: categoryData?.parent_category_id || null,
|
parent_category_id: categoryData?.parent_category_id || null,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -378,14 +394,15 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the base mpath if the category has a parent. The model `create` hook will append the own id to the base mpath.
|
// Set the base mpath if the category has a parent. The model `create` hook will append the own id to the base mpath.
|
||||||
|
let parentCategory: InferEntityType<typeof ProductCategory> | null =
|
||||||
|
null
|
||||||
const parentCategoryId =
|
const parentCategoryId =
|
||||||
categoryData.parent_category_id ?? categoryData.parent_category?.id
|
categoryData.parent_category_id ?? categoryData.parent_category?.id
|
||||||
|
|
||||||
if (parentCategoryId) {
|
if (parentCategoryId) {
|
||||||
const parentCategory = await manager.findOne(
|
parentCategory = await manager.findOne<
|
||||||
ProductCategory,
|
InferEntityType<typeof ProductCategory>
|
||||||
parentCategoryId
|
>(ProductCategory.name, parentCategoryId)
|
||||||
)
|
|
||||||
|
|
||||||
if (!parentCategory) {
|
if (!parentCategory) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
@@ -393,14 +410,28 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
`Parent category with id: '${parentCategoryId}' does not exist`
|
`Parent category with id: '${parentCategoryId}' does not exist`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
categoryData.mpath = parentCategory.mpath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager.create(
|
const result = await manager.create<
|
||||||
ProductCategory,
|
InferEntityType<typeof ProductCategory>
|
||||||
categoryData as RequiredEntityData<ProductCategory>
|
>(
|
||||||
|
ProductCategory.name,
|
||||||
|
categoryData as unknown as InferEntityType<typeof ProductCategory>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since "mpath" calculation relies on the id of the created
|
||||||
|
* category, we have to compute it after calling manager.create. So
|
||||||
|
* that we can access the "category.id" which is under the hood
|
||||||
|
* defined by DML.
|
||||||
|
*/
|
||||||
|
manager.assign(result, {
|
||||||
|
mpath: parentCategory
|
||||||
|
? `${parentCategory.mpath}.${result.id}`
|
||||||
|
: result.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -411,14 +442,18 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
async update(
|
async update(
|
||||||
data: UpdateCategoryInput[],
|
data: UpdateCategoryInput[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
): Promise<ProductCategory[]> {
|
): Promise<InferEntityType<typeof ProductCategory>[]> {
|
||||||
const manager = super.getActiveManager<SqlEntityManager>(context)
|
const manager = super.getActiveManager<SqlEntityManager>(context)
|
||||||
const categories = await Promise.all(
|
const categories = await Promise.all(
|
||||||
data.map(async (entry, i) => {
|
data.map(async (entry, i) => {
|
||||||
const categoryData: Partial<EntityDTO<ProductCategory>> = { ...entry }
|
const categoryData: Partial<InferEntityType<typeof ProductCategory>> = {
|
||||||
let productCategory = (await manager.findOne(ProductCategory, {
|
...entry,
|
||||||
|
}
|
||||||
|
let productCategory = await manager.findOne<
|
||||||
|
InferEntityType<typeof ProductCategory>
|
||||||
|
>(ProductCategory.name, {
|
||||||
id: categoryData.id,
|
id: categoryData.id,
|
||||||
})) as ProductCategory
|
})
|
||||||
|
|
||||||
if (!productCategory) {
|
if (!productCategory) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
@@ -465,10 +500,9 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
)
|
)
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
const newParentCategory = await manager.findOne(
|
const newParentCategory = await manager.findOne<
|
||||||
ProductCategory,
|
InferEntityType<typeof ProductCategory>
|
||||||
categoryData.parent_category_id
|
>(ProductCategory.name, categoryData.parent_category_id)
|
||||||
)
|
|
||||||
|
|
||||||
if (!newParentCategory) {
|
if (!newParentCategory) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
@@ -487,13 +521,13 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
)
|
)
|
||||||
|
|
||||||
function updateMpathRecursively(
|
function updateMpathRecursively(
|
||||||
category: ProductCategory,
|
category: InferEntityType<typeof ProductCategory>,
|
||||||
newBaseMpath: string
|
newBaseMpath: string
|
||||||
) {
|
) {
|
||||||
const newMpath = `${newBaseMpath}.${category.id}`
|
const newMpath = `${newBaseMpath}.${category.id}`
|
||||||
category.mpath = newMpath
|
category.mpath = newMpath
|
||||||
for (let child of category.category_children) {
|
for (let child of category.category_children) {
|
||||||
child = manager.getReference(ProductCategory, child.id)
|
child = manager.getReference(ProductCategory.name, child.id)
|
||||||
manager.assign(
|
manager.assign(
|
||||||
child,
|
child,
|
||||||
categoryDataChildrenMap.get(child.id) ?? {}
|
categoryDataChildrenMap.get(child.id) ?? {}
|
||||||
@@ -502,12 +536,15 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMpathRecursively(productCategory!, newParentCategory.mpath!)
|
updateMpathRecursively(
|
||||||
|
productCategory!,
|
||||||
|
(newParentCategory as any).mpath!
|
||||||
|
)
|
||||||
// categoryData.mpath = `${newParentCategory.mpath}.${productCategory.id}`
|
// categoryData.mpath = `${newParentCategory.mpath}.${productCategory.id}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rerank the siblings in the new parent
|
// Rerank the siblings in the new parent
|
||||||
const siblingsCount = await manager.count(ProductCategory, {
|
const siblingsCount = await manager.count(ProductCategory.name, {
|
||||||
parent_category_id: categoryData.parent_category_id,
|
parent_category_id: categoryData.parent_category_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -534,7 +571,7 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
return productCategory
|
return productCategory
|
||||||
// If only the rank changed, we need to rerank all siblings.
|
// If only the rank changed, we need to rerank all siblings.
|
||||||
} else if (isDefined(categoryData.rank)) {
|
} else if (isDefined(categoryData.rank)) {
|
||||||
const siblingsCount = await manager.count(ProductCategory, {
|
const siblingsCount = await manager.count(ProductCategory.name, {
|
||||||
parent_category_id: productCategory.parent_category_id,
|
parent_category_id: productCategory.parent_category_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -546,7 +583,7 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
await this.rerankAllSiblings(
|
await this.rerankAllSiblings(
|
||||||
manager,
|
manager,
|
||||||
productCategory,
|
productCategory,
|
||||||
categoryData as Partial<EntityDTO<ProductCategory>> & {
|
categoryData as Partial<InferEntityType<typeof ProductCategory>> & {
|
||||||
rank: number
|
rank: number
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -569,9 +606,11 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
|
|
||||||
protected async rerankSiblingsAfterDeletion(
|
protected async rerankSiblingsAfterDeletion(
|
||||||
manager: SqlEntityManager,
|
manager: SqlEntityManager,
|
||||||
removedSibling: Partial<ProductCategory>
|
removedSibling: Partial<InferEntityType<typeof ProductCategory>>
|
||||||
) {
|
) {
|
||||||
const affectedSiblings = await manager.find(ProductCategory, {
|
const affectedSiblings = await manager.find<
|
||||||
|
InferEntityType<typeof ProductCategory>
|
||||||
|
>(ProductCategory.name, {
|
||||||
parent_category_id: removedSibling.parent_category_id,
|
parent_category_id: removedSibling.parent_category_id,
|
||||||
rank: { $gt: removedSibling.rank },
|
rank: { $gt: removedSibling.rank },
|
||||||
})
|
})
|
||||||
@@ -586,9 +625,11 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
|
|
||||||
protected async rerankSiblingsAfterCreation(
|
protected async rerankSiblingsAfterCreation(
|
||||||
manager: SqlEntityManager,
|
manager: SqlEntityManager,
|
||||||
addedSibling: Partial<EntityDTO<ProductCategory>>
|
addedSibling: Partial<InferEntityType<typeof ProductCategory>>
|
||||||
) {
|
) {
|
||||||
const affectedSiblings = await manager.find(ProductCategory, {
|
const affectedSiblings = await manager.find<
|
||||||
|
InferEntityType<typeof ProductCategory>
|
||||||
|
>(ProductCategory.name, {
|
||||||
parent_category_id: addedSibling.parent_category_id,
|
parent_category_id: addedSibling.parent_category_id,
|
||||||
rank: { $gte: addedSibling.rank },
|
rank: { $gte: addedSibling.rank },
|
||||||
})
|
})
|
||||||
@@ -603,16 +644,22 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
|
|
||||||
protected async rerankAllSiblings(
|
protected async rerankAllSiblings(
|
||||||
manager: SqlEntityManager,
|
manager: SqlEntityManager,
|
||||||
originalSibling: Partial<ProductCategory> & { rank: number },
|
originalSibling: Partial<InferEntityType<typeof ProductCategory>> & {
|
||||||
updatedSibling: Partial<EntityDTO<ProductCategory>> & { rank: number }
|
rank: number
|
||||||
|
},
|
||||||
|
updatedSibling: Partial<InferEntityType<typeof ProductCategory>> & {
|
||||||
|
rank: number
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
if (originalSibling.rank === updatedSibling.rank) {
|
if (originalSibling.rank === updatedSibling.rank) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (originalSibling.rank < updatedSibling.rank) {
|
if (originalSibling.rank < updatedSibling.rank) {
|
||||||
const siblings = await manager.find(
|
const siblings = await manager.find<
|
||||||
ProductCategory,
|
InferEntityType<typeof ProductCategory>
|
||||||
|
>(
|
||||||
|
ProductCategory.name,
|
||||||
{
|
{
|
||||||
parent_category_id: originalSibling.parent_category_id,
|
parent_category_id: originalSibling.parent_category_id,
|
||||||
rank: { $gt: originalSibling.rank, $lte: updatedSibling.rank },
|
rank: { $gt: originalSibling.rank, $lte: updatedSibling.rank },
|
||||||
@@ -627,8 +674,10 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
|||||||
|
|
||||||
manager.persist(updatedSiblings)
|
manager.persist(updatedSiblings)
|
||||||
} else {
|
} else {
|
||||||
const siblings = await manager.find(
|
const siblings = await manager.find<
|
||||||
ProductCategory,
|
InferEntityType<typeof ProductCategory>
|
||||||
|
>(
|
||||||
|
ProductCategory.name,
|
||||||
{
|
{
|
||||||
parent_category_id: originalSibling.parent_category_id,
|
parent_category_id: originalSibling.parent_category_id,
|
||||||
rank: { $gte: updatedSibling.rank, $lt: originalSibling.rank },
|
rank: { $gte: updatedSibling.rank, $lt: originalSibling.rank },
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Product } from "@models"
|
import { Product } from "@models"
|
||||||
|
|
||||||
import { Context, DAL } from "@medusajs/framework/types"
|
import { Context, DAL } from "@medusajs/framework/types"
|
||||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
|
||||||
import { DALUtils } from "@medusajs/framework/utils"
|
import { DALUtils } from "@medusajs/framework/utils"
|
||||||
|
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory<Product>(
|
export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
||||||
Product
|
Product
|
||||||
) {
|
) {
|
||||||
constructor(...args: any[]) {
|
constructor(...args: any[]) {
|
||||||
@@ -19,7 +19,9 @@ export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory<Pr
|
|||||||
* first find all products that are in the categories, and then exclude them
|
* first find all products that are in the categories, and then exclude them
|
||||||
*/
|
*/
|
||||||
protected async mutateNotInCategoriesConstraints(
|
protected async mutateNotInCategoriesConstraints(
|
||||||
findOptions: DAL.FindOptions<Product> = { where: {} },
|
findOptions: DAL.FindOptions<typeof Product> = {
|
||||||
|
where: {},
|
||||||
|
},
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||||
@@ -29,7 +31,7 @@ export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory<Pr
|
|||||||
findOptions.where.categories?.id?.["$nin"]
|
findOptions.where.categories?.id?.["$nin"]
|
||||||
) {
|
) {
|
||||||
const productsInCategories = await manager.find(
|
const productsInCategories = await manager.find(
|
||||||
Product,
|
this.entity,
|
||||||
{
|
{
|
||||||
categories: {
|
categories: {
|
||||||
id: { $in: findOptions.where.categories.id["$nin"] },
|
id: { $in: findOptions.where.categories.id["$nin"] },
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
Context,
|
Context,
|
||||||
DAL,
|
DAL,
|
||||||
FindConfig,
|
FindConfig,
|
||||||
|
InferEntityType,
|
||||||
ProductTypes,
|
ProductTypes,
|
||||||
} from "@medusajs/framework/types"
|
} from "@medusajs/framework/types"
|
||||||
import {
|
import {
|
||||||
@@ -33,7 +34,7 @@ export default class ProductCategoryService {
|
|||||||
productCategoryId: string,
|
productCategoryId: string,
|
||||||
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<ProductCategory> {
|
): Promise<InferEntityType<typeof ProductCategory>> {
|
||||||
if (!isDefined(productCategoryId)) {
|
if (!isDefined(productCategoryId)) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.NOT_FOUND,
|
MedusaError.Types.NOT_FOUND,
|
||||||
@@ -41,7 +42,7 @@ export default class ProductCategoryService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryOptions = ModulesSdkUtils.buildQuery<ProductCategory>(
|
const queryOptions = ModulesSdkUtils.buildQuery(
|
||||||
{
|
{
|
||||||
id: productCategoryId,
|
id: productCategoryId,
|
||||||
},
|
},
|
||||||
@@ -67,7 +68,7 @@ export default class ProductCategoryService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return productCategories[0] as ProductCategory
|
return productCategories[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
@InjectManager("productCategoryRepository_")
|
@InjectManager("productCategoryRepository_")
|
||||||
@@ -75,7 +76,7 @@ export default class ProductCategoryService {
|
|||||||
filters: ProductTypes.FilterableProductCategoryProps = {},
|
filters: ProductTypes.FilterableProductCategoryProps = {},
|
||||||
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<ProductCategory[]> {
|
): Promise<InferEntityType<typeof ProductCategory>[]> {
|
||||||
const transformOptions = {
|
const transformOptions = {
|
||||||
includeDescendantsTree: filters?.include_descendants_tree || false,
|
includeDescendantsTree: filters?.include_descendants_tree || false,
|
||||||
includeAncestorsTree: filters?.include_ancestors_tree || false,
|
includeAncestorsTree: filters?.include_ancestors_tree || false,
|
||||||
@@ -94,17 +95,14 @@ export default class ProductCategoryService {
|
|||||||
delete filters.q
|
delete filters.q
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryOptions = ModulesSdkUtils.buildQuery<ProductCategory>(
|
const queryOptions = ModulesSdkUtils.buildQuery(filters, config)
|
||||||
filters,
|
|
||||||
config
|
|
||||||
)
|
|
||||||
queryOptions.where ??= {}
|
queryOptions.where ??= {}
|
||||||
|
|
||||||
return (await this.productCategoryRepository_.find(
|
return await this.productCategoryRepository_.find(
|
||||||
queryOptions,
|
queryOptions,
|
||||||
transformOptions,
|
transformOptions,
|
||||||
sharedContext
|
sharedContext
|
||||||
)) as ProductCategory[]
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@InjectManager("productCategoryRepository_")
|
@InjectManager("productCategoryRepository_")
|
||||||
@@ -112,7 +110,7 @@ export default class ProductCategoryService {
|
|||||||
filters: ProductTypes.FilterableProductCategoryProps = {},
|
filters: ProductTypes.FilterableProductCategoryProps = {},
|
||||||
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<[ProductCategory[], number]> {
|
): Promise<[InferEntityType<typeof ProductCategory>[], number]> {
|
||||||
const transformOptions = {
|
const transformOptions = {
|
||||||
includeDescendantsTree: filters?.include_descendants_tree || false,
|
includeDescendantsTree: filters?.include_descendants_tree || false,
|
||||||
includeAncestorsTree: filters?.include_ancestors_tree || false,
|
includeAncestorsTree: filters?.include_ancestors_tree || false,
|
||||||
@@ -131,37 +129,34 @@ export default class ProductCategoryService {
|
|||||||
delete filters.q
|
delete filters.q
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryOptions = ModulesSdkUtils.buildQuery<ProductCategory>(
|
const queryOptions = ModulesSdkUtils.buildQuery(filters, config)
|
||||||
filters,
|
|
||||||
config
|
|
||||||
)
|
|
||||||
queryOptions.where ??= {}
|
queryOptions.where ??= {}
|
||||||
|
|
||||||
return (await this.productCategoryRepository_.findAndCount(
|
return await this.productCategoryRepository_.findAndCount(
|
||||||
queryOptions,
|
queryOptions,
|
||||||
transformOptions,
|
transformOptions,
|
||||||
sharedContext
|
sharedContext
|
||||||
)) as [ProductCategory[], number]
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@InjectTransactionManager("productCategoryRepository_")
|
@InjectTransactionManager("productCategoryRepository_")
|
||||||
async create(
|
async create(
|
||||||
data: ProductTypes.CreateProductCategoryDTO[],
|
data: ProductTypes.CreateProductCategoryDTO[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<ProductCategory[]> {
|
): Promise<InferEntityType<typeof ProductCategory>[]> {
|
||||||
return (await (
|
return await (
|
||||||
this.productCategoryRepository_ as unknown as ProductCategoryRepository
|
this.productCategoryRepository_ as unknown as ProductCategoryRepository
|
||||||
).create(data, sharedContext)) as ProductCategory[]
|
).create(data, sharedContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
@InjectTransactionManager("productCategoryRepository_")
|
@InjectTransactionManager("productCategoryRepository_")
|
||||||
async update(
|
async update(
|
||||||
data: UpdateCategoryInput[],
|
data: UpdateCategoryInput[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<ProductCategory[]> {
|
): Promise<InferEntityType<typeof ProductCategory>[]> {
|
||||||
return (await (
|
return await (
|
||||||
this.productCategoryRepository_ as unknown as ProductCategoryRepository
|
this.productCategoryRepository_ as unknown as ProductCategoryRepository
|
||||||
).update(data, sharedContext)) as ProductCategory[]
|
).update(data, sharedContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
@InjectTransactionManager("productCategoryRepository_")
|
@InjectTransactionManager("productCategoryRepository_")
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
DAL,
|
DAL,
|
||||||
FindConfig,
|
FindConfig,
|
||||||
IEventBusModuleService,
|
IEventBusModuleService,
|
||||||
|
InferEntityType,
|
||||||
InternalModuleDeclaration,
|
InternalModuleDeclaration,
|
||||||
ModuleJoinerConfig,
|
ModuleJoinerConfig,
|
||||||
ModulesSdkTypes,
|
ModulesSdkTypes,
|
||||||
@@ -105,15 +106,31 @@ export default class ProductModuleService
|
|||||||
implements ProductTypes.IProductModuleService
|
implements ProductTypes.IProductModuleService
|
||||||
{
|
{
|
||||||
protected baseRepository_: DAL.RepositoryService
|
protected baseRepository_: DAL.RepositoryService
|
||||||
protected readonly productService_: ModulesSdkTypes.IMedusaInternalService<Product>
|
protected readonly productService_: ModulesSdkTypes.IMedusaInternalService<
|
||||||
protected readonly productVariantService_: ModulesSdkTypes.IMedusaInternalService<ProductVariant>
|
InferEntityType<typeof Product>
|
||||||
|
>
|
||||||
|
protected readonly productVariantService_: ModulesSdkTypes.IMedusaInternalService<
|
||||||
|
InferEntityType<typeof ProductVariant>
|
||||||
|
>
|
||||||
protected readonly productCategoryService_: ProductCategoryService
|
protected readonly productCategoryService_: ProductCategoryService
|
||||||
protected readonly productTagService_: ModulesSdkTypes.IMedusaInternalService<ProductTag>
|
protected readonly productTagService_: ModulesSdkTypes.IMedusaInternalService<
|
||||||
protected readonly productCollectionService_: ModulesSdkTypes.IMedusaInternalService<ProductCollection>
|
InferEntityType<typeof ProductTag>
|
||||||
protected readonly productImageService_: ModulesSdkTypes.IMedusaInternalService<ProductImage>
|
>
|
||||||
protected readonly productTypeService_: ModulesSdkTypes.IMedusaInternalService<ProductType>
|
protected readonly productCollectionService_: ModulesSdkTypes.IMedusaInternalService<
|
||||||
protected readonly productOptionService_: ModulesSdkTypes.IMedusaInternalService<ProductOption>
|
InferEntityType<typeof ProductCollection>
|
||||||
protected readonly productOptionValueService_: ModulesSdkTypes.IMedusaInternalService<ProductOptionValue>
|
>
|
||||||
|
protected readonly productImageService_: ModulesSdkTypes.IMedusaInternalService<
|
||||||
|
InferEntityType<typeof ProductImage>
|
||||||
|
>
|
||||||
|
protected readonly productTypeService_: ModulesSdkTypes.IMedusaInternalService<
|
||||||
|
InferEntityType<typeof ProductType>
|
||||||
|
>
|
||||||
|
protected readonly productOptionService_: ModulesSdkTypes.IMedusaInternalService<
|
||||||
|
InferEntityType<typeof ProductOption>
|
||||||
|
>
|
||||||
|
protected readonly productOptionValueService_: ModulesSdkTypes.IMedusaInternalService<
|
||||||
|
InferEntityType<typeof ProductOptionValue>
|
||||||
|
>
|
||||||
protected readonly eventBusModuleService_?: IEventBusModuleService
|
protected readonly eventBusModuleService_?: IEventBusModuleService
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -211,12 +228,16 @@ export default class ProductModuleService
|
|||||||
return {
|
return {
|
||||||
...config,
|
...config,
|
||||||
order: {
|
order: {
|
||||||
|
id: "ASC",
|
||||||
...config?.order,
|
...config?.order,
|
||||||
...(hasImagesRelation ? {
|
...(hasImagesRelation
|
||||||
images: {
|
? {
|
||||||
rank: "ASC",
|
images: {
|
||||||
},
|
rank: "ASC",
|
||||||
} : {}),
|
...((config?.order?.images as object) ?? {}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,7 +277,7 @@ export default class ProductModuleService
|
|||||||
protected async createVariants_(
|
protected async createVariants_(
|
||||||
data: ProductTypes.CreateProductVariantDTO[],
|
data: ProductTypes.CreateProductVariantDTO[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<ProductVariant[]> {
|
): Promise<InferEntityType<typeof ProductVariant>[]> {
|
||||||
if (data.some((v) => !v.product_id)) {
|
if (data.some((v) => !v.product_id)) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.INVALID_DATA,
|
MedusaError.Types.INVALID_DATA,
|
||||||
@@ -332,8 +353,8 @@ export default class ProductModuleService
|
|||||||
(variant): variant is ProductTypes.CreateProductVariantDTO => !variant.id
|
(variant): variant is ProductTypes.CreateProductVariantDTO => !variant.id
|
||||||
)
|
)
|
||||||
|
|
||||||
let created: ProductVariant[] = []
|
let created: InferEntityType<typeof ProductVariant>[] = []
|
||||||
let updated: ProductVariant[] = []
|
let updated: InferEntityType<typeof ProductVariant>[] = []
|
||||||
|
|
||||||
if (forCreate.length) {
|
if (forCreate.length) {
|
||||||
created = await this.createVariants_(forCreate, sharedContext)
|
created = await this.createVariants_(forCreate, sharedContext)
|
||||||
@@ -400,7 +421,7 @@ export default class ProductModuleService
|
|||||||
protected async updateVariants_(
|
protected async updateVariants_(
|
||||||
data: UpdateProductVariantInput[],
|
data: UpdateProductVariantInput[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<ProductVariant[]> {
|
): Promise<InferEntityType<typeof ProductVariant>[]> {
|
||||||
// Validation step
|
// Validation step
|
||||||
const variantIdsToUpdate = data.map(({ id }) => id)
|
const variantIdsToUpdate = data.map(({ id }) => id)
|
||||||
const variants = await this.productVariantService_.list(
|
const variants = await this.productVariantService_.list(
|
||||||
@@ -535,8 +556,8 @@ export default class ProductModuleService
|
|||||||
(tag): tag is ProductTypes.CreateProductTagDTO => !tag.id
|
(tag): tag is ProductTypes.CreateProductTagDTO => !tag.id
|
||||||
)
|
)
|
||||||
|
|
||||||
let created: ProductTag[] = []
|
let created: InferEntityType<typeof ProductTag>[] = []
|
||||||
let updated: ProductTag[] = []
|
let updated: InferEntityType<typeof ProductTag>[] = []
|
||||||
|
|
||||||
if (forCreate.length) {
|
if (forCreate.length) {
|
||||||
created = await this.productTagService_.create(forCreate, sharedContext)
|
created = await this.productTagService_.create(forCreate, sharedContext)
|
||||||
@@ -665,8 +686,8 @@ export default class ProductModuleService
|
|||||||
(type): type is ProductTypes.CreateProductTypeDTO => !type.id
|
(type): type is ProductTypes.CreateProductTypeDTO => !type.id
|
||||||
)
|
)
|
||||||
|
|
||||||
let created: ProductType[] = []
|
let created: InferEntityType<typeof ProductType>[] = []
|
||||||
let updated: ProductType[] = []
|
let updated: InferEntityType<typeof ProductType>[] = []
|
||||||
|
|
||||||
if (forCreate.length) {
|
if (forCreate.length) {
|
||||||
created = await this.productTypeService_.create(forCreate, sharedContext)
|
created = await this.productTypeService_.create(forCreate, sharedContext)
|
||||||
@@ -763,7 +784,7 @@ export default class ProductModuleService
|
|||||||
protected async createOptions_(
|
protected async createOptions_(
|
||||||
data: ProductTypes.CreateProductOptionDTO[],
|
data: ProductTypes.CreateProductOptionDTO[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<ProductOption[]> {
|
): Promise<InferEntityType<typeof ProductOption>[]> {
|
||||||
if (data.some((v) => !v.product_id)) {
|
if (data.some((v) => !v.product_id)) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.INVALID_DATA,
|
MedusaError.Types.INVALID_DATA,
|
||||||
@@ -810,8 +831,8 @@ export default class ProductModuleService
|
|||||||
(option): option is ProductTypes.CreateProductOptionDTO => !option.id
|
(option): option is ProductTypes.CreateProductOptionDTO => !option.id
|
||||||
)
|
)
|
||||||
|
|
||||||
let created: ProductOption[] = []
|
let created: InferEntityType<typeof ProductOption>[] = []
|
||||||
let updated: ProductOption[] = []
|
let updated: InferEntityType<typeof ProductOption>[] = []
|
||||||
|
|
||||||
if (forCreate.length) {
|
if (forCreate.length) {
|
||||||
created = await this.createOptions_(forCreate, sharedContext)
|
created = await this.createOptions_(forCreate, sharedContext)
|
||||||
@@ -876,7 +897,7 @@ export default class ProductModuleService
|
|||||||
protected async updateOptions_(
|
protected async updateOptions_(
|
||||||
data: UpdateProductOptionInput[],
|
data: UpdateProductOptionInput[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<ProductOption[]> {
|
): Promise<InferEntityType<typeof ProductOption>[]> {
|
||||||
// Validation step
|
// Validation step
|
||||||
if (data.some((option) => !option.id)) {
|
if (data.some((option) => !option.id)) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
@@ -985,7 +1006,7 @@ export default class ProductModuleService
|
|||||||
async createCollections_(
|
async createCollections_(
|
||||||
data: ProductTypes.CreateProductCollectionDTO[],
|
data: ProductTypes.CreateProductCollectionDTO[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<ProductCollection[]> {
|
): Promise<InferEntityType<typeof ProductCollection>[]> {
|
||||||
const normalizedInput = data.map(
|
const normalizedInput = data.map(
|
||||||
ProductModuleService.normalizeCreateProductCollectionInput
|
ProductModuleService.normalizeCreateProductCollectionInput
|
||||||
)
|
)
|
||||||
@@ -1030,8 +1051,8 @@ export default class ProductModuleService
|
|||||||
!collection.id
|
!collection.id
|
||||||
)
|
)
|
||||||
|
|
||||||
let created: ProductCollection[] = []
|
let created: InferEntityType<typeof ProductCollection>[] = []
|
||||||
let updated: ProductCollection[] = []
|
let updated: InferEntityType<typeof ProductCollection>[] = []
|
||||||
|
|
||||||
if (forCreate.length) {
|
if (forCreate.length) {
|
||||||
created = await this.createCollections_(forCreate, sharedContext)
|
created = await this.createCollections_(forCreate, sharedContext)
|
||||||
@@ -1125,7 +1146,7 @@ export default class ProductModuleService
|
|||||||
protected async updateCollections_(
|
protected async updateCollections_(
|
||||||
data: UpdateCollectionInput[],
|
data: UpdateCollectionInput[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<ProductCollection[]> {
|
): Promise<InferEntityType<typeof ProductCollection>[]> {
|
||||||
const normalizedInput = data.map(
|
const normalizedInput = data.map(
|
||||||
ProductModuleService.normalizeUpdateProductCollectionInput
|
ProductModuleService.normalizeUpdateProductCollectionInput
|
||||||
) as UpdateCollectionInput[]
|
) as UpdateCollectionInput[]
|
||||||
@@ -1139,7 +1160,7 @@ export default class ProductModuleService
|
|||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
|
|
||||||
const collections: ProductCollection[] = []
|
const collections: InferEntityType<typeof ProductCollection>[] = []
|
||||||
|
|
||||||
const updateSelectorAndData = updatedCollections.flatMap(
|
const updateSelectorAndData = updatedCollections.flatMap(
|
||||||
(collectionData) => {
|
(collectionData) => {
|
||||||
@@ -1178,7 +1199,7 @@ export default class ProductModuleService
|
|||||||
collections.push({
|
collections.push({
|
||||||
...collectionData,
|
...collectionData,
|
||||||
products: productsToUpdate ?? [],
|
products: productsToUpdate ?? [],
|
||||||
} as ProductCollection)
|
} as InferEntityType<typeof ProductCollection>)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -1208,7 +1229,12 @@ export default class ProductModuleService
|
|||||||
): Promise<
|
): Promise<
|
||||||
ProductTypes.ProductCategoryDTO[] | ProductTypes.ProductCategoryDTO
|
ProductTypes.ProductCategoryDTO[] | ProductTypes.ProductCategoryDTO
|
||||||
> {
|
> {
|
||||||
const input = Array.isArray(data) ? data : [data]
|
const input = (Array.isArray(data) ? data : [data]).map(
|
||||||
|
(productCategory) => {
|
||||||
|
productCategory.handle ??= kebabCase(productCategory.name)
|
||||||
|
return productCategory
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const categories = await this.productCategoryService_.create(
|
const categories = await this.productCategoryService_.create(
|
||||||
input,
|
input,
|
||||||
@@ -1255,8 +1281,8 @@ export default class ProductModuleService
|
|||||||
!category.id
|
!category.id
|
||||||
)
|
)
|
||||||
|
|
||||||
let created: ProductCategory[] = []
|
let created: InferEntityType<typeof ProductCategory>[] = []
|
||||||
let updated: ProductCategory[] = []
|
let updated: InferEntityType<typeof ProductCategory>[] = []
|
||||||
|
|
||||||
if (forCreate.length) {
|
if (forCreate.length) {
|
||||||
created = await this.productCategoryService_.create(
|
created = await this.productCategoryService_.create(
|
||||||
@@ -1406,8 +1432,8 @@ export default class ProductModuleService
|
|||||||
(product): product is ProductTypes.CreateProductDTO => !product.id
|
(product): product is ProductTypes.CreateProductDTO => !product.id
|
||||||
)
|
)
|
||||||
|
|
||||||
let created: Product[] = []
|
let created: InferEntityType<typeof Product>[] = []
|
||||||
let updated: Product[] = []
|
let updated: InferEntityType<typeof Product>[] = []
|
||||||
|
|
||||||
if (forCreate.length) {
|
if (forCreate.length) {
|
||||||
created = await this.createProducts_(forCreate, sharedContext)
|
created = await this.createProducts_(forCreate, sharedContext)
|
||||||
@@ -1494,7 +1520,7 @@ export default class ProductModuleService
|
|||||||
protected async createProducts_(
|
protected async createProducts_(
|
||||||
data: ProductTypes.CreateProductDTO[],
|
data: ProductTypes.CreateProductDTO[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<Product[]> {
|
): Promise<InferEntityType<typeof Product>[]> {
|
||||||
const normalizedInput = await promiseAll(
|
const normalizedInput = await promiseAll(
|
||||||
data.map(async (d) => {
|
data.map(async (d) => {
|
||||||
const normalized = await this.normalizeCreateProductInput(
|
const normalized = await this.normalizeCreateProductInput(
|
||||||
@@ -1581,7 +1607,7 @@ export default class ProductModuleService
|
|||||||
protected async updateProducts_(
|
protected async updateProducts_(
|
||||||
data: UpdateProductInput[],
|
data: UpdateProductInput[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<Product[]> {
|
): Promise<InferEntityType<typeof Product>[]> {
|
||||||
const normalizedInput = await promiseAll(
|
const normalizedInput = await promiseAll(
|
||||||
data.map(async (d) => {
|
data.map(async (d) => {
|
||||||
const normalized = await this.normalizeUpdateProductInput(
|
const normalized = await this.normalizeUpdateProductInput(
|
||||||
@@ -1733,6 +1759,13 @@ export default class ProductModuleService
|
|||||||
) {
|
) {
|
||||||
this.validateProductPayload(productData)
|
this.validateProductPayload(productData)
|
||||||
|
|
||||||
|
if (!productData.title) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Product title is required`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const options = productData.options
|
const options = productData.options
|
||||||
const missingOptionsVariants: string[] = []
|
const missingOptionsVariants: string[] = []
|
||||||
|
|
||||||
@@ -1872,7 +1905,7 @@ export default class ProductModuleService
|
|||||||
variants:
|
variants:
|
||||||
| ProductTypes.CreateProductVariantDTO[]
|
| ProductTypes.CreateProductVariantDTO[]
|
||||||
| ProductTypes.UpdateProductVariantDTO[],
|
| ProductTypes.UpdateProductVariantDTO[],
|
||||||
options: ProductOption[]
|
options: InferEntityType<typeof ProductOption>[]
|
||||||
):
|
):
|
||||||
| ProductTypes.CreateProductVariantDTO[]
|
| ProductTypes.CreateProductVariantDTO[]
|
||||||
| ProductTypes.UpdateProductVariantDTO[] {
|
| ProductTypes.UpdateProductVariantDTO[] {
|
||||||
@@ -1945,7 +1978,7 @@ export default class ProductModuleService
|
|||||||
| ProductTypes.CreateProductVariantDTO
|
| ProductTypes.CreateProductVariantDTO
|
||||||
| UpdateProductVariantInput
|
| UpdateProductVariantInput
|
||||||
) & { options: { id: string }[]; product_id: string })[],
|
) & { options: { id: string }[]; product_id: string })[],
|
||||||
variants: ProductVariant[]
|
variants: InferEntityType<typeof ProductVariant>[]
|
||||||
) {
|
) {
|
||||||
for (const variantData of data) {
|
for (const variantData of data) {
|
||||||
const existingVariant = variants.find((v) => {
|
const existingVariant = variants.find((v) => {
|
||||||
|
|||||||
Reference in new issue
Block a user