feat: Workflow engine modules (#6128)

This commit is contained in:
Carlos R. L. Rodrigues
2024-01-23 10:08:08 -03:00
committed by GitHub
parent d85fee42ee
commit 302323916b
119 changed files with 5339 additions and 263 deletions

View File

@@ -26,3 +26,16 @@ workflow()
.then((res) => {
console.log(res.result) // result: { step2: { test: "test", test2: "step1" } }
})
/*type type0 = typeof workflow extends ReturnWorkflow<infer T, infer R, infer X>
? T
: never
function run<
TWorkflow extends ReturnWorkflow<any, any, any>,
TData = TWorkflow extends ReturnWorkflow<infer T, infer R, infer X>
? T
: never
>(name: string, options: FlowRunOptions<TData>) {}
const test = run<typeof workflow>("workflow", { input: "string" })*/

View File

@@ -1,4 +1,11 @@
import { resolveValue, StepResponse } from "./helpers"
import {
TransactionStepsDefinition,
WorkflowManager,
} from "@medusajs/orchestration"
import { OrchestrationUtils, isString } from "@medusajs/utils"
import { ulid } from "ulid"
import { StepResponse, resolveValue } from "./helpers"
import { proxify } from "./helpers/proxy"
import {
CreateWorkflowComposerContext,
StepExecutionContext,
@@ -6,9 +13,6 @@ import {
StepFunctionResult,
WorkflowData,
} from "./type"
import { proxify } from "./helpers/proxy"
import { TransactionStepsDefinition } from "@medusajs/orchestration"
import { isString, OrchestrationUtils } from "@medusajs/utils"
/**
* The type of invocation function passed to a step.
@@ -166,19 +170,37 @@ function applyStep<
: undefined,
}
stepConfig!.noCompensation = !compensateFn
stepConfig.uuid = ulid()
stepConfig.noCompensation = !compensateFn
this.flow.addAction(stepName, stepConfig)
this.handlers.set(stepName, handler)
if (!this.handlers.has(stepName)) {
this.handlers.set(stepName, handler)
}
const ret = {
__type: OrchestrationUtils.SymbolWorkflowStep,
__step__: stepName,
config: (config: Pick<TransactionStepsDefinition, "maxRetries">) => {
this.flow.replaceAction(stepName, stepName, {
config: (
localConfig: { name?: string } & Omit<
TransactionStepsDefinition,
"next" | "uuid" | "action"
>
) => {
const newStepName = localConfig.name ?? stepName
delete localConfig.name
this.handlers.set(newStepName, handler)
this.flow.replaceAction(stepConfig.uuid!, newStepName, {
...stepConfig,
...config,
...localConfig,
})
WorkflowManager.update(this.workflowId, this.flow, this.handlers)
return proxify(ret)
},
}
@@ -241,11 +263,14 @@ export function createStep<
TInvokeResultCompensateInput
>(
/**
* The name of the step or its configuration (currently support maxRetries).
* The name of the step or its configuration.
*/
nameOrConfig:
| string
| ({ name: string } & Pick<TransactionStepsDefinition, "maxRetries">),
| ({ name: string } & Omit<
TransactionStepsDefinition,
"next" | "uuid" | "action"
>),
/**
* An invocation function that will be executed when the workflow is executed. The function must return an instance of {@link StepResponse}. The constructor of {@link StepResponse}
* accepts the output of the step as a first argument, and optionally as a second argument the data to be passed to the compensation function as a parameter.

View File

@@ -5,7 +5,7 @@ import {
WorkflowManager,
} from "@medusajs/orchestration"
import { LoadedModule, MedusaContainer } from "@medusajs/types"
import { OrchestrationUtils } from "@medusajs/utils"
import { isString, OrchestrationUtils } from "@medusajs/utils"
import { ExportedWorkflow, exportWorkflow } from "../../helper"
import { proxify } from "./helpers/proxy"
import {
@@ -63,7 +63,11 @@ global[OrchestrationUtils.SymbolMedusaWorkflowComposerContext] = null
* }
* ```
*/
type ReturnWorkflow<TData, TResult, THooks extends Record<string, Function>> = {
export type ReturnWorkflow<
TData,
TResult,
THooks extends Record<string, Function>
> = {
<TDataOverride = undefined, TResultOverride = undefined>(
container?: LoadedModule[] | MedusaContainer
): Omit<
@@ -73,8 +77,20 @@ type ReturnWorkflow<TData, TResult, THooks extends Record<string, Function>> = {
ExportedWorkflow<TData, TResult, TDataOverride, TResultOverride>
} & THooks & {
getName: () => string
} & {
config: (config: TransactionModelOptions) => void
}
/**
* Extract the raw type of the expected input data of a workflow.
*
* @example
* type WorkflowInputData = UnwrapWorkflowInputDataType<typeof myWorkflow>
*/
export type UnwrapWorkflowInputDataType<
T extends ReturnWorkflow<any, any, any>
> = T extends ReturnWorkflow<infer TData, infer R, infer THooks> ? TData : never
/**
* This function creates a workflow with the provided name and a constructor function.
* The constructor function builds the workflow from steps created by the {@link createStep} function.
@@ -136,9 +152,9 @@ export function createWorkflow<
THooks extends Record<string, Function> = Record<string, Function>
>(
/**
* The name of the workflow.
* The name of the workflow or its configuration.
*/
name: string,
nameOrConfig: string | ({ name: string } & TransactionModelOptions),
/**
* The constructor function that is executed when the `run` method in {@link ReturnWorkflow} is used.
* The function can't be an arrow function or an asynchronus function. It also can't directly manipulate data.
@@ -151,9 +167,11 @@ export function createWorkflow<
[K in keyof TResult]:
| WorkflowData<TResult[K]>
| WorkflowDataProperties<TResult[K]>
},
options?: TransactionModelOptions
}
): ReturnWorkflow<TData, TResult, THooks> {
const name = isString(nameOrConfig) ? nameOrConfig : nameOrConfig.name
const options = isString(nameOrConfig) ? {} : nameOrConfig
const handlers: WorkflowHandler = new Map()
if (WorkflowManager.getWorkflow(name)) {
@@ -185,13 +203,17 @@ export function createWorkflow<
const inputPlaceHolder = proxify<WorkflowData>({
__type: OrchestrationUtils.SymbolInputReference,
__step__: "",
config: () => {
// TODO: config default value?
throw new Error("Config is not available for the input object.")
},
})
const returnedStep = composer.apply(context, [inputPlaceHolder])
delete global[OrchestrationUtils.SymbolMedusaWorkflowComposerContext]
WorkflowManager.update(name, context.flow, handlers)
WorkflowManager.update(name, context.flow, handlers, options)
const workflow = exportWorkflow<TData, TResult>(
name,
@@ -206,8 +228,12 @@ export function createWorkflow<
container?: LoadedModule[] | MedusaContainer
) => {
const workflow_ = workflow<TDataOverride, TResultOverride>(container)
const expandedFlow: any = workflow_
expandedFlow.config = (config) => {
workflow_.setOptions(config)
}
return workflow_
return expandedFlow
}
let shouldRegisterHookHandler = true

View File

@@ -37,13 +37,8 @@ export type StepFunction<TInput, TOutput = unknown> = (keyof TInput extends []
}) &
WorkflowDataProperties<{
[K in keyof TOutput]: TOutput[K]
}> & {
config(
config: Pick<TransactionStepsDefinition, "maxRetries">
): WorkflowData<{
[K in keyof TOutput]: TOutput[K]
}>
} & WorkflowDataProperties<{
}> &
WorkflowDataProperties<{
[K in keyof TOutput]: TOutput[K]
}>
@@ -62,7 +57,22 @@ export type WorkflowData<T = unknown> = (T extends object
[Key in keyof T]: WorkflowData<T[Key]>
}
: WorkflowDataProperties<T>) &
WorkflowDataProperties<T>
WorkflowDataProperties<T> & {
config(
config: { name?: string } & Omit<
TransactionStepsDefinition,
"next" | "uuid" | "action"
>
): T extends object
? WorkflowData<
T extends object
? {
[K in keyof T]: T[K]
}
: T
>
: T
}
export type CreateWorkflowComposerContext = {
hooks_: string[]