Feat: @medusajs/workflows (#4553)

feat: medusa workflows
This commit is contained in:
Carlos R. L. Rodrigues
2023-07-25 10:13:14 -03:00
committed by GitHub
parent ae33f4825f
commit f12299deb1
52 changed files with 1358 additions and 331 deletions

View File

@@ -0,0 +1 @@
export const emptyHandler: any = () => {}

View File

@@ -0,0 +1,3 @@
export * from "./empty-handler"
export * from "./pipe"
export * from "./workflow-export"

View 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
}, {})
}
}

View 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
>
>
}
}
}