feat(product): Move mikro orm utils to the utils package (#4631)

Move utils to the utils package as much as possible

Co-authored-by: Shahed Nasser <27354907+shahednasser@users.noreply.github.com>
This commit is contained in:
Adrien de PerettiandShahed Nasser authored and GitHub committed 2023-07-31 11:30:43 +00:00
1 parent 648eb106d6
commit 4073b73130
39 files changed
+503 -355

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
---
"@medusajs/product": patch
"@medusajs/utils": patch
"@medusajs/types": patch
---
feat(product): Move mikro orm utils to the utils package
@@ -1,6 +1,6 @@
import { BaseRepository } from "../../src/repositories/base" import { DALUtils } from "@medusajs/utils"
class CustomRepository extends BaseRepository { class CustomRepository extends DALUtils.MikroOrmBaseRepository {
constructor({ manager }) { constructor({ manager }) {
// @ts-ignore // @ts-ignore
super(...arguments) super(...arguments)
@@ -1,7 +1,6 @@
import { IProductModuleService } from "@medusajs/types" import { IProductModuleService, ProductTypes } from "@medusajs/types"
import { Product, ProductCategory } from "@models" import { Product, ProductCategory } from "@models"
import { SqlEntityManager } from "@mikro-orm/postgresql" import { SqlEntityManager } from "@mikro-orm/postgresql"
import { ProductTypes } from "@medusajs/types"
import { initialize } from "../../../../src" import { initialize } from "../../../../src"
import { DB_URL, TestDatabase } from "../../../utils" import { DB_URL, TestDatabase } from "../../../utils"
@@ -43,15 +42,18 @@ describe("ProductModuleService product categories", () => {
status: ProductTypes.ProductStatus.PUBLISHED, status: ProductTypes.ProductStatus.PUBLISHED,
}) })
const productCategoriesData = [{ const productCategoriesData = [
{
id: "test-1", id: "test-1",
name: "category 1", name: "category 1",
products: [productOne], products: [productOne],
},{ },
{
id: "test-2", id: "test-2",
name: "category", name: "category",
products: [productTwo], products: [productTwo],
}] },
]
productCategories = await createProductCategories( productCategories = await createProductCategories(
testManager, testManager,
@@ -121,10 +123,12 @@ describe("ProductModuleService product categories", () => {
expect.objectContaining({ expect.objectContaining({
id: "test-1", id: "test-1",
name: "category 1", name: "category 1",
products: [expect.objectContaining({ products: [
expect.objectContaining({
id: "product-1", id: "product-1",
title: "product 1", title: "product 1",
})], }),
],
}), }),
]) ])
}) })
@@ -191,10 +195,12 @@ describe("ProductModuleService product categories", () => {
expect.objectContaining({ expect.objectContaining({
id: "test-1", id: "test-1",
name: "category 1", name: "category 1",
products: [expect.objectContaining({ products: [
expect.objectContaining({
id: "product-1", id: "product-1",
title: "product 1", title: "product 1",
})], }),
],
}), }),
]) ])
}) })
@@ -203,35 +209,34 @@ describe("ProductModuleService product categories", () => {
describe("retrieveCategory", () => { describe("retrieveCategory", () => {
it("should return the requested category", async () => { it("should return the requested category", async () => {
const result = await service.retrieveCategory(productCategoryOne.id, { const result = await service.retrieveCategory(productCategoryOne.id, {
select: ["id", "name"] select: ["id", "name"],
}) })
expect(result).toEqual( expect(result).toEqual(
expect.objectContaining({ expect.objectContaining({
id: "test-1", id: "test-1",
name: "category 1", name: "category 1",
}), })
) )
}) })
it("should return requested attributes when requested through config", async () => { it("should return requested attributes when requested through config", async () => {
const result = await service.retrieveCategory( const result = await service.retrieveCategory(productCategoryOne.id, {
productCategoryOne.id,
{
select: ["id", "name", "products.title"], select: ["id", "name", "products.title"],
relations: ["products"], relations: ["products"],
} })
)
expect(result).toEqual( expect(result).toEqual(
expect.objectContaining({ expect.objectContaining({
id: "test-1", id: "test-1",
name: "category 1", name: "category 1",
products: [expect.objectContaining({ products: [
expect.objectContaining({
id: "product-1", id: "product-1",
title: "product 1", title: "product 1",
})],
}), }),
],
})
) )
}) })
@@ -244,7 +249,9 @@ describe("ProductModuleService product categories", () => {
error = e error = e
} }
expect(error.message).toEqual("ProductCategory with id: does-not-exist was not found") expect(error.message).toEqual(
"ProductCategory with id: does-not-exist was not found"
)
}) })
}) })
@@ -255,11 +262,14 @@ describe("ProductModuleService product categories", () => {
parent_category_id: productCategoryOne.id, parent_category_id: productCategoryOne.id,
}) })
const [productCategory] = await service.listCategories({ const [productCategory] = await service.listCategories(
name: "New Category" {
}, { name: "New Category",
select: ["name", "rank"] },
}) {
select: ["name", "rank"],
}
)
expect(productCategory).toEqual( expect(productCategory).toEqual(
expect.objectContaining({ expect.objectContaining({
@@ -273,7 +283,7 @@ describe("ProductModuleService product categories", () => {
await service.createCategory({ await service.createCategory({
name: "New Category", name: "New Category",
parent_category_id: productCategoryOne.id, parent_category_id: productCategoryOne.id,
rank: 0 rank: 0,
}) })
await service.createCategory({ await service.createCategory({
@@ -281,11 +291,14 @@ describe("ProductModuleService product categories", () => {
parent_category_id: productCategoryOne.id, parent_category_id: productCategoryOne.id,
}) })
const [productCategoryNew] = await service.listCategories({ const [productCategoryNew] = await service.listCategories(
name: "New Category 2" {
}, { name: "New Category 2",
select: ["name", "rank"] },
}) {
select: ["name", "rank"],
}
)
expect(productCategoryNew).toEqual( expect(productCategoryNew).toEqual(
expect.objectContaining({ expect.objectContaining({
@@ -299,11 +312,14 @@ describe("ProductModuleService product categories", () => {
parent_category_id: productCategoryNew.id, parent_category_id: productCategoryNew.id,
}) })
const [productCategoryWithParent] = await service.listCategories({ const [productCategoryWithParent] = await service.listCategories(
name: "New Category 2.1" {
}, { name: "New Category 2.1",
select: ["name", "rank", "parent_category_id"] },
}) {
select: ["name", "rank", "parent_category_id"],
}
)
expect(productCategoryWithParent).toEqual( expect(productCategoryWithParent).toEqual(
expect.objectContaining({ expect.objectContaining({
@@ -342,12 +358,15 @@ describe("ProductModuleService product categories", () => {
it("should update the name of the category successfully", async () => { it("should update the name of the category successfully", async () => {
await service.updateCategory(productCategoryZero.id, { await service.updateCategory(productCategoryZero.id, {
name: "New Category" name: "New Category",
}) })
const productCategory = await service.retrieveCategory(productCategoryZero.id, { const productCategory = await service.retrieveCategory(
select: ["name"] productCategoryZero.id,
}) {
select: ["name"],
}
)
expect(productCategory.name).toEqual("New Category") expect(productCategory.name).toEqual("New Category")
}) })
@@ -357,13 +376,15 @@ describe("ProductModuleService product categories", () => {
try { try {
await service.updateCategory("does-not-exist", { await service.updateCategory("does-not-exist", {
name: "New Category" name: "New Category",
}) })
} catch (e) { } catch (e) {
error = e error = e
} }
expect(error.message).toEqual(`ProductCategory not found ({ id: 'does-not-exist' })`) expect(error.message).toEqual(
`ProductCategory not found ({ id: 'does-not-exist' })`
)
}) })
it("should reorder rank successfully in the same parent", async () => { it("should reorder rank successfully in the same parent", async () => {
@@ -371,11 +392,14 @@ describe("ProductModuleService product categories", () => {
rank: 0, rank: 0,
}) })
const productCategories = await service.listCategories({ const productCategories = await service.listCategories(
parent_category_id: null {
}, { parent_category_id: null,
select: ["name", "rank"] },
}) {
select: ["name", "rank"],
}
)
expect(productCategories).toEqual( expect(productCategories).toEqual(
expect.arrayContaining([ expect.arrayContaining([
@@ -390,7 +414,7 @@ describe("ProductModuleService product categories", () => {
expect.objectContaining({ expect.objectContaining({
id: productCategoryOne.id, id: productCategoryOne.id,
rank: "2", rank: "2",
}) }),
]) ])
) )
}) })
@@ -398,14 +422,17 @@ describe("ProductModuleService product categories", () => {
it("should reorder rank successfully when changing parent", async () => { it("should reorder rank successfully when changing parent", async () => {
await service.updateCategory(productCategoryTwo.id, { await service.updateCategory(productCategoryTwo.id, {
rank: 0, rank: 0,
parent_category_id: productCategoryZero.id parent_category_id: productCategoryZero.id,
}) })
const productCategories = await service.listCategories({ const productCategories = await service.listCategories(
parent_category_id: productCategoryZero.id {
}, { parent_category_id: productCategoryZero.id,
select: ["name", "rank"] },
}) {
select: ["name", "rank"],
}
)
expect(productCategories).toEqual( expect(productCategories).toEqual(
expect.arrayContaining([ expect.arrayContaining([
@@ -424,7 +451,7 @@ describe("ProductModuleService product categories", () => {
expect.objectContaining({ expect.objectContaining({
id: productCategoryZeroTwo.id, id: productCategoryZeroTwo.id,
rank: "3", rank: "3",
}) }),
]) ])
) )
}) })
@@ -432,14 +459,17 @@ describe("ProductModuleService product categories", () => {
it("should reorder rank successfully when changing parent and in first position", async () => { it("should reorder rank successfully when changing parent and in first position", async () => {
await service.updateCategory(productCategoryTwo.id, { await service.updateCategory(productCategoryTwo.id, {
rank: 0, rank: 0,
parent_category_id: productCategoryZero.id parent_category_id: productCategoryZero.id,
}) })
const productCategories = await service.listCategories({ const productCategories = await service.listCategories(
parent_category_id: productCategoryZero.id {
}, { parent_category_id: productCategoryZero.id,
select: ["name", "rank"] },
}) {
select: ["name", "rank"],
}
)
expect(productCategories).toEqual( expect(productCategories).toEqual(
expect.arrayContaining([ expect.arrayContaining([
@@ -458,7 +488,7 @@ describe("ProductModuleService product categories", () => {
expect.objectContaining({ expect.objectContaining({
id: productCategoryZeroTwo.id, id: productCategoryZeroTwo.id,
rank: "3", rank: "3",
}) }),
]) ])
) )
}) })
@@ -492,7 +522,9 @@ describe("ProductModuleService product categories", () => {
error = e error = e
} }
expect(error.message).toEqual(`ProductCategory not found ({ id: 'does-not-exist' })`) expect(error.message).toEqual(
`ProductCategory not found ({ id: 'does-not-exist' })`
)
}) })
it("should throw an error when it has children", async () => { it("should throw an error when it has children", async () => {
@@ -504,17 +536,22 @@ describe("ProductModuleService product categories", () => {
error = e error = e
} }
expect(error.message).toEqual(`Deleting ProductCategory (category-0-0) with category children is not allowed`) expect(error.message).toEqual(
`Deleting ProductCategory (category-0-0) with category children is not allowed`
)
}) })
it("should reorder siblings rank successfully on deleting", async () => { it("should reorder siblings rank successfully on deleting", async () => {
await service.deleteCategory(productCategoryOne.id) await service.deleteCategory(productCategoryOne.id)
const productCategories = await service.listCategories({ const productCategories = await service.listCategories(
parent_category_id: null {
}, { parent_category_id: null,
select: ["id", "rank"] },
}) {
select: ["id", "rank"],
}
)
expect(productCategories).toEqual( expect(productCategories).toEqual(
expect.arrayContaining([ expect.arrayContaining([
@@ -525,10 +562,9 @@ describe("ProductModuleService product categories", () => {
expect.objectContaining({ expect.objectContaining({
id: productCategoryTwo.id, id: productCategoryTwo.id,
rank: "1", rank: "1",
}) }),
]) ])
) )
}) })
}) })
}) })
+2 -3
View File
@@ -6,12 +6,11 @@ import {
MODULE_RESOURCE_TYPE, MODULE_RESOURCE_TYPE,
MODULE_SCOPE, MODULE_SCOPE,
} from "@medusajs/modules-sdk" } from "@medusajs/modules-sdk"
import { MedusaError, ModulesSdkUtils } from "@medusajs/utils" import { DALUtils, MedusaError, ModulesSdkUtils } from "@medusajs/utils"
import { EntitySchema } from "@mikro-orm/core" import { EntitySchema } from "@mikro-orm/core"
import * as ProductModels from "@models" import * as ProductModels from "@models"
import { createConnection } from "../utils"
import { ConfigModule, ModulesSdkTypes } from "@medusajs/types" import { ConfigModule, ModulesSdkTypes } from "@medusajs/types"
export default async ( export default async (
@@ -61,7 +60,7 @@ async function loadDefault({ database, container }) {
} }
const entities = Object.values(ProductModels) as unknown as EntitySchema[] const entities = Object.values(ProductModels) as unknown as EntitySchema[]
const orm = await createConnection(database, entities) const orm = await DALUtils.mikroOrmCreateConnection(database, entities)
container.register({ container.register({
manager: asValue(orm.em.fork()), manager: asValue(orm.em.fork()),
@@ -1,5 +1,4 @@
import * as DefaultRepositories from "@repositories" import * as DefaultRepositories from "@repositories"
import { import {
BaseRepository, BaseRepository,
ProductCategoryRepository, ProductCategoryRepository,
@@ -2,6 +2,7 @@ import {
BeforeCreate, BeforeCreate,
Collection, Collection,
Entity, Entity,
Filter,
Index, Index,
OneToMany, OneToMany,
OptionalProps, OptionalProps,
@@ -10,14 +11,13 @@ import {
Unique, Unique,
} from "@mikro-orm/core" } from "@mikro-orm/core"
import { generateEntityId, kebabCase } from "@medusajs/utils" import { DALUtils, generateEntityId, kebabCase } from "@medusajs/utils"
import Product from "./product" import Product from "./product"
import { SoftDeletable } from "../utils"
type OptionalRelations = "products" type OptionalRelations = "products"
@Entity({ tableName: "product_collection" }) @Entity({ tableName: "product_collection" })
@SoftDeletable() @Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class ProductCollection { class ProductCollection {
[OptionalProps]?: OptionalRelations [OptionalProps]?: OptionalRelations
+3 -3
View File
@@ -2,6 +2,7 @@ import {
BeforeCreate, BeforeCreate,
Collection, Collection,
Entity, Entity,
Filter,
Index, Index,
ManyToMany, ManyToMany,
OptionalProps, OptionalProps,
@@ -9,14 +10,13 @@ import {
Property, Property,
} from "@mikro-orm/core" } from "@mikro-orm/core"
import { generateEntityId } from "@medusajs/utils" import { DALUtils, generateEntityId } from "@medusajs/utils"
import Product from "./product" import Product from "./product"
import { SoftDeletable } from "../utils"
type OptionalRelations = "products" type OptionalRelations = "products"
@Entity({ tableName: "image" }) @Entity({ tableName: "image" })
@SoftDeletable() @Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class ProductImage { class ProductImage {
[OptionalProps]?: OptionalRelations [OptionalProps]?: OptionalRelations
@@ -1,6 +1,7 @@
import { import {
BeforeCreate, BeforeCreate,
Entity, Entity,
Filter,
Index, Index,
ManyToOne, ManyToOne,
OptionalProps, OptionalProps,
@@ -8,9 +9,7 @@ import {
Property, Property,
} from "@mikro-orm/core" } from "@mikro-orm/core"
import { ProductOption, ProductVariant } from "./index" import { ProductOption, ProductVariant } from "./index"
import { DALUtils, generateEntityId } from "@medusajs/utils"
import { SoftDeletable } from "../utils"
import { generateEntityId } from "@medusajs/utils"
type OptionalFields = type OptionalFields =
| "created_at" | "created_at"
@@ -22,7 +21,7 @@ type OptionalFields =
type OptionalRelations = "product" | "option" | "variant" type OptionalRelations = "product" | "option" | "variant"
@Entity({ tableName: "product_option_value" }) @Entity({ tableName: "product_option_value" })
@SoftDeletable() @Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class ProductOptionValue { class ProductOptionValue {
[OptionalProps]?: OptionalFields | OptionalRelations [OptionalProps]?: OptionalFields | OptionalRelations
@@ -1,9 +1,10 @@
import { generateEntityId } from "@medusajs/utils" import { DALUtils, generateEntityId } from "@medusajs/utils"
import { import {
BeforeCreate, BeforeCreate,
Cascade, Cascade,
Collection, Collection,
Entity, Entity,
Filter,
Index, Index,
ManyToOne, ManyToOne,
OneToMany, OneToMany,
@@ -13,13 +14,12 @@ import {
} from "@mikro-orm/core" } from "@mikro-orm/core"
import { Product } from "./index" import { Product } from "./index"
import ProductOptionValue from "./product-option-value" import ProductOptionValue from "./product-option-value"
import { SoftDeletable } from "../utils"
type OptionalRelations = "values" | "product" type OptionalRelations = "values" | "product"
type OptionalFields = "product_id" type OptionalFields = "product_id"
@Entity({ tableName: "product_option" }) @Entity({ tableName: "product_option" })
@SoftDeletable() @Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class ProductOption { class ProductOption {
[OptionalProps]?: OptionalRelations | OptionalFields [OptionalProps]?: OptionalRelations | OptionalFields
+3 -3
View File
@@ -2,6 +2,7 @@ import {
BeforeCreate, BeforeCreate,
Collection, Collection,
Entity, Entity,
Filter,
Index, Index,
ManyToMany, ManyToMany,
OptionalProps, OptionalProps,
@@ -9,14 +10,13 @@ import {
Property, Property,
} from "@mikro-orm/core" } from "@mikro-orm/core"
import { generateEntityId } from "@medusajs/utils" import { DALUtils, generateEntityId } from "@medusajs/utils"
import Product from "./product" import Product from "./product"
import { SoftDeletable } from "../utils"
type OptionalRelations = "products" type OptionalRelations = "products"
@Entity({ tableName: "product_tag" }) @Entity({ tableName: "product_tag" })
@SoftDeletable() @Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class ProductTag { class ProductTag {
[OptionalProps]?: OptionalRelations [OptionalProps]?: OptionalRelations
+3 -3
View File
@@ -1,16 +1,16 @@
import { import {
BeforeCreate, BeforeCreate,
Entity, Entity,
Filter,
Index, Index,
PrimaryKey, PrimaryKey,
Property, Property,
} from "@mikro-orm/core" } from "@mikro-orm/core"
import { generateEntityId } from "@medusajs/utils" import { DALUtils, generateEntityId } from "@medusajs/utils"
import { SoftDeletable } from "../utils"
@Entity({ tableName: "product_type" }) @Entity({ tableName: "product_type" })
@SoftDeletable() @Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class ProductType { class ProductType {
@PrimaryKey({ columnType: "text" }) @PrimaryKey({ columnType: "text" })
id!: string id!: string
@@ -1,9 +1,10 @@
import { generateEntityId } from "@medusajs/utils" import { DALUtils, generateEntityId } from "@medusajs/utils"
import { import {
BeforeCreate, BeforeCreate,
Cascade, Cascade,
Collection, Collection,
Entity, Entity,
Filter,
Index, Index,
ManyToOne, ManyToOne,
OneToMany, OneToMany,
@@ -14,7 +15,6 @@ import {
} from "@mikro-orm/core" } from "@mikro-orm/core"
import { Product } from "@models" import { Product } from "@models"
import ProductOptionValue from "./product-option-value" import ProductOptionValue from "./product-option-value"
import { SoftDeletable } from "../utils"
type OptionalFields = type OptionalFields =
| "created_at" | "created_at"
@@ -25,7 +25,7 @@ type OptionalFields =
| "product_id" | "product_id"
@Entity({ tableName: "product_variant" }) @Entity({ tableName: "product_variant" })
@SoftDeletable() @Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class ProductVariant { class ProductVariant {
[OptionalProps]?: OptionalFields [OptionalProps]?: OptionalFields
+3 -3
View File
@@ -3,6 +3,7 @@ import {
Collection, Collection,
Entity, Entity,
Enum, Enum,
Filter,
Index, Index,
ManyToMany, ManyToMany,
ManyToOne, ManyToOne,
@@ -14,7 +15,7 @@ import {
} from "@mikro-orm/core" } from "@mikro-orm/core"
import { ProductTypes } from "@medusajs/types" import { ProductTypes } from "@medusajs/types"
import { generateEntityId, kebabCase } from "@medusajs/utils" import { DALUtils, generateEntityId, kebabCase } from "@medusajs/utils"
import ProductCategory from "./product-category" import ProductCategory from "./product-category"
import ProductCollection from "./product-collection" import ProductCollection from "./product-collection"
import ProductOption from "./product-option" import ProductOption from "./product-option"
@@ -22,7 +23,6 @@ import ProductTag from "./product-tag"
import ProductType from "./product-type" import ProductType from "./product-type"
import ProductVariant from "./product-variant" import ProductVariant from "./product-variant"
import ProductImage from "./product-image" import ProductImage from "./product-image"
import { SoftDeletable } from "../utils"
type OptionalRelations = "collection" | "type" type OptionalRelations = "collection" | "type"
type OptionalFields = type OptionalFields =
@@ -34,7 +34,7 @@ type OptionalFields =
| "updated_at" | "updated_at"
@Entity({ tableName: "product" }) @Entity({ tableName: "product" })
@SoftDeletable() @Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class Product { class Product {
[OptionalProps]?: OptionalRelations | OptionalFields [OptionalProps]?: OptionalRelations | OptionalFields
+1 -1
View File
@@ -1,4 +1,4 @@
export { BaseRepository } from "./base" export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
export { ProductRepository } from "./product" export { ProductRepository } from "./product"
export { ProductTagRepository } from "./product-tag" export { ProductTagRepository } from "./product-tag"
export { ProductVariantRepository } from "./product-variant" export { ProductVariantRepository } from "./product-variant"
@@ -3,12 +3,17 @@ import {
FindOptions as MikroOptions, FindOptions as MikroOptions,
LoadStrategy, LoadStrategy,
} from "@mikro-orm/core" } from "@mikro-orm/core"
import { Product, ProductCategory } from "@models" import { ProductCategory } from "@models"
import { Context, DAL, ProductCategoryTransformOptions } from "@medusajs/types" import { Context, DAL, ProductCategoryTransformOptions } from "@medusajs/types"
import groupBy from "lodash/groupBy" import groupBy from "lodash/groupBy"
import { BaseTreeRepository } from "./base"
import { SqlEntityManager } from "@mikro-orm/postgresql" import { SqlEntityManager } from "@mikro-orm/postgresql"
import { InjectTransactionManager, MedusaContext, isDefined, MedusaError } from "@medusajs/utils" import {
DALUtils,
InjectTransactionManager,
isDefined,
MedusaContext,
MedusaError,
} from "@medusajs/utils"
import { ProductCategoryServiceTypes } from "../types" import { ProductCategoryServiceTypes } from "../types"
@@ -25,7 +30,7 @@ export type ReorderConditions = {
} }
export const tempReorderRank = 99999 export const tempReorderRank = 99999
export class ProductCategoryRepository extends BaseTreeRepository { export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeRepository {
protected readonly manager_: SqlEntityManager protected readonly manager_: SqlEntityManager
constructor({ manager }: { manager: SqlEntityManager }) { constructor({ manager }: { manager: SqlEntityManager }) {
@@ -209,12 +214,9 @@ export class ProductCategoryRepository extends BaseTreeRepository {
): Promise<ProductCategory> { ): Promise<ProductCategory> {
const categoryData = { ...data } const categoryData = { ...data }
const manager = this.getActiveManager<SqlEntityManager>(sharedContext) const manager = this.getActiveManager<SqlEntityManager>(sharedContext)
const siblings = await manager.find( const siblings = await manager.find(ProductCategory, {
ProductCategory, parent_category_id: categoryData?.parent_category_id || null,
{ })
parent_category_id: categoryData?.parent_category_id || null
},
)
if (!isDefined(categoryData.rank)) { if (!isDefined(categoryData.rank)) {
categoryData.rank = siblings.length categoryData.rank = siblings.length
@@ -343,25 +345,19 @@ export class ProductCategoryRepository extends BaseTreeRepository {
// The current sibling count will replace targetRank if // The current sibling count will replace targetRank if
// targetRank is greater than the count of siblings. // targetRank is greater than the count of siblings.
const siblingCount = await manager.count( const siblingCount = await manager.count(ProductCategory, {
ProductCategory,
{
parent_category_id: targetParentId || null, parent_category_id: targetParentId || null,
id: { $ne: targetCategoryId }, id: { $ne: targetCategoryId },
} })
)
// The category record that will be placed at the requested rank // The category record that will be placed at the requested rank
// We've temporarily placed it at a temporary rank that is // We've temporarily placed it at a temporary rank that is
// beyond a reasonable value (tempReorderRank) // beyond a reasonable value (tempReorderRank)
const targetCategory = await manager.findOne( const targetCategory = await manager.findOne(ProductCategory, {
ProductCategory,
{
id: targetCategoryId, id: targetCategoryId,
parent_category_id: targetParentId || null, parent_category_id: targetParentId || null,
rank: tempReorderRank, rank: tempReorderRank,
} })
)
// If the targetRank is not present, or if targetRank is beyond the // If the targetRank is not present, or if targetRank is beyond the
// rank of the last category, we set the rank as the last rank // rank of the last category, we set the rank as the last rank
@@ -7,14 +7,13 @@ import {
import { Context, DAL, ProductTypes } from "@medusajs/types" import { Context, DAL, ProductTypes } from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql" import { SqlEntityManager } from "@mikro-orm/postgresql"
import { import {
DALUtils,
InjectTransactionManager, InjectTransactionManager,
MedusaContext, MedusaContext,
MedusaError, MedusaError,
} from "@medusajs/utils" } from "@medusajs/utils"
import { BaseRepository } from "./base" export class ProductCollectionRepository extends DALUtils.MikroOrmBaseRepository {
export class ProductCollectionRepository extends BaseRepository {
protected readonly manager_: SqlEntityManager protected readonly manager_: SqlEntityManager
constructor({ manager }: { manager: SqlEntityManager }) { constructor({ manager }: { manager: SqlEntityManager }) {
@@ -5,11 +5,14 @@ import {
} from "@mikro-orm/core" } from "@mikro-orm/core"
import { Context, DAL } from "@medusajs/types" import { Context, DAL } from "@medusajs/types"
import { Image, Product } from "@models" import { Image, Product } from "@models"
import { AbstractBaseRepository } from "./base"
import { SqlEntityManager } from "@mikro-orm/postgresql" import { SqlEntityManager } from "@mikro-orm/postgresql"
import { InjectTransactionManager, MedusaContext } from "@medusajs/utils" import {
DALUtils,
InjectTransactionManager,
MedusaContext,
} from "@medusajs/utils"
export class ProductImageRepository extends AbstractBaseRepository<Image> { export class ProductImageRepository extends DALUtils.MikroOrmAbstractBaseRepository<Image> {
protected readonly manager_: SqlEntityManager protected readonly manager_: SqlEntityManager
constructor({ manager }: { manager: SqlEntityManager }) { constructor({ manager }: { manager: SqlEntityManager }) {
@@ -5,15 +5,15 @@ import {
} from "@mikro-orm/core" } from "@mikro-orm/core"
import { Product, ProductOption } from "@models" import { Product, ProductOption } from "@models"
import { Context, DAL, ProductTypes } from "@medusajs/types" import { Context, DAL, ProductTypes } from "@medusajs/types"
import { AbstractBaseRepository } from "./base"
import { SqlEntityManager } from "@mikro-orm/postgresql" import { SqlEntityManager } from "@mikro-orm/postgresql"
import { import {
DALUtils,
InjectTransactionManager, InjectTransactionManager,
MedusaContext, MedusaContext,
MedusaError, MedusaError,
} from "@medusajs/utils" } from "@medusajs/utils"
export class ProductOptionRepository extends AbstractBaseRepository<ProductOption> { export class ProductOptionRepository extends DALUtils.MikroOrmAbstractBaseRepository<ProductOption> {
protected readonly manager_: SqlEntityManager protected readonly manager_: SqlEntityManager
constructor({ manager }: { manager: SqlEntityManager }) { constructor({ manager }: { manager: SqlEntityManager }) {
@@ -88,13 +88,15 @@ export class ProductOptionRepository extends AbstractBaseRepository<ProductOptio
data.forEach((d) => d.product_id && productIds.push(d.product_id)) data.forEach((d) => d.product_id && productIds.push(d.product_id))
const existingProducts = await manager.find( const existingProducts = await manager.find(Product, {
Product, id: { $in: productIds },
{ id: { $in: productIds } }, })
)
const existingProductsMap = new Map( const existingProductsMap = new Map(
existingProducts.map<[string, Product]>((product) => [product.id, product]) existingProducts.map<[string, Product]>((product) => [
product.id,
product,
])
) )
const productOptions = data.map((optionData) => { const productOptions = data.map((optionData) => {
@@ -136,7 +138,10 @@ export class ProductOptionRepository extends AbstractBaseRepository<ProductOptio
) )
const existingOptionsMap = new Map( const existingOptionsMap = new Map(
existingOptions.map<[string, ProductOption]>((option) => [option.id, option]) existingOptions.map<[string, ProductOption]>((option) => [
option.id,
option,
])
) )
const productOptions = data.map((optionData) => { const productOptions = data.map((optionData) => {
@@ -12,15 +12,15 @@ import {
UpdateProductTagDTO, UpdateProductTagDTO,
UpsertProductTagDTO, UpsertProductTagDTO,
} from "@medusajs/types" } from "@medusajs/types"
import { BaseRepository } from "./base"
import { SqlEntityManager } from "@mikro-orm/postgresql" import { SqlEntityManager } from "@mikro-orm/postgresql"
import { import {
DALUtils,
InjectTransactionManager, InjectTransactionManager,
MedusaContext, MedusaContext,
MedusaError, MedusaError,
} from "@medusajs/utils" } from "@medusajs/utils"
export class ProductTagRepository extends BaseRepository { export class ProductTagRepository extends DALUtils.MikroOrmBaseRepository {
protected readonly manager_: SqlEntityManager protected readonly manager_: SqlEntityManager
constructor({ manager }: { manager: SqlEntityManager }) { constructor({ manager }: { manager: SqlEntityManager }) {
@@ -13,14 +13,13 @@ import {
} from "@medusajs/types" } from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql" import { SqlEntityManager } from "@mikro-orm/postgresql"
import { import {
DALUtils,
InjectTransactionManager, InjectTransactionManager,
MedusaContext, MedusaContext,
MedusaError, MedusaError,
} from "@medusajs/utils" } from "@medusajs/utils"
import { BaseRepository } from "./base" export class ProductTypeRepository extends DALUtils.MikroOrmBaseRepository {
export class ProductTypeRepository extends BaseRepository {
protected readonly manager_: SqlEntityManager protected readonly manager_: SqlEntityManager
constructor({ manager }: { manager: SqlEntityManager }) { constructor({ manager }: { manager: SqlEntityManager }) {
@@ -8,17 +8,15 @@ import { ProductVariant } from "@models"
import { Context, DAL, WithRequiredProperty } from "@medusajs/types" import { Context, DAL, WithRequiredProperty } from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql" import { SqlEntityManager } from "@mikro-orm/postgresql"
import { import {
MedusaError, DALUtils,
isDefined,
InjectTransactionManager, InjectTransactionManager,
MedusaContext, MedusaContext,
MedusaError,
} from "@medusajs/utils" } from "@medusajs/utils"
import { ProductVariantServiceTypes } from "../types/services" import { ProductVariantServiceTypes } from "../types/services"
import { AbstractBaseRepository } from "./base"
import { doNotForceTransaction } from "../utils"
export class ProductVariantRepository extends AbstractBaseRepository<ProductVariant> { export class ProductVariantRepository extends DALUtils.MikroOrmAbstractBaseRepository<ProductVariant> {
protected readonly manager_: SqlEntityManager protected readonly manager_: SqlEntityManager
constructor({ manager }: { manager: SqlEntityManager }) { constructor({ manager }: { manager: SqlEntityManager }) {
@@ -96,14 +94,17 @@ export class ProductVariantRepository extends AbstractBaseRepository<ProductVari
} }
async update( async update(
data: WithRequiredProperty<ProductVariantServiceTypes.UpdateProductVariantDTO, "id">[], data: WithRequiredProperty<
ProductVariantServiceTypes.UpdateProductVariantDTO,
"id"
>[],
context: Context = {} context: Context = {}
): Promise<ProductVariant[]> { ): Promise<ProductVariant[]> {
const manager = (context.transactionManager ?? const manager = (context.transactionManager ??
this.manager_) as SqlEntityManager this.manager_) as SqlEntityManager
const productVariantsToUpdate = await manager.find(ProductVariant, { const productVariantsToUpdate = await manager.find(ProductVariant, {
id: data.map((updateData) => updateData.id) id: data.map((updateData) => updateData.id),
}) })
const productVariantsToUpdateMap = new Map<string, ProductVariant>( const productVariantsToUpdateMap = new Map<string, ProductVariant>(
+56 -37
View File
@@ -2,15 +2,14 @@ import {
Product, Product,
ProductCategory, ProductCategory,
ProductCollection, ProductCollection,
ProductType,
ProductTag, ProductTag,
ProductType,
} from "@models" } from "@models"
import { import {
FilterQuery as MikroFilterQuery, FilterQuery as MikroFilterQuery,
FindOptions as MikroOptions, FindOptions as MikroOptions,
LoadStrategy, LoadStrategy,
wrap
} from "@mikro-orm/core" } from "@mikro-orm/core"
import { import {
@@ -20,12 +19,17 @@ import {
WithRequiredProperty, WithRequiredProperty,
} from "@medusajs/types" } from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql" import { SqlEntityManager } from "@mikro-orm/postgresql"
import { MedusaError, isDefined, InjectTransactionManager, MedusaContext } from "@medusajs/utils" import {
DALUtils,
InjectTransactionManager,
isDefined,
MedusaContext,
MedusaError,
} from "@medusajs/utils"
import { AbstractBaseRepository } from "./base"
import { ProductServiceTypes } from "../types/services" import { ProductServiceTypes } from "../types/services"
export class ProductRepository extends AbstractBaseRepository<Product> { export class ProductRepository extends DALUtils.MikroOrmAbstractBaseRepository<Product> {
protected readonly manager_: SqlEntityManager protected readonly manager_: SqlEntityManager
constructor({ manager }: { manager: SqlEntityManager }) { constructor({ manager }: { manager: SqlEntityManager }) {
@@ -157,12 +161,10 @@ export class ProductRepository extends AbstractBaseRepository<Product> {
data.forEach((productData) => { data.forEach((productData) => {
categoryIds = categoryIds.concat( categoryIds = categoryIds.concat(
productData?.categories?.map(c => c.id) || [] productData?.categories?.map((c) => c.id) || []
) )
tagIds = tagIds.concat( tagIds = tagIds.concat(productData?.tags?.map((c) => c.id) || [])
productData?.tags?.map(c => c.id) || []
)
if (productData.collection_id) { if (productData.collection_id) {
collectionIds.push(productData.collection_id) collectionIds.push(productData.collection_id)
@@ -173,27 +175,39 @@ export class ProductRepository extends AbstractBaseRepository<Product> {
} }
}) })
const productsToUpdate = await manager.find(Product, { const productsToUpdate = await manager.find(
id: data.map((updateData) => updateData.id) Product,
}, { {
populate: ["tags", "categories"] id: data.map((updateData) => updateData.id),
},
{
populate: ["tags", "categories"],
}
)
const collectionsToAssign = collectionIds.length
? await manager.find(ProductCollection, {
id: collectionIds,
}) })
: []
const collectionsToAssign = collectionIds.length ? await manager.find(ProductCollection, { const typesToAssign = typeIds.length
id: collectionIds ? await manager.find(ProductType, {
}) : [] id: typeIds,
})
: []
const typesToAssign = typeIds.length ? await manager.find(ProductType, { const categoriesToAssign = categoryIds.length
id: typeIds ? await manager.find(ProductCategory, {
}) : [] id: categoryIds,
})
: []
const categoriesToAssign = categoryIds.length ? await manager.find(ProductCategory, { const tagsToAssign = tagIds.length
id: categoryIds ? await manager.find(ProductTag, {
}) : [] id: tagIds,
})
const tagsToAssign = tagIds.length ? await manager.find(ProductTag, { : []
id: tagIds
}) : []
const categoriesToAssignMap = new Map<string, ProductCategory>( const categoriesToAssignMap = new Map<string, ProductCategory>(
categoriesToAssign.map((category) => [category.id, category]) categoriesToAssign.map((category) => [category.id, category])
@@ -227,8 +241,8 @@ export class ProductRepository extends AbstractBaseRepository<Product> {
} }
const { const {
categories: categoriesData, categories: categoriesData = [],
tags: tagsData, tags: tagsData = [],
collection_id: collectionId, collection_id: collectionId,
type_id: typeId, type_id: typeId,
} = updateData } = updateData
@@ -249,9 +263,14 @@ export class ProductRepository extends AbstractBaseRepository<Product> {
} }
} }
const categoryIdsToAssignSet = new Set(categoriesData.map(cd => cd.id)) const categoryIdsToAssignSet = new Set(
const categoriesToDelete = product.categories.getItems().filter( categoriesData.map((cd) => cd.id)
(existingCategory) => !categoryIdsToAssignSet.has(existingCategory.id) )
const categoriesToDelete = product.categories
.getItems()
.filter(
(existingCategory) =>
!categoryIdsToAssignSet.has(existingCategory.id)
) )
await product.categories.remove(categoriesToDelete) await product.categories.remove(categoriesToDelete)
@@ -272,22 +291,22 @@ export class ProductRepository extends AbstractBaseRepository<Product> {
} }
} }
const tagIdsToAssignSet = new Set(tagsData.map(cd => cd.id)) const tagIdsToAssignSet = new Set(tagsData.map((cd) => cd.id))
const tagsToDelete = product.tags.getItems().filter( const tagsToDelete = product.tags
(existingTag) => !tagIdsToAssignSet.has(existingTag.id) .getItems()
) .filter((existingTag) => !tagIdsToAssignSet.has(existingTag.id))
await product.tags.remove(tagsToDelete) await product.tags.remove(tagsToDelete)
} }
if (isDefined(collectionId)) { if (isDefined(collectionId)) {
const collection = collectionsToAssignMap.get(collectionId) const collection = collectionsToAssignMap.get(collectionId!)
product.collection = collection || null product.collection = collection || null
} }
if (isDefined(typeId)) { if (isDefined(typeId)) {
const type = typesToAssignMap.get(typeId) const type = typesToAssignMap.get(typeId!)
if (type) { if (type) {
product.type = type product.type = type
@@ -3,8 +3,7 @@ import * as ProductModels from "@models"
import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types" import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types"
import { EntitySchema } from "@mikro-orm/core" import { EntitySchema } from "@mikro-orm/core"
import { ModulesSdkUtils } from "@medusajs/utils" import { DALUtils, ModulesSdkUtils } from "@medusajs/utils"
import { createConnection } from "../utils"
/** /**
* This script is only valid for mikro orm managers. If a user provide a custom manager * This script is only valid for mikro orm managers. If a user provide a custom manager
@@ -28,7 +27,7 @@ export async function revertMigration({
const dbData = ModulesSdkUtils.loadDatabaseConfig("product", options) const dbData = ModulesSdkUtils.loadDatabaseConfig("product", options)
const entities = Object.values(ProductModels) as unknown as EntitySchema[] const entities = Object.values(ProductModels) as unknown as EntitySchema[]
const orm = await createConnection(dbData, entities) const orm = await DALUtils.mikroOrmCreateConnection(dbData, entities)
try { try {
const migrator = orm.getMigrator() const migrator = orm.getMigrator()
+2 -3
View File
@@ -1,8 +1,7 @@
import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types" import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types"
import { createConnection } from "../utils"
import * as ProductModels from "@models" import * as ProductModels from "@models"
import { EntitySchema } from "@mikro-orm/core" import { EntitySchema } from "@mikro-orm/core"
import { ModulesSdkUtils } from "@medusajs/utils" import { DALUtils, ModulesSdkUtils } from "@medusajs/utils"
/** /**
* This script is only valid for mikro orm managers. If a user provide a custom manager * This script is only valid for mikro orm managers. If a user provide a custom manager
@@ -26,7 +25,7 @@ export async function runMigrations({
const dbData = ModulesSdkUtils.loadDatabaseConfig("product", options) const dbData = ModulesSdkUtils.loadDatabaseConfig("product", options)
const entities = Object.values(ProductModels) as unknown as EntitySchema[] const entities = Object.values(ProductModels) as unknown as EntitySchema[]
const orm = await createConnection(dbData, entities) const orm = await DALUtils.mikroOrmCreateConnection(dbData, entities)
try { try {
const migrator = orm.getMigrator() const migrator = orm.getMigrator()
+2 -3
View File
@@ -1,4 +1,3 @@
import { createConnection } from "../utils"
import * as ProductModels from "@models" import * as ProductModels from "@models"
import { Product, ProductCategory, ProductVariant } from "@models" import { Product, ProductCategory, ProductVariant } from "@models"
import { EntitySchema } from "@mikro-orm/core" import { EntitySchema } from "@mikro-orm/core"
@@ -6,7 +5,7 @@ import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types"
import { EOL } from "os" import { EOL } from "os"
import { SqlEntityManager } from "@mikro-orm/postgresql" import { SqlEntityManager } from "@mikro-orm/postgresql"
import { resolve } from "path" import { resolve } from "path"
import { ModulesSdkUtils } from "@medusajs/utils" import { DALUtils, ModulesSdkUtils } from "@medusajs/utils"
export async function run({ export async function run({
options, options,
@@ -38,7 +37,7 @@ export async function run({
const dbData = ModulesSdkUtils.loadDatabaseConfig("product", options) const dbData = ModulesSdkUtils.loadDatabaseConfig("product", options)
const entities = Object.values(ProductModels) as unknown as EntitySchema[] const entities = Object.values(ProductModels) as unknown as EntitySchema[]
const orm = await createConnection(dbData, entities) const orm = await DALUtils.mikroOrmCreateConnection(dbData, entities)
const manager = orm.em.fork() const manager = orm.em.fork()
try { try {
-3
View File
@@ -1,8 +1,5 @@
import { MODULE_RESOURCE_TYPE } from "@medusajs/types" import { MODULE_RESOURCE_TYPE } from "@medusajs/types"
export * from "./create-connection"
export * from "./soft-deletable"
export function shouldForceTransaction(target: any): boolean { export function shouldForceTransaction(target: any): boolean {
return target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED return target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
} }
@@ -1,23 +0,0 @@
// TODO: Should we create a mikro orm specific package for this and the base repository?
import { Filter } from "@mikro-orm/core"
import { SoftDeletableFilterKey } from "@medusajs/utils"
interface FilterArguments {
withDeleted?: boolean
}
export const SoftDeletable = (): ClassDecorator => {
return Filter({
name: SoftDeletableFilterKey,
cond: ({ withDeleted }: FilterArguments = {}) => {
if (withDeleted) {
return {}
}
return {
deleted_at: null,
}
},
default: true,
})
}
+3 -6
View File
@@ -35,13 +35,9 @@ export interface RepositoryService<T = any> extends BaseRepositoryService<T> {
context?: Context context?: Context
): Promise<[T[], number]> ): Promise<[T[], number]>
// Only required for some repositories
upsert?(data: any, context?: Context): Promise<T[]>
create(data: unknown[], context?: Context): Promise<T[]> create(data: unknown[], context?: Context): Promise<T[]>
// TODO: remove optionality when all the other repositories have an update update(data: unknown[], context?: Context): Promise<T[]>
update?(data: unknown[], context?: Context): Promise<T[]>
delete(ids: string[], context?: Context): Promise<void> delete(ids: string[], context?: Context): Promise<void>
@@ -50,7 +46,8 @@ export interface RepositoryService<T = any> extends BaseRepositoryService<T> {
restore(ids: string[], context?: Context): Promise<T[]> restore(ids: string[], context?: Context): Promise<T[]>
} }
export interface TreeRepositoryService<T = any> extends BaseRepositoryService<T> { export interface TreeRepositoryService<T = any>
extends BaseRepositoryService<T> {
find( find(
options?: FindOptions<T>, options?: FindOptions<T>,
transformOptions?: RepositoryTransformOptions, transformOptions?: RepositoryTransformOptions,
+1
View File
@@ -2,3 +2,4 @@ export * as DecoratorUtils from "./decorators"
export * as EventBusUtils from "./event-bus" export * as EventBusUtils from "./event-bus"
export * as SearchUtils from "./search" export * as SearchUtils from "./search"
export * as ModulesSdkUtils from "./modules-sdk" export * as ModulesSdkUtils from "./modules-sdk"
export * as DALUtils from "./dal"
-1
View File
@@ -1 +0,0 @@
export const SoftDeletableFilterKey = "softDeletable"
-1
View File
@@ -22,4 +22,3 @@ export * from "./stringify-circular"
export * from "./to-kebab-case" export * from "./to-kebab-case"
export * from "./to-pascal-case" export * from "./to-pascal-case"
export * from "./wrap-handler" export * from "./wrap-handler"
export * from "./dal"
+5
View File
@@ -0,0 +1,5 @@
export * from "./mikro-orm/mikro-orm-repository"
export * from "./repository"
export * from "./utils"
export * from "./mikro-orm/mikro-orm-create-connection"
export * from "./mikro-orm/mikro-orm-soft-deletable-filter"
@@ -1,12 +1,13 @@
import { MikroORM, PostgreSqlDriver } from "@mikro-orm/postgresql"
import { ModuleServiceInitializeOptions } from "@medusajs/types" import { ModuleServiceInitializeOptions } from "@medusajs/types"
export async function createConnection( export async function mikroOrmCreateConnection(
database: ModuleServiceInitializeOptions["database"], database: ModuleServiceInitializeOptions["database"],
entities: any[] entities: any[]
) { ) {
const { MikroORM } = await import("@mikro-orm/postgresql")
const schema = database.schema || "public" const schema = database.schema || "public"
const orm = await MikroORM.init<PostgreSqlDriver>({ const orm = await MikroORM.init({
discovery: { disableDynamicFileAccess: true }, discovery: { disableDynamicFileAccess: true },
entities, entities,
debug: database.debug ?? process.env.NODE_ENV?.startsWith("dev") ?? false, debug: database.debug ?? process.env.NODE_ENV?.startsWith("dev") ?? false,
@@ -1,100 +1,14 @@
import { Context, DAL, RepositoryTransformOptions } from "@medusajs/types" import { Context, DAL, RepositoryTransformOptions } from "@medusajs/types"
import { SoftDeletableFilterKey } from "@medusajs/utils" import { MedusaContext } from "../../decorators"
import { SqlEntityManager } from "@mikro-orm/postgresql" import { buildQuery, InjectTransactionManager } from "../../modules-sdk"
import { import {
buildQuery, mikroOrmSerializer,
InjectTransactionManager, mikroOrmUpdateDeletedAtRecursively,
MedusaContext, transactionWrapper,
} from "@medusajs/utils" } from "../utils"
import { serialize } from "@mikro-orm/core"
// TODO: move to utils package class MikroOrmBase<T = any> {
protected readonly manager_: any
async function transactionWrapper<TManager = unknown>(
this: any,
task: (transactionManager: unknown) => Promise<any>,
{
transaction,
isolationLevel,
enableNestedTransactions = false,
}: {
isolationLevel?: string
transaction?: TManager
enableNestedTransactions?: boolean
} = {}
): Promise<any> {
// Reuse the same transaction if it is already provided and nested transactions are disabled
if (!enableNestedTransactions && transaction) {
return await task(transaction)
}
const options = {}
if (transaction) {
Object.assign(options, { ctx: transaction })
}
if (isolationLevel) {
Object.assign(options, { isolationLevel })
}
return await (this.manager_ as SqlEntityManager).transactional(task, options)
}
// TODO: move to utils package
const mikroOrmUpdateDeletedAtRecursively = async <T extends object = any>(
manager: any,
entities: T[],
value: Date | null
) => {
for (const entity of entities) {
if (!("deleted_at" in entity)) continue
;(entity as any).deleted_at = value
const relations = manager
.getDriver()
.getMetadata()
.get(entity.constructor.name).relations
const relationsToCascade = relations.filter((relation) =>
relation.cascade.includes("soft-remove" as any)
)
for (const relation of relationsToCascade) {
let collectionRelation = entity[relation.name]
if (!collectionRelation.isInitialized()) {
await collectionRelation.init()
}
const relationEntities = await collectionRelation.getItems({
filters: {
[SoftDeletableFilterKey]: {
withDeleted: true,
},
},
})
await mikroOrmUpdateDeletedAtRecursively(manager, relationEntities, value)
}
await manager.persist(entity)
}
}
const serializer = <TOutput extends object>(
data: any,
options?: any
): Promise<TOutput> => {
options ??= {}
const result = serialize(data, options)
return result as unknown as Promise<TOutput>
}
// TODO: move to utils package
class AbstractBase<T = any> {
protected readonly manager_: SqlEntityManager
protected constructor({ manager }) { protected constructor({ manager }) {
this.manager_ = manager this.manager_ = manager
@@ -115,29 +29,26 @@ class AbstractBase<T = any> {
async transaction<TManager = unknown>( async transaction<TManager = unknown>(
task: (transactionManager: TManager) => Promise<any>, task: (transactionManager: TManager) => Promise<any>,
{ options: {
transaction,
isolationLevel,
enableNestedTransactions = false,
}: {
isolationLevel?: string isolationLevel?: string
enableNestedTransactions?: boolean enableNestedTransactions?: boolean
transaction?: TManager transaction?: TManager
} = {} } = {}
): Promise<any> { ): Promise<any> {
return await transactionWrapper.apply(this, arguments) // @ts-ignore
return await transactionWrapper.bind(this)(task, options)
} }
async serialize<TOutput extends object | object[]>( async serialize<TOutput extends object | object[]>(
data: any, data: any,
options?: any options?: any
): Promise<TOutput> { ): Promise<TOutput> {
return await serializer<TOutput>(data, options) return await mikroOrmSerializer<TOutput>(data, options)
} }
} }
export abstract class AbstractBaseRepository<T = any> export abstract class MikroOrmAbstractBaseRepository<T = any>
extends AbstractBase extends MikroOrmBase
implements DAL.RepositoryService<T> implements DAL.RepositoryService<T>
{ {
abstract find(options?: DAL.FindOptions<T>, context?: Context) abstract find(options?: DAL.FindOptions<T>, context?: Context)
@@ -149,6 +60,10 @@ export abstract class AbstractBaseRepository<T = any>
abstract create(data: unknown[], context?: Context): Promise<T[]> abstract create(data: unknown[], context?: Context): Promise<T[]>
update(data: unknown[], context?: Context): Promise<T[]> {
throw new Error("Method not implemented.")
}
abstract delete(ids: string[], context?: Context): Promise<void> abstract delete(ids: string[], context?: Context): Promise<void>
@InjectTransactionManager() @InjectTransactionManager()
@@ -160,11 +75,7 @@ export abstract class AbstractBaseRepository<T = any>
const entities = await this.find({ where: { id: { $in: ids } } as any }) const entities = await this.find({ where: { id: { $in: ids } } as any })
const date = new Date() const date = new Date()
await mikroOrmUpdateDeletedAtRecursively( await mikroOrmUpdateDeletedAtRecursively(manager, entities, date)
manager as SqlEntityManager,
entities,
date
)
return entities return entities
} }
@@ -173,7 +84,7 @@ export abstract class AbstractBaseRepository<T = any>
async restore( async restore(
ids: string[], ids: string[],
@MedusaContext() @MedusaContext()
{ transactionManager: manager }: Context<SqlEntityManager> = {} { transactionManager: manager }: Context = {}
): Promise<T[]> { ): Promise<T[]> {
const query = buildQuery( const query = buildQuery(
{ id: { $in: ids } }, { id: { $in: ids } },
@@ -184,19 +95,14 @@ export abstract class AbstractBaseRepository<T = any>
const entities = await this.find(query) const entities = await this.find(query)
await mikroOrmUpdateDeletedAtRecursively( await mikroOrmUpdateDeletedAtRecursively(manager, entities, null)
manager as SqlEntityManager,
entities,
null
)
return entities return entities
} }
} }
// TODO: move to utils package export abstract class MikroOrmAbstractTreeRepositoryBase<T = any>
export abstract class AbstractTreeRepositoryBase<T = any> extends MikroOrmBase<T>
extends AbstractBase<T>
implements DAL.TreeRepositoryService<T> implements DAL.TreeRepositoryService<T>
{ {
protected constructor({ manager }) { protected constructor({ manager }) {
@@ -221,13 +127,15 @@ export abstract class AbstractTreeRepositoryBase<T = any>
abstract delete(id: string, context?: Context): Promise<void> abstract delete(id: string, context?: Context): Promise<void>
} }
// TODO: move to utils package
/** /**
* Only used internally in order to be able to wrap in transaction from a * Priviliged extends of the abstract classes unless most of the methods can't be implemented
* non identified repository * in your repository. This base repository is also used to provide a base repository
* injection if needed to be able to use the common methods without being related to an entity.
* In this case, none of the method will be implemented except the manager and transaction
* related ones.
*/ */
export class BaseRepository extends AbstractBaseRepository { export class MikroOrmBaseRepository extends MikroOrmAbstractBaseRepository {
constructor({ manager }) { constructor({ manager }) {
// @ts-ignore // @ts-ignore
super(...arguments) super(...arguments)
@@ -237,6 +145,10 @@ export class BaseRepository extends AbstractBaseRepository {
throw new Error("Method not implemented.") throw new Error("Method not implemented.")
} }
update(data: unknown[], context?: Context): Promise<any[]> {
throw new Error("Method not implemented.")
}
delete(ids: string[], context?: Context): Promise<void> { delete(ids: string[], context?: Context): Promise<void> {
throw new Error("Method not implemented.") throw new Error("Method not implemented.")
} }
@@ -253,8 +165,7 @@ export class BaseRepository extends AbstractBaseRepository {
} }
} }
export class MikroOrmBaseTreeRepository extends MikroOrmAbstractTreeRepositoryBase {
export class BaseTreeRepository extends AbstractTreeRepositoryBase {
constructor({ manager }) { constructor({ manager }) {
// @ts-ignore // @ts-ignore
super(...arguments) super(...arguments)
@@ -0,0 +1,19 @@
export const SoftDeletableFilterKey = "softDeletable"
interface FilterArguments {
withDeleted?: boolean
}
export const mikroOrmSoftDeletableFilterOptions = {
name: SoftDeletableFilterKey,
cond: ({ withDeleted }: FilterArguments = {}) => {
if (withDeleted) {
return {}
}
return {
deleted_at: null,
}
},
default: true,
args: false,
}
+95
View File
@@ -0,0 +1,95 @@
import { Context, DAL, RepositoryTransformOptions } from "@medusajs/types"
import { MedusaContext } from "../decorators"
class AbstractBase<T = any> {
protected readonly manager_: any
protected constructor({ manager }) {
this.manager_ = manager
}
getActiveManager<TManager = unknown>(
@MedusaContext()
{ transactionManager, manager }: Context = {}
): TManager {
return (transactionManager ?? manager ?? this.manager_) as TManager
}
async transaction<TManager = unknown>(
task: (transactionManager: TManager) => Promise<any>,
{
transaction,
isolationLevel,
enableNestedTransactions = false,
}: {
isolationLevel?: string
enableNestedTransactions?: boolean
transaction?: TManager
} = {}
): Promise<any> {
// @ts-ignore
return await transactionWrapper.apply(this, arguments)
}
}
export abstract class AbstractBaseRepository<T = any>
extends AbstractBase
implements DAL.RepositoryService<T>
{
abstract find(options?: DAL.FindOptions<T>, context?: Context)
abstract findAndCount(
options?: DAL.FindOptions<T>,
context?: Context
): Promise<[T[], number]>
abstract create(data: unknown[], context?: Context): Promise<T[]>
abstract update(data: unknown[], context?: Context): Promise<T[]>
abstract delete(ids: string[], context?: Context): Promise<void>
abstract softDelete(ids: string[], context?: Context): Promise<T[]>
abstract restore(ids: string[], context?: Context): Promise<T[]>
abstract getFreshManager<TManager = unknown>(): TManager
abstract serialize<TOutput extends object | object[]>(
data: any,
options?: any
): Promise<TOutput>
}
export abstract class AbstractTreeRepositoryBase<T = any>
extends AbstractBase<T>
implements DAL.TreeRepositoryService<T>
{
protected constructor({ manager }) {
// @ts-ignore
super(...arguments)
}
abstract find(
options?: DAL.FindOptions<T>,
transformOptions?: RepositoryTransformOptions,
context?: Context
)
abstract findAndCount(
options?: DAL.FindOptions<T>,
transformOptions?: RepositoryTransformOptions,
context?: Context
): Promise<[T[], number]>
abstract create(data: unknown, context?: Context): Promise<T>
abstract delete(id: string, context?: Context): Promise<void>
abstract getFreshManager<TManager = unknown>(): TManager
abstract serialize<TOutput extends object | object[]>(
data: any,
options?: any
): Promise<TOutput>
}
+86
View File
@@ -0,0 +1,86 @@
import { SoftDeletableFilterKey } from "../dal"
export async function transactionWrapper<TManager = unknown>(
this: any,
task: (transactionManager: unknown) => Promise<any>,
{
transaction,
isolationLevel,
enableNestedTransactions = false,
}: {
isolationLevel?: string
transaction?: TManager
enableNestedTransactions?: boolean
} = {}
): Promise<any> {
// Reuse the same transaction if it is already provided and nested transactions are disabled
if (!enableNestedTransactions && transaction) {
return await task(transaction)
}
const options = {}
if (transaction) {
Object.assign(options, { ctx: transaction })
}
if (isolationLevel) {
Object.assign(options, { isolationLevel })
}
const transactionMethod =
this.manager_.transaction ?? this.manager_.transactional
return await transactionMethod.bind(this.manager_)(task, options)
}
export const mikroOrmUpdateDeletedAtRecursively = async <
T extends object = any
>(
manager: any,
entities: T[],
value: Date | null
) => {
for (const entity of entities) {
if (!("deleted_at" in entity)) continue
;(entity as any).deleted_at = value
const relations = manager
.getDriver()
.getMetadata()
.get(entity.constructor.name).relations
const relationsToCascade = relations.filter((relation) =>
relation.cascade.includes("soft-remove" as any)
)
for (const relation of relationsToCascade) {
let collectionRelation = entity[relation.name]
if (!collectionRelation.isInitialized()) {
await collectionRelation.init()
}
const relationEntities = await collectionRelation.getItems({
filters: {
[SoftDeletableFilterKey]: {
withDeleted: true,
},
},
})
await mikroOrmUpdateDeletedAtRecursively(manager, relationEntities, value)
}
await manager.persist(entity)
}
}
export const mikroOrmSerializer = async <TOutput extends object>(
data: any,
options?: any
): Promise<TOutput> => {
options ??= {}
const { serialize } = await import("@mikro-orm/core")
const result = serialize(data, options)
return result as unknown as Promise<TOutput>
}
+1
View File
@@ -5,3 +5,4 @@ export * from "./decorators"
export * from "./event-bus" export * from "./event-bus"
export * from "./search" export * from "./search"
export * from "./modules-sdk" export * from "./modules-sdk"
export * from "./dal"
@@ -1,6 +1,7 @@
import { DAL, FindConfig } from "@medusajs/types" import { DAL, FindConfig } from "@medusajs/types"
import { deduplicate, isObject, SoftDeletableFilterKey } from "../common" import { deduplicate, isObject } from "../common"
import { SoftDeletableFilterKey } from "../dal"
export function buildQuery<T = any, TDto = any>( export function buildQuery<T = any, TDto = any>(
filters: Record<string, any> = {}, filters: Record<string, any> = {},