ခြုံငုံသုံးသပ်ချက်
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 နှစ်ခုလုံးအတွက် အသုံးပြုပါသည်။
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
- 01
process()
Executor က process(context, uid, props, args) ခေါ်ပါသည်။ NodeResult.single(...) ပြန်ပေးရပါသည်။
- 02
nextToExecute()
process ပြီးနောက် နောက် node id များကို မေးပါသည်။ null = UI next edges; string[] = routing override။
- 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 စစ်ရန်
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}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] သုံးရပါသည်)။
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 အဖြစ် များသောအားဖြင့် ဖြတ်ပေးပါသည်။
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 ပေးပါသည်။
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 လုပ်ရပါသည်။
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 ပေးရပါသည်။
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(...) ဖြင့် ပြန်ပေးရပါသည်