committed by
GitHub
parent
ae33f4825f
commit
f12299deb1
56
packages/workflows/src/definition/create-products.ts
Normal file
56
packages/workflows/src/definition/create-products.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { InputAlias, Workflows } from "../definitions"
|
||||
import {
|
||||
TransactionStepsDefinition,
|
||||
WorkflowManager,
|
||||
} from "@medusajs/orchestration"
|
||||
import {
|
||||
createProducts as createProductsHandler,
|
||||
removeProducts,
|
||||
} from "../handlers"
|
||||
import { exportWorkflow, pipe } from "../helper"
|
||||
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
|
||||
enum Actions {
|
||||
createProducts = "createProducts",
|
||||
}
|
||||
|
||||
const workflowSteps: TransactionStepsDefinition = {
|
||||
next: {
|
||||
action: Actions.createProducts,
|
||||
},
|
||||
}
|
||||
|
||||
const handlers = new Map([
|
||||
[
|
||||
Actions.createProducts,
|
||||
{
|
||||
invoke: pipe(
|
||||
{
|
||||
inputAlias: InputAlias.Products,
|
||||
invoke: {
|
||||
from: InputAlias.Products,
|
||||
alias: InputAlias.Products,
|
||||
},
|
||||
},
|
||||
createProductsHandler
|
||||
),
|
||||
compensate: pipe(
|
||||
{
|
||||
invoke: {
|
||||
from: Actions.createProducts,
|
||||
alias: InputAlias.Products,
|
||||
},
|
||||
},
|
||||
removeProducts
|
||||
),
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
WorkflowManager.register(Workflows.CreateProducts, workflowSteps, handlers)
|
||||
|
||||
export const createProducts = exportWorkflow<
|
||||
ProductTypes.CreateProductDTO[],
|
||||
ProductTypes.ProductDTO[]
|
||||
>(Workflows.CreateProducts, Actions.createProducts)
|
||||
1
packages/workflows/src/definition/index.ts
Normal file
1
packages/workflows/src/definition/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./create-products"
|
||||
14
packages/workflows/src/definitions.ts
Normal file
14
packages/workflows/src/definitions.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export enum Workflows {
|
||||
CreateProducts = "create-products",
|
||||
}
|
||||
|
||||
export enum InputAlias {
|
||||
Products = "products",
|
||||
RemovedProducts = "removedProducts",
|
||||
|
||||
InventoryItems = "inventoryItems",
|
||||
RemovedInventoryItems = "removedInventoryItems",
|
||||
|
||||
AttachedInventoryItems = "attachedInventoryItems",
|
||||
DetachedInventoryItems = "detachedInventoryItems",
|
||||
}
|
||||
35
packages/workflows/src/handlers/attach-inventory-items.ts
Normal file
35
packages/workflows/src/handlers/attach-inventory-items.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { InventoryItemDTO, ProductTypes } from "@medusajs/types"
|
||||
|
||||
import { InputAlias } from "../definitions"
|
||||
import { WorkflowArguments } from "../helper"
|
||||
|
||||
export async function attachInventoryItems({
|
||||
container,
|
||||
data,
|
||||
}: WorkflowArguments & {
|
||||
data: {
|
||||
variant: ProductTypes.ProductVariantDTO
|
||||
[InputAlias.InventoryItems]: InventoryItemDTO
|
||||
}[]
|
||||
}) {
|
||||
const manager = container.resolve("manager")
|
||||
const productVariantInventoryService = container
|
||||
.resolve("productVariantInventoryService")
|
||||
.withTransaction(manager)
|
||||
|
||||
const value = await Promise.all(
|
||||
data
|
||||
.filter((d) => d)
|
||||
.map(async ({ variant, [InputAlias.InventoryItems]: inventoryItem }) => {
|
||||
return await productVariantInventoryService.attachInventoryItem(
|
||||
variant.id,
|
||||
inventoryItem.id
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
return {
|
||||
alias: InputAlias.AttachedInventoryItems,
|
||||
value,
|
||||
}
|
||||
}
|
||||
59
packages/workflows/src/handlers/create-inventory-items.ts
Normal file
59
packages/workflows/src/handlers/create-inventory-items.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { IInventoryService, ProductTypes } from "@medusajs/types"
|
||||
|
||||
import { InputAlias } from "../definitions"
|
||||
import { WorkflowArguments } from "../helper"
|
||||
|
||||
export async function createInventoryItems({
|
||||
container,
|
||||
data,
|
||||
}: WorkflowArguments & {
|
||||
data: {
|
||||
[InputAlias.Products]: ProductTypes.ProductDTO[]
|
||||
}
|
||||
}) {
|
||||
const manager = container.resolve("manager")
|
||||
const inventoryService: IInventoryService =
|
||||
container.resolve("inventoryService")
|
||||
const context = { transactionManager: manager }
|
||||
|
||||
const products = data[InputAlias.Products]
|
||||
const variants = products.reduce(
|
||||
(
|
||||
acc: ProductTypes.ProductVariantDTO[],
|
||||
product: ProductTypes.ProductDTO
|
||||
) => {
|
||||
return acc.concat(product.variants)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const value = await Promise.all(
|
||||
variants.map(async (variant) => {
|
||||
if (!variant.manage_inventory) {
|
||||
return
|
||||
}
|
||||
|
||||
const inventoryItem = await inventoryService!.createInventoryItem(
|
||||
{
|
||||
sku: variant.sku!,
|
||||
origin_country: variant.origin_country!,
|
||||
hs_code: variant.hs_code!,
|
||||
mid_code: variant.mid_code!,
|
||||
material: variant.material!,
|
||||
weight: variant.weight!,
|
||||
length: variant.length!,
|
||||
height: variant.height!,
|
||||
width: variant.width!,
|
||||
},
|
||||
context
|
||||
)
|
||||
|
||||
return { variant, inventoryItem }
|
||||
})
|
||||
)
|
||||
|
||||
return {
|
||||
alias: InputAlias.InventoryItems,
|
||||
value,
|
||||
}
|
||||
}
|
||||
23
packages/workflows/src/handlers/create-products.ts
Normal file
23
packages/workflows/src/handlers/create-products.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { InputAlias } from "../definitions"
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
import { WorkflowArguments } from "../helper"
|
||||
|
||||
export async function createProducts({
|
||||
container,
|
||||
context,
|
||||
data,
|
||||
}: WorkflowArguments & {
|
||||
data: { [InputAlias.Products]: ProductTypes.CreateProductDTO[] }
|
||||
}) {
|
||||
const productModuleService = container.resolve("productModuleService")
|
||||
|
||||
const value = await productModuleService.create(
|
||||
data[InputAlias.Products],
|
||||
context
|
||||
)
|
||||
|
||||
return {
|
||||
alias: InputAlias.Products,
|
||||
value,
|
||||
}
|
||||
}
|
||||
5
packages/workflows/src/handlers/index.ts
Normal file
5
packages/workflows/src/handlers/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from "./remove-products"
|
||||
export * from "./create-products"
|
||||
export * from "./create-inventory-items"
|
||||
export * from "./remove-inventory-items"
|
||||
export * from "./attach-inventory-items"
|
||||
31
packages/workflows/src/handlers/remove-inventory-items.ts
Normal file
31
packages/workflows/src/handlers/remove-inventory-items.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { InventoryItemDTO, MedusaContainer } from "@medusajs/types"
|
||||
|
||||
import { InputAlias } from "../definitions"
|
||||
import { WorkflowArguments } from "../helper"
|
||||
|
||||
export async function removeInventoryItems({
|
||||
container,
|
||||
data,
|
||||
}: WorkflowArguments & {
|
||||
data: {
|
||||
[InputAlias.InventoryItems]: InventoryItemDTO
|
||||
}[]
|
||||
}) {
|
||||
const manager = container.resolve("manager")
|
||||
const inventoryService = container.resolve("inventoryService")
|
||||
const context = { transactionManager: manager }
|
||||
|
||||
const value = await Promise.all(
|
||||
data.map(async ({ [InputAlias.InventoryItems]: inventoryItem }) => {
|
||||
return await inventoryService!.deleteInventoryItem(
|
||||
inventoryItem.id,
|
||||
context
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
return {
|
||||
alias: InputAlias.RemovedInventoryItems,
|
||||
value,
|
||||
}
|
||||
}
|
||||
22
packages/workflows/src/handlers/remove-products.ts
Normal file
22
packages/workflows/src/handlers/remove-products.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { InputAlias } from "../definitions"
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
import { WorkflowArguments } from "../helper"
|
||||
|
||||
export async function removeProducts({
|
||||
container,
|
||||
data,
|
||||
}: WorkflowArguments & {
|
||||
data: {
|
||||
[InputAlias.Products]: ProductTypes.ProductDTO[]
|
||||
}
|
||||
}) {
|
||||
const productModuleService = container.resolve("productModuleService")
|
||||
const value = await productModuleService.softDelete(
|
||||
data[InputAlias.Products].map((p) => p.id)
|
||||
)
|
||||
|
||||
return {
|
||||
alias: InputAlias.RemovedProducts,
|
||||
value,
|
||||
}
|
||||
}
|
||||
1
packages/workflows/src/helper/empty-handler.ts
Normal file
1
packages/workflows/src/helper/empty-handler.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const emptyHandler: any = () => {}
|
||||
3
packages/workflows/src/helper/index.ts
Normal file
3
packages/workflows/src/helper/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./empty-handler"
|
||||
export * from "./pipe"
|
||||
export * from "./workflow-export"
|
||||
98
packages/workflows/src/helper/pipe.ts
Normal file
98
packages/workflows/src/helper/pipe.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { Context, MedusaContainer, SharedContext } from "@medusajs/types"
|
||||
import {
|
||||
TransactionMetadata,
|
||||
WorkflowStepHandler,
|
||||
} from "@medusajs/orchestration"
|
||||
|
||||
import { InputAlias } from "../definitions"
|
||||
|
||||
type WorkflowStepReturn = {
|
||||
alias: string
|
||||
value: any
|
||||
}
|
||||
|
||||
type WorkflowStepInput = {
|
||||
from: string
|
||||
alias: string
|
||||
}
|
||||
|
||||
interface PipelineInput {
|
||||
inputAlias?: InputAlias | string
|
||||
invoke?: WorkflowStepInput | WorkflowStepInput[]
|
||||
compensate?: WorkflowStepInput | WorkflowStepInput[]
|
||||
}
|
||||
|
||||
export type WorkflowArguments = {
|
||||
container: MedusaContainer
|
||||
payload: unknown
|
||||
data: any
|
||||
metadata: TransactionMetadata
|
||||
context: Context | SharedContext
|
||||
}
|
||||
|
||||
export type PipelineHandler = (
|
||||
args: WorkflowArguments
|
||||
) => Promise<WorkflowStepReturn | WorkflowStepReturn[]>
|
||||
|
||||
export function pipe(
|
||||
input: PipelineInput,
|
||||
...functions: PipelineHandler[]
|
||||
): WorkflowStepHandler {
|
||||
return async ({
|
||||
container,
|
||||
payload,
|
||||
invoke,
|
||||
compensate,
|
||||
metadata,
|
||||
context,
|
||||
}) => {
|
||||
const data = {}
|
||||
|
||||
const original = {
|
||||
invoke: invoke ?? {},
|
||||
compensate: compensate ?? {},
|
||||
}
|
||||
|
||||
if (input.inputAlias) {
|
||||
Object.assign(original.invoke, { [input.inputAlias]: payload })
|
||||
}
|
||||
|
||||
for (const key in input) {
|
||||
if (!input[key]) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!Array.isArray(input[key])) {
|
||||
input[key] = [input[key]]
|
||||
}
|
||||
|
||||
for (const action of input[key]) {
|
||||
if (action?.alias) {
|
||||
data[action.alias] = original[key][action.from]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return functions.reduce(async (_, fn) => {
|
||||
let result = await fn({
|
||||
container,
|
||||
payload,
|
||||
data,
|
||||
metadata,
|
||||
context: context as Context,
|
||||
})
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
for (const action of result) {
|
||||
if (action?.alias) {
|
||||
data[action.alias] = action.value
|
||||
}
|
||||
}
|
||||
} else if (result?.alias) {
|
||||
data[result.alias] = result.value
|
||||
}
|
||||
|
||||
return result
|
||||
}, {})
|
||||
}
|
||||
}
|
||||
107
packages/workflows/src/helper/workflow-export.ts
Normal file
107
packages/workflows/src/helper/workflow-export.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { Context, LoadedModule, MedusaContainer } from "@medusajs/types"
|
||||
import {
|
||||
DistributedTransaction,
|
||||
LocalWorkflow,
|
||||
TransactionState,
|
||||
TransactionStepError,
|
||||
} from "@medusajs/orchestration"
|
||||
|
||||
import { EOL } from "os"
|
||||
import { MedusaModule } from "@medusajs/modules-sdk"
|
||||
import { Workflows } from "../definitions"
|
||||
import { ulid } from "ulid"
|
||||
|
||||
export type FlowRunOptions<TData = unknown> = {
|
||||
input?: TData
|
||||
context?: Context
|
||||
resultFrom?: string | string[]
|
||||
throwOnError?: boolean
|
||||
}
|
||||
|
||||
export type WorkflowResult<TResult = unknown> = {
|
||||
errors: TransactionStepError[]
|
||||
transaction: DistributedTransaction
|
||||
result: TResult
|
||||
}
|
||||
|
||||
export const exportWorkflow = <TData = unknown, TResult = unknown>(
|
||||
workflowId: Workflows,
|
||||
defaultResult?: string
|
||||
) => {
|
||||
return function <TDataOverride = undefined, TResultOverride = undefined>(
|
||||
container?: LoadedModule[] | MedusaContainer
|
||||
): Omit<LocalWorkflow, "run"> & {
|
||||
run: (
|
||||
args?: FlowRunOptions<
|
||||
TDataOverride extends undefined ? TData : TDataOverride
|
||||
>
|
||||
) => Promise<
|
||||
WorkflowResult<
|
||||
TResultOverride extends undefined ? TResult : TResultOverride
|
||||
>
|
||||
>
|
||||
} {
|
||||
if (!container) {
|
||||
container = MedusaModule.getLoadedModules().map(
|
||||
(mod) => Object.values(mod)[0]
|
||||
)
|
||||
}
|
||||
|
||||
const flow = new LocalWorkflow(workflowId, container)
|
||||
|
||||
const originalRun = flow.run.bind(flow)
|
||||
const newRun = async (
|
||||
{ input, context, throwOnError, resultFrom }: FlowRunOptions = {
|
||||
throwOnError: true,
|
||||
resultFrom: defaultResult,
|
||||
}
|
||||
) => {
|
||||
const transaction = await originalRun(
|
||||
context?.transactionId ?? ulid(),
|
||||
input,
|
||||
context
|
||||
)
|
||||
|
||||
const errors = transaction.getErrors()
|
||||
|
||||
const failedStatus = [TransactionState.FAILED, TransactionState.REVERTED]
|
||||
if (failedStatus.includes(transaction.getState()) && throwOnError) {
|
||||
const errorMessage = errors
|
||||
?.map((err) => `${err.error?.message}${EOL}${err.error?.stack}`)
|
||||
?.join(`${EOL}`)
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
let result: any = undefined
|
||||
|
||||
if (resultFrom) {
|
||||
if (Array.isArray(resultFrom)) {
|
||||
result = resultFrom.map(
|
||||
(from) => transaction.getContext().invoke?.[from]
|
||||
)
|
||||
} else {
|
||||
result = transaction.getContext().invoke?.[resultFrom]
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
errors,
|
||||
transaction,
|
||||
result,
|
||||
}
|
||||
}
|
||||
flow.run = newRun as any
|
||||
|
||||
return flow as unknown as LocalWorkflow & {
|
||||
run: (
|
||||
args?: FlowRunOptions<
|
||||
TDataOverride extends undefined ? TData : TDataOverride
|
||||
>
|
||||
) => Promise<
|
||||
WorkflowResult<
|
||||
TResultOverride extends undefined ? TResult : TResultOverride
|
||||
>
|
||||
>
|
||||
}
|
||||
}
|
||||
}
|
||||
4
packages/workflows/src/index.ts
Normal file
4
packages/workflows/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./definition"
|
||||
export * from "./definitions"
|
||||
export * as Handlers from "./handlers"
|
||||
export * from "./helper"
|
||||
Reference in New Issue
Block a user