Demo

Workflow

Custom workflow node (IWorkflowNode) တည်ဆောက်ရပါသည်၊ module တွင် မှတ်ပုံတင်ရပါသည်၊ service မှ trigger လုပ်ရပါသည်၊ controller API များကို @WorkflowEntry ဖြင့် discoverable step အဖြစ် ဖော်ပြရပါသည်။

ခြုံငုံသုံးသပ်ချက်

Workflow engine သည် node graph များကို run လုပ်ပါသည်။ Plugin များက node ပေးနိုင်ပြီး domain event တွင် workflow စတင်နိုင်ပါသည်။ Type များကို @quan-erp/shared-backend-core မှ import လုပ်ရပါသည်။

  • Custom node — IWorkflowNode implement; @Module({ workflowNodes }) တွင် မှတ်ပုံတင်ရပါသည်
  • Trigger — isTriggerable: true + WorkflowService.findByInitId / executeWorkflow
  • API entry — controller method ပေါ်တွင် @WorkflowEntry
  • Frontend — @quan-erp/base-frontend မှ workflowNodes() ဖြင့် UI node မှတ်ပုံတင်ရပါသည်

Node data model

  • Props — workflow editor UI မှ configuration
  • Args — run အချိန်တွင် inject လုပ်သော တန်ဖိုးများ
  • ReturnType — နောက် node များသို့ ပေးသော ထွက်ရှိချက်
  • type — plugin node အတွက် အမြဲ `${metadata.name}/${NODE_ID}`

Props & return schemas

PropsType ဖြင့် JSON schema ကြေညာပြီး SchemaToType ဖြင့် TypeScript type ရယူရပါသည်။ Schema သည် process() typing နှင့် frontend editor နှစ်ခုလုံးအတွက် အသုံးပြုပါသည်။

TSXschemas
1import { 2 PropsType, 3 SchemaToType, 4} from "@quan-erp/shared-backend-core"; 5 6const inputSchema = { 7 type: "number", 8} as const satisfies PropsType; 9type Props = SchemaToType<typeof inputSchema>; 10 11const returnSchema = { 12 type: "object", 13 properties: { 14 bookingId: { type: "number" }, 15 bookingSource: { type: "string" }, 16 }, 17} as const satisfies PropsType; 18type ReturnType = SchemaToType<typeof returnSchema>; 19 20export type Args = { 21 bookingId: number; 22 bookingSource: string; 23};

Execution lifecycle

  1. 01

    process()

    Executor က process(context, uid, props, args) ခေါ်ပါသည်။ NodeResult.single(...) ပြန်ပေးရပါသည်။

  2. 02

    nextToExecute()

    process ပြီးနောက် နောက် node id များကို မေးပါသည်။ null = UI next edges; string[] = routing override။

  3. 03

    Routing

    null → canvas next array။ string[] → ထို node id များကို run လုပ်ပါသည်။

ExecutionContext

Run တိုင်းတွင် node အားလုံး မျှဝေသော ExecutionContext ရရှိပါသည်။ UI-mapped props ကို မသုံးမီ resolve လုပ်ရပါသည်။

  • context.pipeData.resolve(props) — ${{node_….output}} များကို ဖြေရှင်းပါသည်; props မဖတ်မီ အမြဲ resolve လုပ်ရပါသည်
  • context.execute(nodeId, args) — အခြား node ကို ချက်ချင်း run / await (NodeResultType = T[])
  • toolResult[0] — sub-execution ၏ raw payload
  • context.findWorkflowNodeById(uid) — custom nextToExecute အတွက် edges စစ်ရန်
TSXresolve props
1import type { 2 ExecutionContext, 3 NodeResultType, 4 PromisableReturn, 5} from "@quan-erp/shared-backend-core"; 6import { NodeResult } from "@quan-erp/shared-backend-core"; 7 8function process( 9 context: ExecutionContext, 10 uid: string, 11 props: { expression: string }, 12 args: unknown, 13): PromisableReturn<NodeResultType<boolean>> { 14 const resolvedProps = context.pipeData.resolve(props); 15 const evaluateResult = eval(resolvedProps.expression); 16 return NodeResult.single(evaluateResult as boolean); 17}
TSXsub-execute
1import type { ExecutionContext } from "@quan-erp/shared-backend-core"; 2 3async function callTool( 4 context: ExecutionContext, 5 toolNodeId: string, 6 toolArgs: Record<string, unknown>, 7) { 8 const toolResult = await context.execute(toolNodeId, toolArgs); 9 return toolResult[0]; 10}

Custom routing (nextToExecute)

Conditional / loop အတွက် default edge walk ကို override လုပ်ရပါသည်။ Canvas edges ထားလိုလျှင် null ပြန်ပေးရပါသည်။ process မှ NodeResultType array ရရှိပါသည် (args[0] သုံးရပါသည်)။

TSXif-style routing
1import type { 2 ExecutionContext, 3 NodeResultType, 4 PromisableReturn, 5} from "@quan-erp/shared-backend-core"; 6 7function nextToExecute( 8 context: ExecutionContext, 9 uid: string, 10 args: NodeResultType<boolean>, 11): PromisableReturn<string[]> | PromisableReturn<null> { 12 const current = context.findWorkflowNodeById(uid); 13 const ifTrue = current?.next?.[0]; 14 const ifFalse = current?.next?.[1]; 15 if (args[0] && ifTrue?.id) return [ifTrue.id]; 16 if (!args[0] && ifFalse?.id) return [ifFalse.id]; 17 return null; 18}

Full trigger node

Trigger node များတွင် isTriggerable: true ထားရှိပြီး Args ကို return အဖြစ် များသောအားဖြင့် ဖြတ်ပေးပါသည်။

TSXOnBookingWorkflowNode
1import { 2 ExecutionContext, 3 IWorkflowNode, 4 NodeResult, 5 NodeResultType, 6 PromisableReturn, 7 PropsType, 8 SchemaToType, 9 WorkflowNodeDefination, 10} from "@quan-erp/shared-backend-core"; 11import metadata from "../../../module.metadata.json" with { type: "json" }; 12 13const inputSchema = { 14 type: "number", 15} as const satisfies PropsType; 16type Props = SchemaToType<typeof inputSchema>; 17 18const returnSchema = { 19 type: "object", 20 properties: { 21 bookingId: { type: "number" }, 22 bookingSource: { type: "string" }, 23 }, 24} as const satisfies PropsType; 25type ReturnType = SchemaToType<typeof returnSchema>; 26 27export type Args = { 28 bookingId: number; 29 bookingSource: string; 30}; 31 32export class OnBookingWorkflowNode 33 implements IWorkflowNode<Props, ReturnType, Args> 34{ 35 static NODE_ID = "bookings"; 36 37 process( 38 context: ExecutionContext, 39 id: string, 40 props: Props, 41 args: Args, 42 ): PromisableReturn<NodeResultType<ReturnType>> { 43 return NodeResult.single(args); 44 } 45 46 nextToExecute( 47 context: ExecutionContext, 48 uid: string, 49 args: NodeResultType<ReturnType>, 50 ): PromisableReturn<string[]> | PromisableReturn<null> { 51 return null; 52 } 53 54 getDefination(): WorkflowNodeDefination { 55 return { 56 type: `${metadata.name}/${OnBookingWorkflowNode.NODE_ID}`, 57 group: ["any"], 58 displayName: "On Hotel Booked", 59 description: "Triggers when a hotel is booked", 60 props: inputSchema, 61 isTriggerable: true, 62 returnType: { 63 type: "array", 64 items: returnSchema, 65 }, 66 }; 67 } 68}

Module တွင် မှတ်ပုံတင်ခြင်း

workflowNodes တွင် node class ထည့်ရပါသည် — backend mount လုပ်ပြီး editor သို့ definition ပေးပါသည်။

TSXmodule
1import { Module } from "@quan-erp/shared-backend-core"; 2import metadata from "../../module.metadata.json" with { type: "json" }; 3import { OnBookingWorkflowNode } from "./workflow/on-booking.workflow.js"; 4 5@Module({ 6 name: metadata.name, 7 providers: [], 8 controllers: [], 9 entities: [], 10 workflowNodes: [OnBookingWorkflowNode], 11}) 12export class MyPluginModule {}

Programmatic trigger

isTriggerable node များအတွက် builtin WorkflowService ကို inject လုပ်ရပါသည်။ init id `${metadata.name}/${NODE_ID}` ဖြင့် active workflow ရှာပြီး Args နှင့် execute လုပ်ရပါသည်။

TSXexecuteWorkflow
1import { 2 ContainerRegistryManager, 3 Controller, 4 Inject, 5 Post, 6 ResponseDto, 7 WorkflowService, 8} from "@quan-erp/shared-backend-core"; 9import metadata from "../../module.metadata.json" with { type: "json" }; 10import { OnBookingWorkflowNode } from "../workflow/on-booking.workflow.js"; 11 12@Controller("/bookings") 13export class BookingController { 14 @Inject(WorkflowService, ContainerRegistryManager.BUILTIN_PLUGIN) 15 workflowService: WorkflowService; 16 17 @Post("/") 18 async createBooking() { 19 // … create booking … 20 const workflows = await this.workflowService.findByInitId( 21 `${metadata.name}/${OnBookingWorkflowNode.NODE_ID}`, 22 { active: true }, 23 ); 24 25 await Promise.all( 26 workflows.map((w) => 27 this.workflowService.executeWorkflow({ 28 workflow: w, 29 args: { 30 bookingId: 1, 31 bookingSource: "web", 32 }, 33 }), 34 ), 35 ); 36 37 return ResponseDto.okWithEmpty(); 38 } 39}

@WorkflowEntry (HTTP step)

Controller method ကို workflow engine က discover / invoke လုပ်နိုင်အောင် mark လုပ်ရပါသည်။ name၊ description နှင့် response နှင့် ကိုက်သော returnType schema ပေးရပါသည်။

TSX@WorkflowEntry
1import { 2 Controller, 3 Get, 4 WorkflowEntry, 5} from "@quan-erp/shared-backend-core"; 6 7@Controller("/branch") 8export class BranchController { 9 @Get("/") 10 @WorkflowEntry({ 11 name: "get-branch", 12 description: "get all branches", 13 returnType: { 14 type: "object", 15 properties: { 16 payload: { 17 type: "array", 18 items: { 19 type: "object", 20 properties: { 21 id: { type: "number" }, 22 name: { type: "string" }, 23 }, 24 }, 25 }, 26 }, 27 }, 28 }) 29 async list() { 30 /* … */ 31 } 32}

Checklist

  • Schemas: props နှင့် returnType အတွက် PropsType + SchemaToType
  • IWorkflowNode ပေါ်တွင် process, nextToExecute, getDefination
  • type: `${metadata.name}/${NODE_ID}` — plugin prefix မပျောက်ရပါ
  • @Module({ workflowNodes }) တွင် မှတ်ပုံတင်ရပါသည်
  • Triggerable: domain event တွင် WorkflowService ဖြင့် fire လုပ်ရပါသည်
  • Editor props မသုံးမီ context.pipeData.resolve(props)
  • NodeResult.single(...) ဖြင့် ပြန်ပေးရပါသည်