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 Peretti
2023-07-31 11:30:43 +00:00
committed by GitHub
co-authored by Shahed Nasser
parent 648eb106d6
commit 4073b73130
39 changed files with 523 additions and 375 deletions
+1
View File
@@ -2,3 +2,4 @@ export * as DecoratorUtils from "./decorators"
export * as EventBusUtils from "./event-bus"
export * as SearchUtils from "./search"
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-pascal-case"
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"
@@ -0,0 +1,28 @@
import { ModuleServiceInitializeOptions } from "@medusajs/types"
export async function mikroOrmCreateConnection(
database: ModuleServiceInitializeOptions["database"],
entities: any[]
) {
const { MikroORM } = await import("@mikro-orm/postgresql")
const schema = database.schema || "public"
const orm = await MikroORM.init({
discovery: { disableDynamicFileAccess: true },
entities,
debug: database.debug ?? process.env.NODE_ENV?.startsWith("dev") ?? false,
baseDir: process.cwd(),
clientUrl: database.clientUrl,
schema,
driverOptions: database.driverOptions ?? {
connection: { ssl: true },
},
tsNode: process.env.APP_ENV === "development",
type: "postgresql",
migrations: {
path: __dirname + "/../migrations",
},
})
return orm
}
@@ -0,0 +1,197 @@
import { Context, DAL, RepositoryTransformOptions } from "@medusajs/types"
import { MedusaContext } from "../../decorators"
import { buildQuery, InjectTransactionManager } from "../../modules-sdk"
import {
mikroOrmSerializer,
mikroOrmUpdateDeletedAtRecursively,
transactionWrapper,
} from "../utils"
class MikroOrmBase<T = any> {
protected readonly manager_: any
protected constructor({ manager }) {
this.manager_ = manager
}
getFreshManager<TManager = unknown>(): TManager {
return (this.manager_.fork
? this.manager_.fork()
: this.manager_) as unknown as TManager
}
getActiveManager<TManager = unknown>(
@MedusaContext()
{ transactionManager, manager }: Context = {}
): TManager {
return (transactionManager ?? manager ?? this.manager_) as TManager
}
async transaction<TManager = unknown>(
task: (transactionManager: TManager) => Promise<any>,
options: {
isolationLevel?: string
enableNestedTransactions?: boolean
transaction?: TManager
} = {}
): Promise<any> {
// @ts-ignore
return await transactionWrapper.bind(this)(task, options)
}
async serialize<TOutput extends object | object[]>(
data: any,
options?: any
): Promise<TOutput> {
return await mikroOrmSerializer<TOutput>(data, options)
}
}
export abstract class MikroOrmAbstractBaseRepository<T = any>
extends MikroOrmBase
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[]>
update(data: unknown[], context?: Context): Promise<T[]> {
throw new Error("Method not implemented.")
}
abstract delete(ids: string[], context?: Context): Promise<void>
@InjectTransactionManager()
async softDelete(
ids: string[],
@MedusaContext()
{ transactionManager: manager }: Context = {}
): Promise<T[]> {
const entities = await this.find({ where: { id: { $in: ids } } as any })
const date = new Date()
await mikroOrmUpdateDeletedAtRecursively(manager, entities, date)
return entities
}
@InjectTransactionManager()
async restore(
ids: string[],
@MedusaContext()
{ transactionManager: manager }: Context = {}
): Promise<T[]> {
const query = buildQuery(
{ id: { $in: ids } },
{
withDeleted: true,
}
)
const entities = await this.find(query)
await mikroOrmUpdateDeletedAtRecursively(manager, entities, null)
return entities
}
}
export abstract class MikroOrmAbstractTreeRepositoryBase<T = any>
extends MikroOrmBase<T>
implements DAL.TreeRepositoryService<T>
{
protected constructor({ manager }) {
// @ts-ignore
super(...arguments)
}
abstract find(
options?: DAL.FindOptions<T>,
transformOptions?: RepositoryTransformOptions,
context?: Context
)
abstract findAndCount(
options?: DAL.FindOptions<T>,
transformOptions?: RepositoryTransformOptions,
context?: Context
): Promise<[T[], number]>
abstract create(data: unknown, context?: Context): Promise<T>
abstract delete(id: string, context?: Context): Promise<void>
}
/**
* Priviliged extends of the abstract classes unless most of the methods can't be implemented
* in your repository. This base repository is also used to provide a base repository
* injection if needed to be able to use the common methods without being related to an entity.
* In this case, none of the method will be implemented except the manager and transaction
* related ones.
*/
export class MikroOrmBaseRepository extends MikroOrmAbstractBaseRepository {
constructor({ manager }) {
// @ts-ignore
super(...arguments)
}
create(data: unknown[], context?: Context): Promise<any[]> {
throw new Error("Method not implemented.")
}
update(data: unknown[], context?: Context): Promise<any[]> {
throw new Error("Method not implemented.")
}
delete(ids: string[], context?: Context): Promise<void> {
throw new Error("Method not implemented.")
}
find(options?: DAL.FindOptions, context?: Context): Promise<any[]> {
throw new Error("Method not implemented.")
}
findAndCount(
options?: DAL.FindOptions,
context?: Context
): Promise<[any[], number]> {
throw new Error("Method not implemented.")
}
}
export class MikroOrmBaseTreeRepository extends MikroOrmAbstractTreeRepositoryBase {
constructor({ manager }) {
// @ts-ignore
super(...arguments)
}
find(
options?: DAL.FindOptions,
transformOptions?: RepositoryTransformOptions,
context?: Context
): Promise<any[]> {
throw new Error("Method not implemented.")
}
findAndCount(
options?: DAL.FindOptions,
transformOptions?: RepositoryTransformOptions,
context?: Context
): Promise<[any[], number]> {
throw new Error("Method not implemented.")
}
create(data: unknown, context?: Context): Promise<any> {
throw new Error("Method not implemented.")
}
delete(id: string, context?: Context): Promise<void> {
throw new Error("Method not implemented.")
}
}
@@ -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 "./search"
export * from "./modules-sdk"
export * from "./dal"
@@ -1,6 +1,7 @@
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>(
filters: Record<string, any> = {},