@open-mercato/core 0.6.5-develop.4516.1.88e6ab71a9 → 0.6.5-develop.4534.1.b459babe6d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/step_instance/index.js +2 -0
- package/dist/generated/entities/step_instance/index.js.map +2 -2
- package/dist/generated/entities/user_task/index.js +2 -0
- package/dist/generated/entities/user_task/index.js.map +2 -2
- package/dist/generated/entities/workflow_branch_instance/index.js +39 -0
- package/dist/generated/entities/workflow_branch_instance/index.js.map +7 -0
- package/dist/generated/entities/workflow_event/index.js +2 -0
- package/dist/generated/entities/workflow_event/index.js.map +2 -2
- package/dist/generated/entities/workflow_instance/index.js +2 -0
- package/dist/generated/entities/workflow_instance/index.js.map +2 -2
- package/dist/generated/entities.ids.generated.js +1 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +24 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/progress/api/jobs/[id]/route.js +7 -1
- package/dist/modules/progress/api/jobs/[id]/route.js.map +2 -2
- package/dist/modules/shipping_carriers/api/cancel/route.js +2 -2
- package/dist/modules/shipping_carriers/api/cancel/route.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/status-sync.js +8 -1
- package/dist/modules/shipping_carriers/lib/status-sync.js.map +2 -2
- package/dist/modules/workflows/components/NodeEditDialog.js +3 -1
- package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
- package/dist/modules/workflows/components/WorkflowGraphImpl.js +4 -2
- package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
- package/dist/modules/workflows/components/nodes/ParallelForkNode.js +49 -0
- package/dist/modules/workflows/components/nodes/ParallelForkNode.js.map +7 -0
- package/dist/modules/workflows/components/nodes/ParallelJoinNode.js +49 -0
- package/dist/modules/workflows/components/nodes/ParallelJoinNode.js.map +7 -0
- package/dist/modules/workflows/components/nodes/index.js +4 -0
- package/dist/modules/workflows/components/nodes/index.js.map +2 -2
- package/dist/modules/workflows/data/entities.js +81 -0
- package/dist/modules/workflows/data/entities.js.map +2 -2
- package/dist/modules/workflows/data/validators.js +146 -1
- package/dist/modules/workflows/data/validators.js.map +2 -2
- package/dist/modules/workflows/events.js +7 -1
- package/dist/modules/workflows/events.js.map +2 -2
- package/dist/modules/workflows/lib/activity-executor.js +4 -2
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/dist/modules/workflows/lib/activity-queue-types.js.map +2 -2
- package/dist/modules/workflows/lib/event-logger.js +2 -0
- package/dist/modules/workflows/lib/event-logger.js.map +2 -2
- package/dist/modules/workflows/lib/execution-token.js +98 -0
- package/dist/modules/workflows/lib/execution-token.js.map +7 -0
- package/dist/modules/workflows/lib/node-type-icons.js +14 -5
- package/dist/modules/workflows/lib/node-type-icons.js.map +2 -2
- package/dist/modules/workflows/lib/parallel-handler.js +364 -0
- package/dist/modules/workflows/lib/parallel-handler.js.map +7 -0
- package/dist/modules/workflows/lib/signal-handler.js +63 -1
- package/dist/modules/workflows/lib/signal-handler.js.map +2 -2
- package/dist/modules/workflows/lib/step-handler.js +74 -30
- package/dist/modules/workflows/lib/step-handler.js.map +2 -2
- package/dist/modules/workflows/lib/task-handler.js +26 -0
- package/dist/modules/workflows/lib/task-handler.js.map +2 -2
- package/dist/modules/workflows/lib/timer-handler.js +26 -1
- package/dist/modules/workflows/lib/timer-handler.js.map +2 -2
- package/dist/modules/workflows/lib/transition-handler.js +33 -21
- package/dist/modules/workflows/lib/transition-handler.js.map +2 -2
- package/dist/modules/workflows/lib/workflow-executor.js +39 -1
- package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
- package/dist/modules/workflows/migrations/Migration20260602120000.js +24 -0
- package/dist/modules/workflows/migrations/Migration20260602120000.js.map +7 -0
- package/dist/modules/workflows/workers/workflow-activities.worker.js +8 -4
- package/dist/modules/workflows/workers/workflow-activities.worker.js.map +2 -2
- package/generated/entities/step_instance/index.ts +1 -0
- package/generated/entities/user_task/index.ts +1 -0
- package/generated/entities/workflow_branch_instance/index.ts +18 -0
- package/generated/entities/workflow_event/index.ts +1 -0
- package/generated/entities/workflow_instance/index.ts +1 -0
- package/generated/entities.ids.generated.ts +1 -0
- package/generated/entity-fields-registry.ts +24 -0
- package/package.json +7 -7
- package/src/modules/progress/api/jobs/[id]/route.ts +7 -0
- package/src/modules/shipping_carriers/api/cancel/route.ts +2 -2
- package/src/modules/shipping_carriers/lib/status-sync.ts +19 -0
- package/src/modules/workflows/components/NodeEditDialog.tsx +2 -0
- package/src/modules/workflows/components/WorkflowGraphImpl.tsx +3 -1
- package/src/modules/workflows/components/nodes/ParallelForkNode.tsx +66 -0
- package/src/modules/workflows/components/nodes/ParallelJoinNode.tsx +66 -0
- package/src/modules/workflows/components/nodes/index.ts +6 -0
- package/src/modules/workflows/data/entities.ts +109 -0
- package/src/modules/workflows/data/validators.ts +223 -0
- package/src/modules/workflows/events.ts +7 -0
- package/src/modules/workflows/i18n/de.json +12 -0
- package/src/modules/workflows/i18n/en.json +12 -0
- package/src/modules/workflows/i18n/es.json +12 -0
- package/src/modules/workflows/i18n/pl.json +12 -0
- package/src/modules/workflows/lib/activity-executor.ts +8 -2
- package/src/modules/workflows/lib/activity-queue-types.ts +3 -0
- package/src/modules/workflows/lib/event-logger.ts +3 -0
- package/src/modules/workflows/lib/execution-token.ts +166 -0
- package/src/modules/workflows/lib/node-type-icons.ts +11 -2
- package/src/modules/workflows/lib/parallel-handler.ts +575 -0
- package/src/modules/workflows/lib/signal-handler.ts +72 -1
- package/src/modules/workflows/lib/step-handler.ts +94 -34
- package/src/modules/workflows/lib/task-handler.ts +32 -0
- package/src/modules/workflows/lib/timer-handler.ts +30 -1
- package/src/modules/workflows/lib/transition-handler.ts +56 -24
- package/src/modules/workflows/lib/workflow-executor.ts +53 -1
- package/src/modules/workflows/migrations/.snapshot-open-mercato.json +263 -0
- package/src/modules/workflows/migrations/Migration20260602120000.ts +25 -0
- package/src/modules/workflows/workers/workflow-activities.worker.ts +9 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/workflows/lib/node-type-icons.ts"],
|
|
4
|
-
"sourcesContent": ["import { CircleDot, CircleStop, User, Zap, Workflow, Clock, Timer, LucideIcon } from 'lucide-react'\n\nexport type NodeType = 'start' | 'end' | 'userTask' | 'automated' | 'subWorkflow' | 'waitForSignal' | 'waitForTimer'\n\nexport const NODE_TYPE_ICONS: Record<NodeType, LucideIcon> = {\n start: CircleDot,\n end: CircleStop,\n userTask: User,\n automated: Zap,\n subWorkflow: Workflow,\n waitForSignal: Clock,\n waitForTimer: Timer,\n}\n\nexport const NODE_TYPE_COLORS: Record<NodeType, string> = {\n start: 'text-emerald-500',\n end: 'text-red-500',\n userTask: 'text-blue-500',\n automated: 'text-amber-500',\n subWorkflow: 'text-purple-500',\n waitForSignal: 'text-purple-500',\n waitForTimer: 'text-cyan-500',\n}\n\nexport const NODE_TYPE_LABELS: Record<NodeType, { title: string; description: string }> = {\n start: { title: 'START', description: 'Workflow trigger' },\n end: { title: 'END', description: 'Workflow completion' },\n userTask: { title: 'USER TASK', description: 'Manual action' },\n automated: { title: 'AUTOMATED', description: 'System task' },\n subWorkflow: { title: 'SUB-WORKFLOW', description: 'Invoke workflow' },\n waitForSignal: { title: 'WAIT FOR SIGNAL', description: 'Pause for external event' },\n waitForTimer: { title: 'WAIT FOR TIMER', description: 'Pause for a duration' },\n}\n\nconst STEP_TYPE_TO_NODE_TYPE: Record<string, NodeType> = {\n START: 'start',\n END: 'end',\n USER_TASK: 'userTask',\n AUTOMATED: 'automated',\n SUB_WORKFLOW: 'subWorkflow',\n WAIT_FOR_SIGNAL: 'waitForSignal',\n WAIT_FOR_TIMER: 'waitForTimer',\n}\n\nexport function stepTypeToNodeType(stepType: string): NodeType {\n return STEP_TYPE_TO_NODE_TYPE[stepType] || 'automated'\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,WAAW,YAAY,MAAM,KAAK,UAAU,OAAO,aAAyB;
|
|
4
|
+
"sourcesContent": ["import { CircleDot, CircleStop, User, Zap, Workflow, Clock, Timer, Split, Merge, LucideIcon } from 'lucide-react'\n\nexport type NodeType = 'start' | 'end' | 'userTask' | 'automated' | 'subWorkflow' | 'waitForSignal' | 'waitForTimer' | 'parallelFork' | 'parallelJoin'\n\nexport const NODE_TYPE_ICONS: Record<NodeType, LucideIcon> = {\n start: CircleDot,\n end: CircleStop,\n userTask: User,\n automated: Zap,\n subWorkflow: Workflow,\n waitForSignal: Clock,\n waitForTimer: Timer,\n parallelFork: Split,\n parallelJoin: Merge,\n}\n\nexport const NODE_TYPE_COLORS: Record<NodeType, string> = {\n start: 'text-emerald-500',\n end: 'text-red-500',\n userTask: 'text-blue-500',\n automated: 'text-amber-500',\n subWorkflow: 'text-purple-500',\n waitForSignal: 'text-purple-500',\n waitForTimer: 'text-cyan-500',\n // New nodes use a semantic token (DS rule: no hardcoded color shades).\n parallelFork: 'text-primary',\n parallelJoin: 'text-primary',\n}\n\nexport const NODE_TYPE_LABELS: Record<NodeType, { title: string; description: string }> = {\n start: { title: 'START', description: 'Workflow trigger' },\n end: { title: 'END', description: 'Workflow completion' },\n userTask: { title: 'USER TASK', description: 'Manual action' },\n automated: { title: 'AUTOMATED', description: 'System task' },\n subWorkflow: { title: 'SUB-WORKFLOW', description: 'Invoke workflow' },\n waitForSignal: { title: 'WAIT FOR SIGNAL', description: 'Pause for external event' },\n waitForTimer: { title: 'WAIT FOR TIMER', description: 'Pause for a duration' },\n parallelFork: { title: 'PARALLEL FORK', description: 'Split into parallel branches' },\n parallelJoin: { title: 'PARALLEL JOIN', description: 'Wait for all branches' },\n}\n\nconst STEP_TYPE_TO_NODE_TYPE: Record<string, NodeType> = {\n START: 'start',\n END: 'end',\n USER_TASK: 'userTask',\n AUTOMATED: 'automated',\n SUB_WORKFLOW: 'subWorkflow',\n WAIT_FOR_SIGNAL: 'waitForSignal',\n WAIT_FOR_TIMER: 'waitForTimer',\n PARALLEL_FORK: 'parallelFork',\n PARALLEL_JOIN: 'parallelJoin',\n}\n\nexport function stepTypeToNodeType(stepType: string): NodeType {\n return STEP_TYPE_TO_NODE_TYPE[stepType] || 'automated'\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,WAAW,YAAY,MAAM,KAAK,UAAU,OAAO,OAAO,OAAO,aAAyB;AAI5F,MAAM,kBAAgD;AAAA,EAC3D,OAAO;AAAA,EACP,KAAK;AAAA,EACL,UAAU;AAAA,EACV,WAAW;AAAA,EACX,aAAa;AAAA,EACb,eAAe;AAAA,EACf,cAAc;AAAA,EACd,cAAc;AAAA,EACd,cAAc;AAChB;AAEO,MAAM,mBAA6C;AAAA,EACxD,OAAO;AAAA,EACP,KAAK;AAAA,EACL,UAAU;AAAA,EACV,WAAW;AAAA,EACX,aAAa;AAAA,EACb,eAAe;AAAA,EACf,cAAc;AAAA;AAAA,EAEd,cAAc;AAAA,EACd,cAAc;AAChB;AAEO,MAAM,mBAA6E;AAAA,EACxF,OAAO,EAAE,OAAO,SAAS,aAAa,mBAAmB;AAAA,EACzD,KAAK,EAAE,OAAO,OAAO,aAAa,sBAAsB;AAAA,EACxD,UAAU,EAAE,OAAO,aAAa,aAAa,gBAAgB;AAAA,EAC7D,WAAW,EAAE,OAAO,aAAa,aAAa,cAAc;AAAA,EAC5D,aAAa,EAAE,OAAO,gBAAgB,aAAa,kBAAkB;AAAA,EACrE,eAAe,EAAE,OAAO,mBAAmB,aAAa,2BAA2B;AAAA,EACnF,cAAc,EAAE,OAAO,kBAAkB,aAAa,uBAAuB;AAAA,EAC7E,cAAc,EAAE,OAAO,iBAAiB,aAAa,+BAA+B;AAAA,EACpF,cAAc,EAAE,OAAO,iBAAiB,aAAa,wBAAwB;AAC/E;AAEA,MAAM,yBAAmD;AAAA,EACvD,OAAO;AAAA,EACP,KAAK;AAAA,EACL,WAAW;AAAA,EACX,WAAW;AAAA,EACX,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,eAAe;AACjB;AAEO,SAAS,mBAAmB,UAA4B;AAC7D,SAAO,uBAAuB,QAAQ,KAAK;AAC7C;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { LockMode } from "@mikro-orm/core";
|
|
2
|
+
import {
|
|
3
|
+
WorkflowInstance,
|
|
4
|
+
WorkflowBranchInstance,
|
|
5
|
+
StepInstance,
|
|
6
|
+
UserTask,
|
|
7
|
+
WorkflowEvent
|
|
8
|
+
} from "../data/entities.js";
|
|
9
|
+
import { logWorkflowEvent } from "./event-logger.js";
|
|
10
|
+
import * as stepHandler from "./step-handler.js";
|
|
11
|
+
import { branchToken } from "./execution-token.js";
|
|
12
|
+
async function resumeBranch(em, options) {
|
|
13
|
+
const branch = await em.findOne(WorkflowBranchInstance, {
|
|
14
|
+
id: options.branchInstanceId,
|
|
15
|
+
workflowInstanceId: options.instanceId,
|
|
16
|
+
tenantId: options.tenantId,
|
|
17
|
+
organizationId: options.organizationId
|
|
18
|
+
});
|
|
19
|
+
if (!branch) return false;
|
|
20
|
+
if (branch.status !== "PAUSED" && branch.status !== "WAITING_FOR_ACTIVITIES") {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const now = /* @__PURE__ */ new Date();
|
|
24
|
+
if (options.contextMerge) {
|
|
25
|
+
branch.contextNamespace = { ...branch.contextNamespace || {}, ...options.contextMerge };
|
|
26
|
+
}
|
|
27
|
+
if (options.exitStepInstanceId) {
|
|
28
|
+
const stepInstance = await em.findOne(StepInstance, {
|
|
29
|
+
id: options.exitStepInstanceId,
|
|
30
|
+
workflowInstanceId: options.instanceId,
|
|
31
|
+
status: "ACTIVE"
|
|
32
|
+
});
|
|
33
|
+
if (stepInstance) {
|
|
34
|
+
await stepHandler.exitStep(em, stepInstance, options.exitOutput);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
branch.status = "ACTIVE";
|
|
38
|
+
branch.pendingTransition = null;
|
|
39
|
+
branch.updatedAt = now;
|
|
40
|
+
await em.flush();
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
function getNestedValue(obj, path) {
|
|
44
|
+
return path.split(".").reduce((current, key) => current == null ? void 0 : current[key], obj);
|
|
45
|
+
}
|
|
46
|
+
async function openFork(em, instance, definition, forkStepDef) {
|
|
47
|
+
const forkStepId = forkStepDef.stepId;
|
|
48
|
+
const joinStepId = forkStepDef.config?.joinStepId;
|
|
49
|
+
if (!joinStepId) {
|
|
50
|
+
throw new Error(`[internal] PARALLEL_FORK "${forkStepId}" missing config.joinStepId`);
|
|
51
|
+
}
|
|
52
|
+
const outgoing = (definition.definition.transitions || []).filter(
|
|
53
|
+
(transition) => transition.fromStepId === forkStepId && transition.trigger === "auto"
|
|
54
|
+
);
|
|
55
|
+
const now = /* @__PURE__ */ new Date();
|
|
56
|
+
const branchKeys = [];
|
|
57
|
+
for (const transition of outgoing) {
|
|
58
|
+
branchKeys.push(transition.transitionId);
|
|
59
|
+
const branch = em.create(WorkflowBranchInstance, {
|
|
60
|
+
workflowInstanceId: instance.id,
|
|
61
|
+
forkStepId,
|
|
62
|
+
joinStepId,
|
|
63
|
+
branchKey: transition.transitionId,
|
|
64
|
+
parentBranchId: null,
|
|
65
|
+
// Start ON the fork; advanceBranches runs the branch_key transition first.
|
|
66
|
+
currentStepId: forkStepId,
|
|
67
|
+
status: "ACTIVE",
|
|
68
|
+
contextNamespace: {},
|
|
69
|
+
tenantId: instance.tenantId,
|
|
70
|
+
organizationId: instance.organizationId,
|
|
71
|
+
startedAt: now,
|
|
72
|
+
createdAt: now,
|
|
73
|
+
updatedAt: now
|
|
74
|
+
});
|
|
75
|
+
em.persist(branch);
|
|
76
|
+
}
|
|
77
|
+
instance.status = "FORKED";
|
|
78
|
+
instance.activeForkStepId = forkStepId;
|
|
79
|
+
instance.updatedAt = now;
|
|
80
|
+
await em.flush();
|
|
81
|
+
await logWorkflowEvent(em, {
|
|
82
|
+
workflowInstanceId: instance.id,
|
|
83
|
+
eventType: "PARALLEL_FORK_OPENED",
|
|
84
|
+
eventData: { forkStepId, joinStepId, branchKeys },
|
|
85
|
+
tenantId: instance.tenantId,
|
|
86
|
+
organizationId: instance.organizationId
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async function resumeBranchAfterActivities(em, container, instanceId, branchInstanceId) {
|
|
90
|
+
const branch = await em.findOne(
|
|
91
|
+
WorkflowBranchInstance,
|
|
92
|
+
{ id: branchInstanceId, workflowInstanceId: instanceId },
|
|
93
|
+
{ lockMode: LockMode.PESSIMISTIC_WRITE }
|
|
94
|
+
);
|
|
95
|
+
if (!branch) {
|
|
96
|
+
throw new Error("[internal] Branch not found during async resume");
|
|
97
|
+
}
|
|
98
|
+
if (branch.status !== "WAITING_FOR_ACTIVITIES") {
|
|
99
|
+
return { continueExecution: false };
|
|
100
|
+
}
|
|
101
|
+
const namespace = branch.contextNamespace || {};
|
|
102
|
+
const pendingJobIds = namespace._pendingAsyncActivities || [];
|
|
103
|
+
const completedEvents = await em.find(WorkflowEvent, {
|
|
104
|
+
workflowInstanceId: instanceId,
|
|
105
|
+
branchInstanceId,
|
|
106
|
+
eventType: "ACTIVITY_COMPLETED",
|
|
107
|
+
eventData: { async: true }
|
|
108
|
+
});
|
|
109
|
+
const failedCount = await em.count(WorkflowEvent, {
|
|
110
|
+
workflowInstanceId: instanceId,
|
|
111
|
+
branchInstanceId,
|
|
112
|
+
eventType: "ACTIVITY_FAILED",
|
|
113
|
+
eventData: { async: true }
|
|
114
|
+
});
|
|
115
|
+
if (completedEvents.length + failedCount < pendingJobIds.length) {
|
|
116
|
+
return { continueExecution: false };
|
|
117
|
+
}
|
|
118
|
+
const now = /* @__PURE__ */ new Date();
|
|
119
|
+
if (failedCount > 0) {
|
|
120
|
+
branch.status = "FAILED";
|
|
121
|
+
branch.errorMessage = `${failedCount} async activities failed in branch "${branch.branchKey}"`;
|
|
122
|
+
branch.updatedAt = now;
|
|
123
|
+
await em.flush();
|
|
124
|
+
return { continueExecution: true };
|
|
125
|
+
}
|
|
126
|
+
const mergedNamespace = { ...namespace };
|
|
127
|
+
for (const event of completedEvents) {
|
|
128
|
+
if (event.eventData?.output) {
|
|
129
|
+
mergedNamespace[`${event.eventData.activityId}_result`] = event.eventData.output;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
delete mergedNamespace._pendingAsyncActivities;
|
|
133
|
+
branch.contextNamespace = mergedNamespace;
|
|
134
|
+
const pending = branch.pendingTransition;
|
|
135
|
+
branch.pendingTransition = null;
|
|
136
|
+
branch.status = "ACTIVE";
|
|
137
|
+
branch.updatedAt = now;
|
|
138
|
+
await em.flush();
|
|
139
|
+
if (pending) {
|
|
140
|
+
const instance = await em.findOne(WorkflowInstance, { id: instanceId });
|
|
141
|
+
if (instance) {
|
|
142
|
+
branch.currentStepId = pending.toStepId;
|
|
143
|
+
await em.flush();
|
|
144
|
+
await stepHandler.executeStep(
|
|
145
|
+
em,
|
|
146
|
+
instance,
|
|
147
|
+
pending.toStepId,
|
|
148
|
+
{ workflowContext: { ...instance.context || {}, ...branch.contextNamespace || {} } },
|
|
149
|
+
container,
|
|
150
|
+
branch
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return { continueExecution: true };
|
|
155
|
+
}
|
|
156
|
+
async function advanceBranches(em, container, instance, definition, context) {
|
|
157
|
+
const forkStepId = instance.activeForkStepId;
|
|
158
|
+
if (!forkStepId) {
|
|
159
|
+
throw new Error("[internal] advanceBranches called but instance has no active fork");
|
|
160
|
+
}
|
|
161
|
+
const allBranchesForBudget = await em.find(WorkflowBranchInstance, {
|
|
162
|
+
workflowInstanceId: instance.id,
|
|
163
|
+
forkStepId
|
|
164
|
+
});
|
|
165
|
+
const maxIterations = Math.max(1, allBranchesForBudget.length) * 100;
|
|
166
|
+
let iterations = 0;
|
|
167
|
+
while (iterations < maxIterations) {
|
|
168
|
+
iterations++;
|
|
169
|
+
const branches = await em.find(WorkflowBranchInstance, {
|
|
170
|
+
workflowInstanceId: instance.id,
|
|
171
|
+
forkStepId
|
|
172
|
+
});
|
|
173
|
+
const active = branches.filter((b) => b.status === "ACTIVE");
|
|
174
|
+
if (active.length === 0) {
|
|
175
|
+
const failed = branches.find((b) => b.status === "FAILED");
|
|
176
|
+
if (failed) {
|
|
177
|
+
await cancelSiblings(em, instance, failed);
|
|
178
|
+
return { outcome: "failed", error: failed.errorMessage || "Branch failed" };
|
|
179
|
+
}
|
|
180
|
+
if (branches.every((b) => b.status === "COMPLETED")) {
|
|
181
|
+
await fireJoin(em, instance, definition, branches, forkStepId);
|
|
182
|
+
return { outcome: "joined" };
|
|
183
|
+
}
|
|
184
|
+
return { outcome: "waiting" };
|
|
185
|
+
}
|
|
186
|
+
for (const branch of active) {
|
|
187
|
+
const result = await advanceOneBranch(em, container, instance, definition, branch, context);
|
|
188
|
+
if (result === "failed") {
|
|
189
|
+
await cancelSiblings(em, instance, branch);
|
|
190
|
+
return { outcome: "failed", error: branch.errorMessage || "Branch failed" };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
throw new Error("[internal] Maximum branch execution iterations reached - possible infinite loop");
|
|
195
|
+
}
|
|
196
|
+
async function advanceOneBranch(em, container, instance, definition, branch, context) {
|
|
197
|
+
if (branch.currentStepId === branch.joinStepId) {
|
|
198
|
+
await completeBranchAtJoin(em, instance, branch);
|
|
199
|
+
return "advanced";
|
|
200
|
+
}
|
|
201
|
+
const transitionHandler = await import("./transition-handler.js");
|
|
202
|
+
const token = branchToken(instance, branch);
|
|
203
|
+
const readContext = { ...instance.context || {}, ...branch.contextNamespace || {} };
|
|
204
|
+
const evalContext = { workflowContext: readContext, userId: context.userId };
|
|
205
|
+
let selected = null;
|
|
206
|
+
if (branch.currentStepId === branch.forkStepId) {
|
|
207
|
+
selected = (definition.definition.transitions || []).find(
|
|
208
|
+
(transition) => transition.transitionId === branch.branchKey
|
|
209
|
+
) || null;
|
|
210
|
+
} else {
|
|
211
|
+
const valid = await transitionHandler.findValidTransitions(
|
|
212
|
+
em,
|
|
213
|
+
instance,
|
|
214
|
+
branch.currentStepId,
|
|
215
|
+
evalContext
|
|
216
|
+
);
|
|
217
|
+
const validAuto = valid.filter((vt) => vt.isValid && vt.transition?.trigger === "auto");
|
|
218
|
+
selected = validAuto.length > 0 ? validAuto[0].transition : null;
|
|
219
|
+
}
|
|
220
|
+
if (!selected) {
|
|
221
|
+
await failBranch(em, instance, branch, `Branch "${branch.branchKey}" has no valid transition from "${branch.currentStepId}"`);
|
|
222
|
+
return "failed";
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
const transitionResult = await transitionHandler.executeTransitionForToken(
|
|
226
|
+
em,
|
|
227
|
+
container,
|
|
228
|
+
token,
|
|
229
|
+
selected.fromStepId,
|
|
230
|
+
selected.toStepId,
|
|
231
|
+
evalContext
|
|
232
|
+
);
|
|
233
|
+
if (!transitionResult.success) {
|
|
234
|
+
await failBranch(em, instance, branch, transitionResult.error || "Branch transition failed");
|
|
235
|
+
return "failed";
|
|
236
|
+
}
|
|
237
|
+
if (branch.currentStepId === branch.joinStepId) {
|
|
238
|
+
await completeBranchAtJoin(em, instance, branch);
|
|
239
|
+
}
|
|
240
|
+
return "advanced";
|
|
241
|
+
} catch (error) {
|
|
242
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
243
|
+
await failBranch(em, instance, branch, message);
|
|
244
|
+
return "failed";
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function completeBranchAtJoin(em, instance, branch) {
|
|
248
|
+
const now = /* @__PURE__ */ new Date();
|
|
249
|
+
branch.status = "COMPLETED";
|
|
250
|
+
branch.completedAt = now;
|
|
251
|
+
branch.updatedAt = now;
|
|
252
|
+
await em.flush();
|
|
253
|
+
await logWorkflowEvent(em, {
|
|
254
|
+
workflowInstanceId: instance.id,
|
|
255
|
+
branchInstanceId: branch.id,
|
|
256
|
+
eventType: "PARALLEL_BRANCH_COMPLETED",
|
|
257
|
+
eventData: { branchKey: branch.branchKey, joinStepId: branch.joinStepId },
|
|
258
|
+
tenantId: instance.tenantId,
|
|
259
|
+
organizationId: instance.organizationId
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
async function failBranch(em, instance, branch, message) {
|
|
263
|
+
const now = /* @__PURE__ */ new Date();
|
|
264
|
+
branch.status = "FAILED";
|
|
265
|
+
branch.errorMessage = message;
|
|
266
|
+
branch.updatedAt = now;
|
|
267
|
+
await em.flush();
|
|
268
|
+
await logWorkflowEvent(em, {
|
|
269
|
+
workflowInstanceId: instance.id,
|
|
270
|
+
branchInstanceId: branch.id,
|
|
271
|
+
eventType: "PARALLEL_BRANCH_FAILED",
|
|
272
|
+
eventData: { branchKey: branch.branchKey, error: message },
|
|
273
|
+
tenantId: instance.tenantId,
|
|
274
|
+
organizationId: instance.organizationId
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
async function cancelSiblings(em, instance, failedBranch) {
|
|
278
|
+
const siblings = await em.find(WorkflowBranchInstance, {
|
|
279
|
+
workflowInstanceId: instance.id,
|
|
280
|
+
forkStepId: failedBranch.forkStepId
|
|
281
|
+
});
|
|
282
|
+
const now = /* @__PURE__ */ new Date();
|
|
283
|
+
for (const sibling of siblings) {
|
|
284
|
+
if (sibling.id === failedBranch.id) continue;
|
|
285
|
+
if (sibling.status === "ACTIVE" || sibling.status === "PAUSED" || sibling.status === "WAITING_FOR_ACTIVITIES") {
|
|
286
|
+
sibling.status = "CANCELLED";
|
|
287
|
+
sibling.updatedAt = now;
|
|
288
|
+
const openTasks = await em.find(UserTask, {
|
|
289
|
+
workflowInstanceId: instance.id,
|
|
290
|
+
branchInstanceId: sibling.id,
|
|
291
|
+
status: "PENDING"
|
|
292
|
+
});
|
|
293
|
+
for (const task of openTasks) {
|
|
294
|
+
task.status = "CANCELLED";
|
|
295
|
+
task.updatedAt = now;
|
|
296
|
+
}
|
|
297
|
+
await em.flush();
|
|
298
|
+
await logWorkflowEvent(em, {
|
|
299
|
+
workflowInstanceId: instance.id,
|
|
300
|
+
branchInstanceId: sibling.id,
|
|
301
|
+
eventType: "PARALLEL_BRANCH_CANCELLED",
|
|
302
|
+
eventData: { branchKey: sibling.branchKey, reason: "sibling-failed" },
|
|
303
|
+
tenantId: instance.tenantId,
|
|
304
|
+
organizationId: instance.organizationId
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
await logWorkflowEvent(em, {
|
|
309
|
+
workflowInstanceId: instance.id,
|
|
310
|
+
eventType: "PARALLEL_FORK_FAILED",
|
|
311
|
+
eventData: { forkStepId: failedBranch.forkStepId, failedBranchKey: failedBranch.branchKey },
|
|
312
|
+
tenantId: instance.tenantId,
|
|
313
|
+
organizationId: instance.organizationId
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
async function fireJoin(em, instance, definition, branches, forkStepId) {
|
|
317
|
+
const joinStepId = branches[0]?.joinStepId;
|
|
318
|
+
if (!joinStepId) {
|
|
319
|
+
throw new Error("[internal] fireJoin called without a join step id");
|
|
320
|
+
}
|
|
321
|
+
const joinStep = definition.definition.steps.find((s) => s.stepId === joinStepId);
|
|
322
|
+
const branchesContext = { ...instance.context?.branches || {} };
|
|
323
|
+
const mergedBranchKeys = [];
|
|
324
|
+
for (const branch of branches) {
|
|
325
|
+
branchesContext[branch.branchKey] = branch.contextNamespace || {};
|
|
326
|
+
mergedBranchKeys.push(branch.branchKey);
|
|
327
|
+
}
|
|
328
|
+
let nextContext = {
|
|
329
|
+
...instance.context || {},
|
|
330
|
+
branches: branchesContext
|
|
331
|
+
};
|
|
332
|
+
const outputMapping = joinStep?.config?.outputMapping;
|
|
333
|
+
if (outputMapping) {
|
|
334
|
+
for (const [topKey, sourcePath] of Object.entries(outputMapping)) {
|
|
335
|
+
if (topKey === "branches") continue;
|
|
336
|
+
const value = getNestedValue(nextContext, sourcePath);
|
|
337
|
+
if (value !== void 0) nextContext[topKey] = value;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const afterJoinStepId = (definition.definition.transitions || []).find(
|
|
341
|
+
(transition) => transition.fromStepId === joinStepId
|
|
342
|
+
)?.toStepId ?? null;
|
|
343
|
+
const now = /* @__PURE__ */ new Date();
|
|
344
|
+
instance.context = nextContext;
|
|
345
|
+
instance.status = "RUNNING";
|
|
346
|
+
instance.activeForkStepId = null;
|
|
347
|
+
instance.currentStepId = joinStepId;
|
|
348
|
+
instance.updatedAt = now;
|
|
349
|
+
await em.flush();
|
|
350
|
+
await logWorkflowEvent(em, {
|
|
351
|
+
workflowInstanceId: instance.id,
|
|
352
|
+
eventType: "PARALLEL_JOIN_COMPLETED",
|
|
353
|
+
eventData: { forkStepId, joinStepId, mergedBranchKeys, afterJoinStepId },
|
|
354
|
+
tenantId: instance.tenantId,
|
|
355
|
+
organizationId: instance.organizationId
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
export {
|
|
359
|
+
advanceBranches,
|
|
360
|
+
openFork,
|
|
361
|
+
resumeBranch,
|
|
362
|
+
resumeBranchAfterActivities
|
|
363
|
+
};
|
|
364
|
+
//# sourceMappingURL=parallel-handler.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/workflows/lib/parallel-handler.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Workflows Module - Parallel Fork / Join Handler\n *\n * Implements PARALLEL_FORK / PARALLEL_JOIN execution with a multi-token model:\n *\n * - `openFork` turns a PARALLEL_FORK step into N persistent branch tokens\n * (`WorkflowBranchInstance`), one per outgoing `auto` transition, and puts the\n * instance into the dormant `FORKED` state.\n * - `advanceBranches` runs the interleaved loop: while the instance is FORKED it\n * advances each ACTIVE branch one step at a time (BPMN semantics \u2014 single lock,\n * no thread-level concurrency). A branch pauses independently (USER_TASK /\n * signal / timer / async activity) without blocking siblings.\n * - When every branch has reached its JOIN (wait-all), `fireJoin` merges the\n * branch namespaces back into `instance.context`, applies optional\n * `outputMapping`, and resumes the root token at the step after the JOIN.\n * - A failed branch cancels its siblings and fails the whole instance.\n *\n * All work happens under the executor's pessimistic instance lock + transaction,\n * so wait-all counting and JOIN firing are race-free.\n */\n\nimport { EntityManager, LockMode } from '@mikro-orm/core'\nimport type { AwilixContainer } from 'awilix'\nimport {\n WorkflowInstance,\n WorkflowBranchInstance,\n WorkflowDefinition,\n StepInstance,\n UserTask,\n WorkflowEvent,\n} from '../data/entities'\nimport { logWorkflowEvent } from './event-logger'\nimport * as stepHandler from './step-handler'\nimport { branchToken } from './execution-token'\n\nexport interface AdvanceBranchesResult {\n outcome: 'joined' | 'waiting' | 'failed'\n error?: string\n}\n\nexport interface ResumeBranchOptions {\n instanceId: string\n branchInstanceId: string\n tenantId: string\n organizationId: string\n /** Values merged into the branch's private namespace before resuming (task form data, signal payload, \u2026). */\n contextMerge?: Record<string, any>\n /** Step instance to exit (the paused step's instance) before the branch advances past it. */\n exitStepInstanceId?: string | null\n exitOutput?: any\n}\n\n/**\n * Resume a single paused/waiting branch: optionally merge data into its\n * namespace, exit its paused step instance, and mark it ACTIVE. The caller then\n * re-enters `executeWorkflow` (FORKED mode) so the interleaved loop advances the\n * branch and, if it is the last to arrive, fires the JOIN.\n *\n * Idempotent: a re-delivered signal/timer/task on an already-ACTIVE or terminal\n * branch is a no-op. Returns false when there is nothing to resume.\n */\nexport async function resumeBranch(\n em: EntityManager,\n options: ResumeBranchOptions,\n): Promise<boolean> {\n const branch = await em.findOne(WorkflowBranchInstance, {\n id: options.branchInstanceId,\n workflowInstanceId: options.instanceId,\n tenantId: options.tenantId,\n organizationId: options.organizationId,\n })\n\n if (!branch) return false\n if (branch.status !== 'PAUSED' && branch.status !== 'WAITING_FOR_ACTIVITIES') {\n // Already active or terminal \u2014 re-delivery; nothing to do.\n return false\n }\n\n const now = new Date()\n if (options.contextMerge) {\n branch.contextNamespace = { ...(branch.contextNamespace || {}), ...options.contextMerge }\n }\n\n if (options.exitStepInstanceId) {\n const stepInstance = await em.findOne(StepInstance, {\n id: options.exitStepInstanceId,\n workflowInstanceId: options.instanceId,\n status: 'ACTIVE',\n })\n if (stepInstance) {\n await stepHandler.exitStep(em, stepInstance, options.exitOutput)\n }\n }\n\n branch.status = 'ACTIVE'\n branch.pendingTransition = null\n branch.updatedAt = now\n await em.flush()\n\n return true\n}\n\ninterface ParallelContext {\n userId?: string\n}\n\nfunction getNestedValue(obj: any, path: string): any {\n return path.split('.').reduce((current, key) => (current == null ? undefined : current[key]), obj)\n}\n\n/**\n * Open a PARALLEL_FORK: create one ACTIVE branch per outgoing `auto` transition\n * and mark the instance FORKED. Branch tokens start positioned ON the fork step\n * so the interleaved loop runs each branch's fork transition (and its\n * activities) in that branch's own context.\n */\nexport async function openFork(\n em: EntityManager,\n instance: WorkflowInstance,\n definition: WorkflowDefinition,\n forkStepDef: any,\n): Promise<void> {\n const forkStepId: string = forkStepDef.stepId\n const joinStepId: string | undefined = forkStepDef.config?.joinStepId\n if (!joinStepId) {\n throw new Error(`[internal] PARALLEL_FORK \"${forkStepId}\" missing config.joinStepId`)\n }\n\n const outgoing = (definition.definition.transitions || []).filter(\n (transition: any) => transition.fromStepId === forkStepId && transition.trigger === 'auto',\n )\n\n const now = new Date()\n const branchKeys: string[] = []\n for (const transition of outgoing) {\n branchKeys.push(transition.transitionId)\n const branch = em.create(WorkflowBranchInstance, {\n workflowInstanceId: instance.id,\n forkStepId,\n joinStepId,\n branchKey: transition.transitionId,\n parentBranchId: null,\n // Start ON the fork; advanceBranches runs the branch_key transition first.\n currentStepId: forkStepId,\n status: 'ACTIVE',\n contextNamespace: {},\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n startedAt: now,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(branch)\n }\n\n instance.status = 'FORKED'\n instance.activeForkStepId = forkStepId\n instance.updatedAt = now\n await em.flush()\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n eventType: 'PARALLEL_FORK_OPENED',\n eventData: { forkStepId, joinStepId, branchKeys },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n}\n\n/**\n * Resume a branch that was WAITING_FOR_ACTIVITIES after its async activities\n * finished. Mirrors the instance-level resume but scoped to the branch:\n * merges completed-activity outputs into the branch namespace, advances the\n * branch's pending transition target, and marks it ACTIVE (or FAILED if any\n * async activity failed). The caller re-enters the interleaved loop.\n */\nexport async function resumeBranchAfterActivities(\n em: EntityManager,\n container: AwilixContainer,\n instanceId: string,\n branchInstanceId: string,\n): Promise<{ continueExecution: boolean }> {\n const branch = await em.findOne(\n WorkflowBranchInstance,\n { id: branchInstanceId, workflowInstanceId: instanceId },\n { lockMode: LockMode.PESSIMISTIC_WRITE },\n )\n if (!branch) {\n throw new Error('[internal] Branch not found during async resume')\n }\n if (branch.status !== 'WAITING_FOR_ACTIVITIES') {\n // Re-delivery or already advanced \u2014 nothing to do.\n return { continueExecution: false }\n }\n\n const namespace = branch.contextNamespace || {}\n const pendingJobIds = (namespace._pendingAsyncActivities as any[]) || []\n\n const completedEvents = await em.find(WorkflowEvent, {\n workflowInstanceId: instanceId,\n branchInstanceId,\n eventType: 'ACTIVITY_COMPLETED',\n eventData: { async: true },\n })\n const failedCount = await em.count(WorkflowEvent, {\n workflowInstanceId: instanceId,\n branchInstanceId,\n eventType: 'ACTIVITY_FAILED',\n eventData: { async: true },\n })\n\n if (completedEvents.length + failedCount < pendingJobIds.length) {\n // Still waiting on other branch activities.\n return { continueExecution: false }\n }\n\n const now = new Date()\n if (failedCount > 0) {\n branch.status = 'FAILED'\n branch.errorMessage = `${failedCount} async activities failed in branch \"${branch.branchKey}\"`\n branch.updatedAt = now\n await em.flush()\n // The interleaved loop will observe the FAILED branch and cancel siblings.\n return { continueExecution: true }\n }\n\n const mergedNamespace: Record<string, any> = { ...namespace }\n for (const event of completedEvents) {\n if (event.eventData?.output) {\n mergedNamespace[`${event.eventData.activityId}_result`] = event.eventData.output\n }\n }\n delete mergedNamespace._pendingAsyncActivities\n branch.contextNamespace = mergedNamespace\n\n const pending = branch.pendingTransition\n branch.pendingTransition = null\n branch.status = 'ACTIVE'\n branch.updatedAt = now\n await em.flush()\n\n // Execute the destination step in branch context (mirrors the instance-level\n // resume) so the branch cursor lands on an executed step. The step may pause\n // the branch again (e.g. a following USER_TASK); that is handled by the loop.\n if (pending) {\n const instance = await em.findOne(WorkflowInstance, { id: instanceId })\n if (instance) {\n branch.currentStepId = pending.toStepId\n await em.flush()\n await stepHandler.executeStep(\n em,\n instance,\n pending.toStepId,\n { workflowContext: { ...(instance.context || {}), ...(branch.contextNamespace || {}) } },\n container,\n branch,\n )\n }\n }\n\n return { continueExecution: true }\n}\n\n/**\n * Interleaved branch execution loop. Advances ACTIVE branches one step at a\n * time until either all branches reach the JOIN (\u2192 fireJoin \u2192 'joined'), a\n * branch fails (\u2192 cancel siblings + 'failed'), or no branch is ACTIVE because\n * they are all waiting on external resume (\u2192 'waiting').\n */\nexport async function advanceBranches(\n em: EntityManager,\n container: AwilixContainer,\n instance: WorkflowInstance,\n definition: WorkflowDefinition,\n context: ParallelContext,\n): Promise<AdvanceBranchesResult> {\n const forkStepId = instance.activeForkStepId\n if (!forkStepId) {\n throw new Error('[internal] advanceBranches called but instance has no active fork')\n }\n\n // Branch-aware iteration budget: each branch gets the same per-token budget as\n // the single-token loop, so fan-out alone never trips the guard.\n const allBranchesForBudget = await em.find(WorkflowBranchInstance, {\n workflowInstanceId: instance.id,\n forkStepId,\n })\n const maxIterations = Math.max(1, allBranchesForBudget.length) * 100\n let iterations = 0\n\n while (iterations < maxIterations) {\n iterations++\n\n const branches = await em.find(WorkflowBranchInstance, {\n workflowInstanceId: instance.id,\n forkStepId,\n })\n\n const active = branches.filter((b) => b.status === 'ACTIVE')\n\n if (active.length === 0) {\n const failed = branches.find((b) => b.status === 'FAILED')\n if (failed) {\n // A branch failed out-of-band (e.g. async-activity resume marked it\n // FAILED); cancel any non-terminal siblings before failing the instance.\n await cancelSiblings(em, instance, failed)\n return { outcome: 'failed', error: failed.errorMessage || 'Branch failed' }\n }\n if (branches.every((b) => b.status === 'COMPLETED')) {\n await fireJoin(em, instance, definition, branches, forkStepId)\n return { outcome: 'joined' }\n }\n // Some branches paused/waiting for external resume \u2014 instance idles.\n return { outcome: 'waiting' }\n }\n\n for (const branch of active) {\n const result = await advanceOneBranch(em, container, instance, definition, branch, context)\n if (result === 'failed') {\n await cancelSiblings(em, instance, branch)\n return { outcome: 'failed', error: branch.errorMessage || 'Branch failed' }\n }\n }\n }\n\n throw new Error('[internal] Maximum branch execution iterations reached - possible infinite loop')\n}\n\n/**\n * Advance a single ACTIVE branch by one step. Returns 'failed' if the branch\n * failed; otherwise mutates branch state (advanced / completed@join / paused).\n */\nasync function advanceOneBranch(\n em: EntityManager,\n container: AwilixContainer,\n instance: WorkflowInstance,\n definition: WorkflowDefinition,\n branch: WorkflowBranchInstance,\n context: ParallelContext,\n): Promise<'advanced' | 'failed'> {\n // Already at the join \u2192 synchronize (mark completed, do not execute the join).\n if (branch.currentStepId === branch.joinStepId) {\n await completeBranchAtJoin(em, instance, branch)\n return 'advanced'\n }\n\n const transitionHandler = await import('./transition-handler')\n\n const token = branchToken(instance, branch)\n // Token read context = instance snapshot overlaid with branch namespace.\n const readContext = { ...(instance.context || {}), ...(branch.contextNamespace || {}) }\n const evalContext = { workflowContext: readContext, userId: context.userId }\n\n // Select the transition to take. Branches positioned on the fork follow their\n // own branch_key transition; otherwise pick the highest-priority valid auto one.\n let selected: any | null = null\n if (branch.currentStepId === branch.forkStepId) {\n selected = (definition.definition.transitions || []).find(\n (transition: any) => transition.transitionId === branch.branchKey,\n ) || null\n } else {\n const valid = await transitionHandler.findValidTransitions(\n em,\n instance,\n branch.currentStepId,\n evalContext,\n )\n const validAuto = valid.filter((vt) => vt.isValid && vt.transition?.trigger === 'auto')\n selected = validAuto.length > 0 ? validAuto[0].transition : null\n }\n\n if (!selected) {\n // No outgoing transition and not at the join \u2192 the branch is stuck.\n await failBranch(em, instance, branch, `Branch \"${branch.branchKey}\" has no valid transition from \"${branch.currentStepId}\"`)\n return 'failed'\n }\n\n try {\n const transitionResult = await transitionHandler.executeTransitionForToken(\n em,\n container,\n token,\n selected.fromStepId,\n selected.toStepId,\n evalContext,\n )\n\n if (!transitionResult.success) {\n await failBranch(em, instance, branch, transitionResult.error || 'Branch transition failed')\n return 'failed'\n }\n\n // executeTransitionForToken set branch.currentStepId = toStepId. If that is\n // the join, synchronize now (the join step itself is a no-op).\n if (branch.currentStepId === branch.joinStepId) {\n await completeBranchAtJoin(em, instance, branch)\n }\n return 'advanced'\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n await failBranch(em, instance, branch, message)\n return 'failed'\n }\n}\n\nasync function completeBranchAtJoin(\n em: EntityManager,\n instance: WorkflowInstance,\n branch: WorkflowBranchInstance,\n): Promise<void> {\n const now = new Date()\n branch.status = 'COMPLETED'\n branch.completedAt = now\n branch.updatedAt = now\n await em.flush()\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n branchInstanceId: branch.id,\n eventType: 'PARALLEL_BRANCH_COMPLETED',\n eventData: { branchKey: branch.branchKey, joinStepId: branch.joinStepId },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n}\n\nasync function failBranch(\n em: EntityManager,\n instance: WorkflowInstance,\n branch: WorkflowBranchInstance,\n message: string,\n): Promise<void> {\n const now = new Date()\n branch.status = 'FAILED'\n branch.errorMessage = message\n branch.updatedAt = now\n await em.flush()\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n branchInstanceId: branch.id,\n eventType: 'PARALLEL_BRANCH_FAILED',\n eventData: { branchKey: branch.branchKey, error: message },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n}\n\n/**\n * Cancel sibling branches of a failed branch (best-effort), cancel their open\n * user tasks, and log a cancellation event per branch.\n */\nasync function cancelSiblings(\n em: EntityManager,\n instance: WorkflowInstance,\n failedBranch: WorkflowBranchInstance,\n): Promise<void> {\n const siblings = await em.find(WorkflowBranchInstance, {\n workflowInstanceId: instance.id,\n forkStepId: failedBranch.forkStepId,\n })\n\n const now = new Date()\n for (const sibling of siblings) {\n if (sibling.id === failedBranch.id) continue\n if (sibling.status === 'ACTIVE' || sibling.status === 'PAUSED' || sibling.status === 'WAITING_FOR_ACTIVITIES') {\n sibling.status = 'CANCELLED'\n sibling.updatedAt = now\n\n // Best-effort: cancel the branch's open user tasks.\n const openTasks = await em.find(UserTask, {\n workflowInstanceId: instance.id,\n branchInstanceId: sibling.id,\n status: 'PENDING',\n })\n for (const task of openTasks) {\n task.status = 'CANCELLED'\n task.updatedAt = now\n }\n\n await em.flush()\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n branchInstanceId: sibling.id,\n eventType: 'PARALLEL_BRANCH_CANCELLED',\n eventData: { branchKey: sibling.branchKey, reason: 'sibling-failed' },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n }\n }\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n eventType: 'PARALLEL_FORK_FAILED',\n eventData: { forkStepId: failedBranch.forkStepId, failedBranchKey: failedBranch.branchKey },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n}\n\n/**\n * Fire the JOIN once all branches are COMPLETED: merge each branch namespace\n * under `instance.context.branches[branchKey]`, apply optional outputMapping to\n * lift selected values to the top level, then resume the root token at the step\n * after the JOIN.\n */\nasync function fireJoin(\n em: EntityManager,\n instance: WorkflowInstance,\n definition: WorkflowDefinition,\n branches: WorkflowBranchInstance[],\n forkStepId: string,\n): Promise<void> {\n const joinStepId = branches[0]?.joinStepId\n if (!joinStepId) {\n throw new Error('[internal] fireJoin called without a join step id')\n }\n\n const joinStep = definition.definition.steps.find((s: any) => s.stepId === joinStepId)\n\n // Deterministic merge \u2014 no silent collisions: each branch keeps its own slot.\n const branchesContext: Record<string, any> = { ...(instance.context?.branches || {}) }\n const mergedBranchKeys: string[] = []\n for (const branch of branches) {\n branchesContext[branch.branchKey] = branch.contextNamespace || {}\n mergedBranchKeys.push(branch.branchKey)\n }\n\n let nextContext: Record<string, any> = {\n ...(instance.context || {}),\n branches: branchesContext,\n }\n\n // Optional outputMapping: topLevelKey -> 'branches.<branchKey>.<path>'. The\n // reserved `branches` slot map is never overwritten \u2014 guard it explicitly so a\n // mapping cannot clobber the per-branch namespaces.\n const outputMapping: Record<string, string> | undefined = joinStep?.config?.outputMapping\n if (outputMapping) {\n for (const [topKey, sourcePath] of Object.entries(outputMapping)) {\n if (topKey === 'branches') continue\n const value = getNestedValue(nextContext, sourcePath)\n if (value !== undefined) nextContext[topKey] = value\n }\n }\n\n // Park the root token ON the JOIN step (not its successor) and let the\n // single-token executor loop run the JOIN's outgoing transition. Jumping\n // straight to the post-join step would bypass stepHandler.executeStep() for\n // that step \u2014 so a following USER_TASK / WAIT_FOR_TIMER / WAIT_FOR_SIGNAL would\n // never have its task created / timer enqueued / signal wait registered (the\n // instance would hang), and any activities on the JOIN's outgoing transition\n // would be skipped. The JOIN step itself is a no-op, so the loop simply runs\n // its outgoing transition through the normal step-entry path.\n const afterJoinStepId = (definition.definition.transitions || []).find(\n (transition: any) => transition.fromStepId === joinStepId,\n )?.toStepId ?? null\n\n const now = new Date()\n instance.context = nextContext\n instance.status = 'RUNNING'\n instance.activeForkStepId = null\n instance.currentStepId = joinStepId\n instance.updatedAt = now\n await em.flush()\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n eventType: 'PARALLEL_JOIN_COMPLETED',\n eventData: { forkStepId, joinStepId, mergedBranchKeys, afterJoinStepId },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n}\n"],
|
|
5
|
+
"mappings": "AAqBA,SAAwB,gBAAgB;AAExC;AAAA,EACE;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,wBAAwB;AACjC,YAAY,iBAAiB;AAC7B,SAAS,mBAAmB;AA4B5B,eAAsB,aACpB,IACA,SACkB;AAClB,QAAM,SAAS,MAAM,GAAG,QAAQ,wBAAwB;AAAA,IACtD,IAAI,QAAQ;AAAA,IACZ,oBAAoB,QAAQ;AAAA,IAC5B,UAAU,QAAQ;AAAA,IAClB,gBAAgB,QAAQ;AAAA,EAC1B,CAAC;AAED,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,OAAO,WAAW,YAAY,OAAO,WAAW,0BAA0B;AAE5E,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,oBAAI,KAAK;AACrB,MAAI,QAAQ,cAAc;AACxB,WAAO,mBAAmB,EAAE,GAAI,OAAO,oBAAoB,CAAC,GAAI,GAAG,QAAQ,aAAa;AAAA,EAC1F;AAEA,MAAI,QAAQ,oBAAoB;AAC9B,UAAM,eAAe,MAAM,GAAG,QAAQ,cAAc;AAAA,MAClD,IAAI,QAAQ;AAAA,MACZ,oBAAoB,QAAQ;AAAA,MAC5B,QAAQ;AAAA,IACV,CAAC;AACD,QAAI,cAAc;AAChB,YAAM,YAAY,SAAS,IAAI,cAAc,QAAQ,UAAU;AAAA,IACjE;AAAA,EACF;AAEA,SAAO,SAAS;AAChB,SAAO,oBAAoB;AAC3B,SAAO,YAAY;AACnB,QAAM,GAAG,MAAM;AAEf,SAAO;AACT;AAMA,SAAS,eAAe,KAAU,MAAmB;AACnD,SAAO,KAAK,MAAM,GAAG,EAAE,OAAO,CAAC,SAAS,QAAS,WAAW,OAAO,SAAY,QAAQ,GAAG,GAAI,GAAG;AACnG;AAQA,eAAsB,SACpB,IACA,UACA,YACA,aACe;AACf,QAAM,aAAqB,YAAY;AACvC,QAAM,aAAiC,YAAY,QAAQ;AAC3D,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,6BAA6B,UAAU,6BAA6B;AAAA,EACtF;AAEA,QAAM,YAAY,WAAW,WAAW,eAAe,CAAC,GAAG;AAAA,IACzD,CAAC,eAAoB,WAAW,eAAe,cAAc,WAAW,YAAY;AAAA,EACtF;AAEA,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,aAAuB,CAAC;AAC9B,aAAW,cAAc,UAAU;AACjC,eAAW,KAAK,WAAW,YAAY;AACvC,UAAM,SAAS,GAAG,OAAO,wBAAwB;AAAA,MAC/C,oBAAoB,SAAS;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,WAAW,WAAW;AAAA,MACtB,gBAAgB;AAAA;AAAA,MAEhB,eAAe;AAAA,MACf,QAAQ;AAAA,MACR,kBAAkB,CAAC;AAAA,MACnB,UAAU,SAAS;AAAA,MACnB,gBAAgB,SAAS;AAAA,MACzB,WAAW;AAAA,MACX,WAAW;AAAA,MACX,WAAW;AAAA,IACb,CAAC;AACD,OAAG,QAAQ,MAAM;AAAA,EACnB;AAEA,WAAS,SAAS;AAClB,WAAS,mBAAmB;AAC5B,WAAS,YAAY;AACrB,QAAM,GAAG,MAAM;AAEf,QAAM,iBAAiB,IAAI;AAAA,IACzB,oBAAoB,SAAS;AAAA,IAC7B,WAAW;AAAA,IACX,WAAW,EAAE,YAAY,YAAY,WAAW;AAAA,IAChD,UAAU,SAAS;AAAA,IACnB,gBAAgB,SAAS;AAAA,EAC3B,CAAC;AACH;AASA,eAAsB,4BACpB,IACA,WACA,YACA,kBACyC;AACzC,QAAM,SAAS,MAAM,GAAG;AAAA,IACtB;AAAA,IACA,EAAE,IAAI,kBAAkB,oBAAoB,WAAW;AAAA,IACvD,EAAE,UAAU,SAAS,kBAAkB;AAAA,EACzC;AACA,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AACA,MAAI,OAAO,WAAW,0BAA0B;AAE9C,WAAO,EAAE,mBAAmB,MAAM;AAAA,EACpC;AAEA,QAAM,YAAY,OAAO,oBAAoB,CAAC;AAC9C,QAAM,gBAAiB,UAAU,2BAAqC,CAAC;AAEvE,QAAM,kBAAkB,MAAM,GAAG,KAAK,eAAe;AAAA,IACnD,oBAAoB;AAAA,IACpB;AAAA,IACA,WAAW;AAAA,IACX,WAAW,EAAE,OAAO,KAAK;AAAA,EAC3B,CAAC;AACD,QAAM,cAAc,MAAM,GAAG,MAAM,eAAe;AAAA,IAChD,oBAAoB;AAAA,IACpB;AAAA,IACA,WAAW;AAAA,IACX,WAAW,EAAE,OAAO,KAAK;AAAA,EAC3B,CAAC;AAED,MAAI,gBAAgB,SAAS,cAAc,cAAc,QAAQ;AAE/D,WAAO,EAAE,mBAAmB,MAAM;AAAA,EACpC;AAEA,QAAM,MAAM,oBAAI,KAAK;AACrB,MAAI,cAAc,GAAG;AACnB,WAAO,SAAS;AAChB,WAAO,eAAe,GAAG,WAAW,uCAAuC,OAAO,SAAS;AAC3F,WAAO,YAAY;AACnB,UAAM,GAAG,MAAM;AAEf,WAAO,EAAE,mBAAmB,KAAK;AAAA,EACnC;AAEA,QAAM,kBAAuC,EAAE,GAAG,UAAU;AAC5D,aAAW,SAAS,iBAAiB;AACnC,QAAI,MAAM,WAAW,QAAQ;AAC3B,sBAAgB,GAAG,MAAM,UAAU,UAAU,SAAS,IAAI,MAAM,UAAU;AAAA,IAC5E;AAAA,EACF;AACA,SAAO,gBAAgB;AACvB,SAAO,mBAAmB;AAE1B,QAAM,UAAU,OAAO;AACvB,SAAO,oBAAoB;AAC3B,SAAO,SAAS;AAChB,SAAO,YAAY;AACnB,QAAM,GAAG,MAAM;AAKf,MAAI,SAAS;AACX,UAAM,WAAW,MAAM,GAAG,QAAQ,kBAAkB,EAAE,IAAI,WAAW,CAAC;AACtE,QAAI,UAAU;AACZ,aAAO,gBAAgB,QAAQ;AAC/B,YAAM,GAAG,MAAM;AACf,YAAM,YAAY;AAAA,QAChB;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR,EAAE,iBAAiB,EAAE,GAAI,SAAS,WAAW,CAAC,GAAI,GAAI,OAAO,oBAAoB,CAAC,EAAG,EAAE;AAAA,QACvF;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,mBAAmB,KAAK;AACnC;AAQA,eAAsB,gBACpB,IACA,WACA,UACA,YACA,SACgC;AAChC,QAAM,aAAa,SAAS;AAC5B,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,mEAAmE;AAAA,EACrF;AAIA,QAAM,uBAAuB,MAAM,GAAG,KAAK,wBAAwB;AAAA,IACjE,oBAAoB,SAAS;AAAA,IAC7B;AAAA,EACF,CAAC;AACD,QAAM,gBAAgB,KAAK,IAAI,GAAG,qBAAqB,MAAM,IAAI;AACjE,MAAI,aAAa;AAEjB,SAAO,aAAa,eAAe;AACjC;AAEA,UAAM,WAAW,MAAM,GAAG,KAAK,wBAAwB;AAAA,MACrD,oBAAoB,SAAS;AAAA,MAC7B;AAAA,IACF,CAAC;AAED,UAAM,SAAS,SAAS,OAAO,CAAC,MAAM,EAAE,WAAW,QAAQ;AAE3D,QAAI,OAAO,WAAW,GAAG;AACvB,YAAM,SAAS,SAAS,KAAK,CAAC,MAAM,EAAE,WAAW,QAAQ;AACzD,UAAI,QAAQ;AAGV,cAAM,eAAe,IAAI,UAAU,MAAM;AACzC,eAAO,EAAE,SAAS,UAAU,OAAO,OAAO,gBAAgB,gBAAgB;AAAA,MAC5E;AACA,UAAI,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,WAAW,GAAG;AACnD,cAAM,SAAS,IAAI,UAAU,YAAY,UAAU,UAAU;AAC7D,eAAO,EAAE,SAAS,SAAS;AAAA,MAC7B;AAEA,aAAO,EAAE,SAAS,UAAU;AAAA,IAC9B;AAEA,eAAW,UAAU,QAAQ;AAC3B,YAAM,SAAS,MAAM,iBAAiB,IAAI,WAAW,UAAU,YAAY,QAAQ,OAAO;AAC1F,UAAI,WAAW,UAAU;AACvB,cAAM,eAAe,IAAI,UAAU,MAAM;AACzC,eAAO,EAAE,SAAS,UAAU,OAAO,OAAO,gBAAgB,gBAAgB;AAAA,MAC5E;AAAA,IACF;AAAA,EACF;AAEA,QAAM,IAAI,MAAM,iFAAiF;AACnG;AAMA,eAAe,iBACb,IACA,WACA,UACA,YACA,QACA,SACgC;AAEhC,MAAI,OAAO,kBAAkB,OAAO,YAAY;AAC9C,UAAM,qBAAqB,IAAI,UAAU,MAAM;AAC/C,WAAO;AAAA,EACT;AAEA,QAAM,oBAAoB,MAAM,OAAO,sBAAsB;AAE7D,QAAM,QAAQ,YAAY,UAAU,MAAM;AAE1C,QAAM,cAAc,EAAE,GAAI,SAAS,WAAW,CAAC,GAAI,GAAI,OAAO,oBAAoB,CAAC,EAAG;AACtF,QAAM,cAAc,EAAE,iBAAiB,aAAa,QAAQ,QAAQ,OAAO;AAI3E,MAAI,WAAuB;AAC3B,MAAI,OAAO,kBAAkB,OAAO,YAAY;AAC9C,gBAAY,WAAW,WAAW,eAAe,CAAC,GAAG;AAAA,MACnD,CAAC,eAAoB,WAAW,iBAAiB,OAAO;AAAA,IAC1D,KAAK;AAAA,EACP,OAAO;AACL,UAAM,QAAQ,MAAM,kBAAkB;AAAA,MACpC;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP;AAAA,IACF;AACA,UAAM,YAAY,MAAM,OAAO,CAAC,OAAO,GAAG,WAAW,GAAG,YAAY,YAAY,MAAM;AACtF,eAAW,UAAU,SAAS,IAAI,UAAU,CAAC,EAAE,aAAa;AAAA,EAC9D;AAEA,MAAI,CAAC,UAAU;AAEb,UAAM,WAAW,IAAI,UAAU,QAAQ,WAAW,OAAO,SAAS,mCAAmC,OAAO,aAAa,GAAG;AAC5H,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,mBAAmB,MAAM,kBAAkB;AAAA,MAC/C;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,SAAS;AAAA,MACT;AAAA,IACF;AAEA,QAAI,CAAC,iBAAiB,SAAS;AAC7B,YAAM,WAAW,IAAI,UAAU,QAAQ,iBAAiB,SAAS,0BAA0B;AAC3F,aAAO;AAAA,IACT;AAIA,QAAI,OAAO,kBAAkB,OAAO,YAAY;AAC9C,YAAM,qBAAqB,IAAI,UAAU,MAAM;AAAA,IACjD;AACA,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,UAAM,WAAW,IAAI,UAAU,QAAQ,OAAO;AAC9C,WAAO;AAAA,EACT;AACF;AAEA,eAAe,qBACb,IACA,UACA,QACe;AACf,QAAM,MAAM,oBAAI,KAAK;AACrB,SAAO,SAAS;AAChB,SAAO,cAAc;AACrB,SAAO,YAAY;AACnB,QAAM,GAAG,MAAM;AAEf,QAAM,iBAAiB,IAAI;AAAA,IACzB,oBAAoB,SAAS;AAAA,IAC7B,kBAAkB,OAAO;AAAA,IACzB,WAAW;AAAA,IACX,WAAW,EAAE,WAAW,OAAO,WAAW,YAAY,OAAO,WAAW;AAAA,IACxE,UAAU,SAAS;AAAA,IACnB,gBAAgB,SAAS;AAAA,EAC3B,CAAC;AACH;AAEA,eAAe,WACb,IACA,UACA,QACA,SACe;AACf,QAAM,MAAM,oBAAI,KAAK;AACrB,SAAO,SAAS;AAChB,SAAO,eAAe;AACtB,SAAO,YAAY;AACnB,QAAM,GAAG,MAAM;AAEf,QAAM,iBAAiB,IAAI;AAAA,IACzB,oBAAoB,SAAS;AAAA,IAC7B,kBAAkB,OAAO;AAAA,IACzB,WAAW;AAAA,IACX,WAAW,EAAE,WAAW,OAAO,WAAW,OAAO,QAAQ;AAAA,IACzD,UAAU,SAAS;AAAA,IACnB,gBAAgB,SAAS;AAAA,EAC3B,CAAC;AACH;AAMA,eAAe,eACb,IACA,UACA,cACe;AACf,QAAM,WAAW,MAAM,GAAG,KAAK,wBAAwB;AAAA,IACrD,oBAAoB,SAAS;AAAA,IAC7B,YAAY,aAAa;AAAA,EAC3B,CAAC;AAED,QAAM,MAAM,oBAAI,KAAK;AACrB,aAAW,WAAW,UAAU;AAC9B,QAAI,QAAQ,OAAO,aAAa,GAAI;AACpC,QAAI,QAAQ,WAAW,YAAY,QAAQ,WAAW,YAAY,QAAQ,WAAW,0BAA0B;AAC7G,cAAQ,SAAS;AACjB,cAAQ,YAAY;AAGpB,YAAM,YAAY,MAAM,GAAG,KAAK,UAAU;AAAA,QACxC,oBAAoB,SAAS;AAAA,QAC7B,kBAAkB,QAAQ;AAAA,QAC1B,QAAQ;AAAA,MACV,CAAC;AACD,iBAAW,QAAQ,WAAW;AAC5B,aAAK,SAAS;AACd,aAAK,YAAY;AAAA,MACnB;AAEA,YAAM,GAAG,MAAM;AAEf,YAAM,iBAAiB,IAAI;AAAA,QACzB,oBAAoB,SAAS;AAAA,QAC7B,kBAAkB,QAAQ;AAAA,QAC1B,WAAW;AAAA,QACX,WAAW,EAAE,WAAW,QAAQ,WAAW,QAAQ,iBAAiB;AAAA,QACpE,UAAU,SAAS;AAAA,QACnB,gBAAgB,SAAS;AAAA,MAC3B,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,iBAAiB,IAAI;AAAA,IACzB,oBAAoB,SAAS;AAAA,IAC7B,WAAW;AAAA,IACX,WAAW,EAAE,YAAY,aAAa,YAAY,iBAAiB,aAAa,UAAU;AAAA,IAC1F,UAAU,SAAS;AAAA,IACnB,gBAAgB,SAAS;AAAA,EAC3B,CAAC;AACH;AAQA,eAAe,SACb,IACA,UACA,YACA,UACA,YACe;AACf,QAAM,aAAa,SAAS,CAAC,GAAG;AAChC,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AAEA,QAAM,WAAW,WAAW,WAAW,MAAM,KAAK,CAAC,MAAW,EAAE,WAAW,UAAU;AAGrF,QAAM,kBAAuC,EAAE,GAAI,SAAS,SAAS,YAAY,CAAC,EAAG;AACrF,QAAM,mBAA6B,CAAC;AACpC,aAAW,UAAU,UAAU;AAC7B,oBAAgB,OAAO,SAAS,IAAI,OAAO,oBAAoB,CAAC;AAChE,qBAAiB,KAAK,OAAO,SAAS;AAAA,EACxC;AAEA,MAAI,cAAmC;AAAA,IACrC,GAAI,SAAS,WAAW,CAAC;AAAA,IACzB,UAAU;AAAA,EACZ;AAKA,QAAM,gBAAoD,UAAU,QAAQ;AAC5E,MAAI,eAAe;AACjB,eAAW,CAAC,QAAQ,UAAU,KAAK,OAAO,QAAQ,aAAa,GAAG;AAChE,UAAI,WAAW,WAAY;AAC3B,YAAM,QAAQ,eAAe,aAAa,UAAU;AACpD,UAAI,UAAU,OAAW,aAAY,MAAM,IAAI;AAAA,IACjD;AAAA,EACF;AAUA,QAAM,mBAAmB,WAAW,WAAW,eAAe,CAAC,GAAG;AAAA,IAChE,CAAC,eAAoB,WAAW,eAAe;AAAA,EACjD,GAAG,YAAY;AAEf,QAAM,MAAM,oBAAI,KAAK;AACrB,WAAS,UAAU;AACnB,WAAS,SAAS;AAClB,WAAS,mBAAmB;AAC5B,WAAS,gBAAgB;AACzB,WAAS,YAAY;AACrB,QAAM,GAAG,MAAM;AAEf,QAAM,iBAAiB,IAAI;AAAA,IACzB,oBAAoB,SAAS;AAAA,IAC7B,WAAW;AAAA,IACX,WAAW,EAAE,YAAY,YAAY,kBAAkB,gBAAgB;AAAA,IACvE,UAAU,SAAS;AAAA,IACnB,gBAAgB,SAAS;AAAA,EAC3B,CAAC;AACH;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
2
|
-
import { WorkflowInstance, WorkflowDefinition, StepInstance } from "../data/entities.js";
|
|
2
|
+
import { WorkflowInstance, WorkflowBranchInstance, WorkflowDefinition, StepInstance } from "../data/entities.js";
|
|
3
3
|
class SignalError extends Error {
|
|
4
4
|
constructor(message, code, details) {
|
|
5
5
|
super(message);
|
|
@@ -32,6 +32,68 @@ async function sendSignal(em, container, options) {
|
|
|
32
32
|
{ instanceId }
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
|
+
if (instance.status === "FORKED") {
|
|
36
|
+
const branchDefinition = await findOneWithDecryption(
|
|
37
|
+
em,
|
|
38
|
+
WorkflowDefinition,
|
|
39
|
+
{ id: instance.definitionId, tenantId: instance.tenantId, organizationId: instance.organizationId, deletedAt: null },
|
|
40
|
+
void 0,
|
|
41
|
+
{ tenantId: instance.tenantId, organizationId: instance.organizationId }
|
|
42
|
+
);
|
|
43
|
+
if (!branchDefinition) {
|
|
44
|
+
throw new SignalError("Workflow definition not found", "DEFINITION_NOT_FOUND", { definitionId: instance.definitionId });
|
|
45
|
+
}
|
|
46
|
+
const pausedBranches = await em.find(WorkflowBranchInstance, {
|
|
47
|
+
workflowInstanceId: instanceId,
|
|
48
|
+
status: "PAUSED",
|
|
49
|
+
tenantId,
|
|
50
|
+
organizationId
|
|
51
|
+
});
|
|
52
|
+
let targetBranch = null;
|
|
53
|
+
for (const candidate of pausedBranches) {
|
|
54
|
+
const step = branchDefinition.definition.steps.find((s) => s.stepId === candidate.currentStepId);
|
|
55
|
+
if (step?.stepType === "WAIT_FOR_SIGNAL") {
|
|
56
|
+
const candidateSignal = step.signalConfig?.signalName || step.stepId;
|
|
57
|
+
if (candidateSignal === signalName) {
|
|
58
|
+
targetBranch = candidate;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!targetBranch) {
|
|
64
|
+
throw new SignalError("No parallel branch awaiting this signal", "NO_BRANCH_AWAITING_SIGNAL", { instanceId, signalName });
|
|
65
|
+
}
|
|
66
|
+
const branchStepInstance = await em.findOne(StepInstance, {
|
|
67
|
+
workflowInstanceId: instanceId,
|
|
68
|
+
branchInstanceId: targetBranch.id,
|
|
69
|
+
stepId: targetBranch.currentStepId,
|
|
70
|
+
status: "ACTIVE"
|
|
71
|
+
});
|
|
72
|
+
await eventLogger.logWorkflowEvent(em, {
|
|
73
|
+
workflowInstanceId: instanceId,
|
|
74
|
+
stepInstanceId: branchStepInstance?.id,
|
|
75
|
+
branchInstanceId: targetBranch.id,
|
|
76
|
+
eventType: "SIGNAL_RECEIVED",
|
|
77
|
+
eventData: { signalName, branch: true },
|
|
78
|
+
userId,
|
|
79
|
+
tenantId,
|
|
80
|
+
organizationId
|
|
81
|
+
});
|
|
82
|
+
const { resumeBranch } = await import("./parallel-handler.js");
|
|
83
|
+
const resumed = await resumeBranch(em, {
|
|
84
|
+
instanceId,
|
|
85
|
+
branchInstanceId: targetBranch.id,
|
|
86
|
+
tenantId,
|
|
87
|
+
organizationId,
|
|
88
|
+
contextMerge: payload,
|
|
89
|
+
exitStepInstanceId: branchStepInstance?.id ?? null,
|
|
90
|
+
exitOutput: { signalName, payload }
|
|
91
|
+
});
|
|
92
|
+
if (resumed) {
|
|
93
|
+
await workflowExecutor.executeWorkflow(em, container, instanceId, { userId });
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
35
97
|
if (instance.status !== "PAUSED") {
|
|
36
98
|
throw new SignalError(
|
|
37
99
|
"Workflow is not paused",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/workflows/lib/signal-handler.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Signal Handler Service\n *\n * Receives external signals and resumes workflows waiting for them.\n */\n\nimport { EntityManager } from '@mikro-orm/core'\nimport type { EntityManager as PostgreSqlEntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { WorkflowInstance, WorkflowDefinition, StepInstance } from '../data/entities'\nimport type * as eventLoggerModule from './event-logger'\nimport type * as stepHandlerModule from './step-handler'\nimport type * as transitionHandlerModule from './transition-handler'\nimport type * as workflowExecutorModule from './workflow-executor'\n\nexport interface SendSignalOptions {\n /**\n * Workflow instance ID\n */\n instanceId: string\n\n /**\n * Signal name to match against WAIT_FOR_SIGNAL step config\n */\n signalName: string\n\n /**\n * Optional payload to merge into workflow context\n */\n payload?: Record<string, any>\n\n /**\n * User ID sending the signal\n */\n userId?: string\n\n /**\n * Tenant/org scope\n */\n tenantId: string\n organizationId: string\n}\n\nexport class SignalError extends Error {\n constructor(\n message: string,\n public code: string,\n public details?: any\n ) {\n super(message)\n this.name = 'SignalError'\n }\n}\n\n/**\n * Send signal to workflow instance and resume execution\n */\nexport async function sendSignal(\n em: EntityManager,\n container: AwilixContainer,\n options: SendSignalOptions\n): Promise<void> {\n const { instanceId, signalName, payload, userId, tenantId, organizationId } = options\n\n const eventLogger = container.resolve<typeof eventLoggerModule>('eventLogger')\n const stepHandler = container.resolve<typeof stepHandlerModule>('stepHandler')\n const transitionHandler = container.resolve<typeof transitionHandlerModule>('transitionHandler')\n const workflowExecutor = container.resolve<typeof workflowExecutorModule>('workflowExecutor')\n\n // Fetch workflow instance\n const instance = await findOneWithDecryption(\n em as PostgreSqlEntityManager,\n WorkflowInstance,\n {\n id: instanceId,\n tenantId,\n organizationId,\n },\n undefined,\n { tenantId, organizationId },\n )\n\n if (!instance) {\n throw new SignalError(\n 'Workflow instance not found',\n 'INSTANCE_NOT_FOUND',\n { instanceId }\n )\n }\n\n // Verify workflow is paused\n if (instance.status !== 'PAUSED') {\n throw new SignalError(\n 'Workflow is not paused',\n 'WORKFLOW_NOT_PAUSED',\n { instanceId, status: instance.status }\n )\n }\n\n // Load workflow definition with tenant/org scope to check current step\n const definition = await findOneWithDecryption(\n em as PostgreSqlEntityManager,\n WorkflowDefinition,\n {\n id: instance.definitionId,\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: instance.tenantId, organizationId: instance.organizationId },\n )\n if (!definition) {\n throw new SignalError(\n 'Workflow definition not found',\n 'DEFINITION_NOT_FOUND',\n { definitionId: instance.definitionId }\n )\n }\n\n // Find current step\n const currentStep = definition.definition.steps.find(\n (s: any) => s.stepId === instance.currentStepId\n )\n\n if (!currentStep || currentStep.stepType !== 'WAIT_FOR_SIGNAL') {\n throw new SignalError(\n 'Workflow is not waiting for signal',\n 'NOT_WAITING_FOR_SIGNAL',\n { instanceId, currentStepId: instance.currentStepId }\n )\n }\n\n // Check signal name matches\n const expectedSignalName = currentStep.signalConfig?.signalName || currentStep.stepId\n if (expectedSignalName !== signalName) {\n throw new SignalError(\n 'Signal name mismatch',\n 'SIGNAL_NAME_MISMATCH',\n { expected: expectedSignalName, received: signalName }\n )\n }\n\n const now = new Date()\n\n // Merge signal payload into workflow context\n if (payload) {\n instance.context = {\n ...instance.context,\n ...payload,\n [`signal_${signalName}_payload`]: payload,\n [`signal_${signalName}_receivedAt`]: now.toISOString(),\n }\n }\n\n instance.updatedAt = now\n\n // Log signal received event\n await eventLogger.logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n eventType: 'SIGNAL_RECEIVED',\n eventData: {\n signalName,\n payload,\n },\n userId,\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n\n // Find active step instance and exit it\n const stepInstance = await findOneWithDecryption(\n em as PostgreSqlEntityManager,\n StepInstance,\n {\n workflowInstanceId: instance.id,\n stepId: instance.currentStepId,\n status: 'ACTIVE',\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n },\n undefined,\n { tenantId: instance.tenantId, organizationId: instance.organizationId },\n )\n\n if (stepInstance) {\n await stepHandler.exitStep(em, stepInstance, {\n signalName,\n payload,\n })\n }\n\n // Find automatic transitions from current step\n const autoTransitions = (definition.definition.transitions || []).filter(\n (t: any) => t.fromStepId === instance.currentStepId && t.trigger === 'auto'\n )\n\n if (autoTransitions.length === 0) {\n // No automatic transitions, mark as RUNNING but stay at current step\n instance.status = 'RUNNING'\n await em.flush()\n return\n }\n\n // Find valid transitions\n const transitionContext = {\n workflowContext: instance.context,\n userId,\n }\n\n const validTransitions = await transitionHandler.findValidTransitions(\n em,\n instance,\n instance.currentStepId,\n transitionContext\n )\n\n const firstValidTransition = validTransitions.find((t) => t.isValid)\n\n if (!firstValidTransition || !firstValidTransition.transition) {\n // No valid transitions, mark as RUNNING anyway\n instance.status = 'RUNNING'\n await em.flush()\n return\n }\n\n // Execute transition to next step\n const transitionResult = await transitionHandler.executeTransition(\n em,\n container,\n instance,\n instance.currentStepId,\n firstValidTransition.transition.toStepId,\n transitionContext\n )\n\n if (!transitionResult.success) {\n throw new SignalError(\n 'Transition failed after signal',\n 'TRANSITION_FAILED',\n { error: transitionResult.error }\n )\n }\n\n // Resume workflow execution\n await workflowExecutor.executeWorkflow(em, container, instance.id, { userId })\n}\n\n/**\n * Send signal by correlation key (finds all waiting instances)\n */\nexport async function sendSignalByCorrelationKey(\n em: EntityManager,\n container: AwilixContainer,\n options: Omit<SendSignalOptions, 'instanceId'> & { correlationKey: string }\n): Promise<number> {\n const { correlationKey, signalName, payload, userId, tenantId, organizationId } = options\n\n // Find all paused instances with this correlation key\n const instances = await findWithDecryption(\n em as PostgreSqlEntityManager,\n WorkflowInstance,\n {\n correlationKey,\n status: 'PAUSED',\n tenantId,\n organizationId,\n },\n undefined,\n { tenantId, organizationId },\n )\n\n let signalsProcessed = 0\n\n for (const instance of instances) {\n try {\n await sendSignal(em, container, {\n instanceId: instance.id,\n signalName,\n payload,\n userId,\n tenantId,\n organizationId,\n })\n signalsProcessed++\n } catch (error) {\n // Log error but continue processing other instances\n console.error(`Failed to send signal to instance ${instance.id}:`, error)\n }\n }\n\n return signalsProcessed\n}\n"],
|
|
5
|
-
"mappings": "AASA,SAAS,uBAAuB,0BAA0B;AAC1D,SAAS,kBAAkB,oBAAoB,oBAAoB;
|
|
4
|
+
"sourcesContent": ["/**\n * Signal Handler Service\n *\n * Receives external signals and resumes workflows waiting for them.\n */\n\nimport { EntityManager } from '@mikro-orm/core'\nimport type { EntityManager as PostgreSqlEntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { WorkflowInstance, WorkflowBranchInstance, WorkflowDefinition, StepInstance } from '../data/entities'\nimport type * as eventLoggerModule from './event-logger'\nimport type * as stepHandlerModule from './step-handler'\nimport type * as transitionHandlerModule from './transition-handler'\nimport type * as workflowExecutorModule from './workflow-executor'\n\nexport interface SendSignalOptions {\n /**\n * Workflow instance ID\n */\n instanceId: string\n\n /**\n * Signal name to match against WAIT_FOR_SIGNAL step config\n */\n signalName: string\n\n /**\n * Optional payload to merge into workflow context\n */\n payload?: Record<string, any>\n\n /**\n * User ID sending the signal\n */\n userId?: string\n\n /**\n * Tenant/org scope\n */\n tenantId: string\n organizationId: string\n}\n\nexport class SignalError extends Error {\n constructor(\n message: string,\n public code: string,\n public details?: any\n ) {\n super(message)\n this.name = 'SignalError'\n }\n}\n\n/**\n * Send signal to workflow instance and resume execution\n */\nexport async function sendSignal(\n em: EntityManager,\n container: AwilixContainer,\n options: SendSignalOptions\n): Promise<void> {\n const { instanceId, signalName, payload, userId, tenantId, organizationId } = options\n\n const eventLogger = container.resolve<typeof eventLoggerModule>('eventLogger')\n const stepHandler = container.resolve<typeof stepHandlerModule>('stepHandler')\n const transitionHandler = container.resolve<typeof transitionHandlerModule>('transitionHandler')\n const workflowExecutor = container.resolve<typeof workflowExecutorModule>('workflowExecutor')\n\n // Fetch workflow instance\n const instance = await findOneWithDecryption(\n em as PostgreSqlEntityManager,\n WorkflowInstance,\n {\n id: instanceId,\n tenantId,\n organizationId,\n },\n undefined,\n { tenantId, organizationId },\n )\n\n if (!instance) {\n throw new SignalError(\n 'Workflow instance not found',\n 'INSTANCE_NOT_FOUND',\n { instanceId }\n )\n }\n\n // Branch-scoped signal: a FORKED instance routes the signal to the branch\n // paused at a matching WAIT_FOR_SIGNAL step.\n if (instance.status === 'FORKED') {\n const branchDefinition = await findOneWithDecryption(\n em as PostgreSqlEntityManager,\n WorkflowDefinition,\n { id: instance.definitionId, tenantId: instance.tenantId, organizationId: instance.organizationId, deletedAt: null },\n undefined,\n { tenantId: instance.tenantId, organizationId: instance.organizationId },\n )\n if (!branchDefinition) {\n throw new SignalError('Workflow definition not found', 'DEFINITION_NOT_FOUND', { definitionId: instance.definitionId })\n }\n\n const pausedBranches = await em.find(WorkflowBranchInstance, {\n workflowInstanceId: instanceId,\n status: 'PAUSED',\n tenantId,\n organizationId,\n })\n\n let targetBranch: WorkflowBranchInstance | null = null\n for (const candidate of pausedBranches) {\n const step = branchDefinition.definition.steps.find((s: any) => s.stepId === candidate.currentStepId)\n if (step?.stepType === 'WAIT_FOR_SIGNAL') {\n const candidateSignal = step.signalConfig?.signalName || step.stepId\n if (candidateSignal === signalName) {\n targetBranch = candidate\n break\n }\n }\n }\n\n if (!targetBranch) {\n throw new SignalError('No parallel branch awaiting this signal', 'NO_BRANCH_AWAITING_SIGNAL', { instanceId, signalName })\n }\n\n const branchStepInstance = await em.findOne(StepInstance, {\n workflowInstanceId: instanceId,\n branchInstanceId: targetBranch.id,\n stepId: targetBranch.currentStepId,\n status: 'ACTIVE',\n })\n\n await eventLogger.logWorkflowEvent(em, {\n workflowInstanceId: instanceId,\n stepInstanceId: branchStepInstance?.id,\n branchInstanceId: targetBranch.id,\n eventType: 'SIGNAL_RECEIVED',\n eventData: { signalName, branch: true },\n userId,\n tenantId,\n organizationId,\n })\n\n const { resumeBranch } = await import('./parallel-handler')\n const resumed = await resumeBranch(em, {\n instanceId,\n branchInstanceId: targetBranch.id,\n tenantId,\n organizationId,\n contextMerge: payload,\n exitStepInstanceId: branchStepInstance?.id ?? null,\n exitOutput: { signalName, payload },\n })\n if (resumed) {\n await workflowExecutor.executeWorkflow(em, container, instanceId, { userId })\n }\n return\n }\n\n // Verify workflow is paused\n if (instance.status !== 'PAUSED') {\n throw new SignalError(\n 'Workflow is not paused',\n 'WORKFLOW_NOT_PAUSED',\n { instanceId, status: instance.status }\n )\n }\n\n // Load workflow definition with tenant/org scope to check current step\n const definition = await findOneWithDecryption(\n em as PostgreSqlEntityManager,\n WorkflowDefinition,\n {\n id: instance.definitionId,\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: instance.tenantId, organizationId: instance.organizationId },\n )\n if (!definition) {\n throw new SignalError(\n 'Workflow definition not found',\n 'DEFINITION_NOT_FOUND',\n { definitionId: instance.definitionId }\n )\n }\n\n // Find current step\n const currentStep = definition.definition.steps.find(\n (s: any) => s.stepId === instance.currentStepId\n )\n\n if (!currentStep || currentStep.stepType !== 'WAIT_FOR_SIGNAL') {\n throw new SignalError(\n 'Workflow is not waiting for signal',\n 'NOT_WAITING_FOR_SIGNAL',\n { instanceId, currentStepId: instance.currentStepId }\n )\n }\n\n // Check signal name matches\n const expectedSignalName = currentStep.signalConfig?.signalName || currentStep.stepId\n if (expectedSignalName !== signalName) {\n throw new SignalError(\n 'Signal name mismatch',\n 'SIGNAL_NAME_MISMATCH',\n { expected: expectedSignalName, received: signalName }\n )\n }\n\n const now = new Date()\n\n // Merge signal payload into workflow context\n if (payload) {\n instance.context = {\n ...instance.context,\n ...payload,\n [`signal_${signalName}_payload`]: payload,\n [`signal_${signalName}_receivedAt`]: now.toISOString(),\n }\n }\n\n instance.updatedAt = now\n\n // Log signal received event\n await eventLogger.logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n eventType: 'SIGNAL_RECEIVED',\n eventData: {\n signalName,\n payload,\n },\n userId,\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n\n // Find active step instance and exit it\n const stepInstance = await findOneWithDecryption(\n em as PostgreSqlEntityManager,\n StepInstance,\n {\n workflowInstanceId: instance.id,\n stepId: instance.currentStepId,\n status: 'ACTIVE',\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n },\n undefined,\n { tenantId: instance.tenantId, organizationId: instance.organizationId },\n )\n\n if (stepInstance) {\n await stepHandler.exitStep(em, stepInstance, {\n signalName,\n payload,\n })\n }\n\n // Find automatic transitions from current step\n const autoTransitions = (definition.definition.transitions || []).filter(\n (t: any) => t.fromStepId === instance.currentStepId && t.trigger === 'auto'\n )\n\n if (autoTransitions.length === 0) {\n // No automatic transitions, mark as RUNNING but stay at current step\n instance.status = 'RUNNING'\n await em.flush()\n return\n }\n\n // Find valid transitions\n const transitionContext = {\n workflowContext: instance.context,\n userId,\n }\n\n const validTransitions = await transitionHandler.findValidTransitions(\n em,\n instance,\n instance.currentStepId,\n transitionContext\n )\n\n const firstValidTransition = validTransitions.find((t) => t.isValid)\n\n if (!firstValidTransition || !firstValidTransition.transition) {\n // No valid transitions, mark as RUNNING anyway\n instance.status = 'RUNNING'\n await em.flush()\n return\n }\n\n // Execute transition to next step\n const transitionResult = await transitionHandler.executeTransition(\n em,\n container,\n instance,\n instance.currentStepId,\n firstValidTransition.transition.toStepId,\n transitionContext\n )\n\n if (!transitionResult.success) {\n throw new SignalError(\n 'Transition failed after signal',\n 'TRANSITION_FAILED',\n { error: transitionResult.error }\n )\n }\n\n // Resume workflow execution\n await workflowExecutor.executeWorkflow(em, container, instance.id, { userId })\n}\n\n/**\n * Send signal by correlation key (finds all waiting instances)\n */\nexport async function sendSignalByCorrelationKey(\n em: EntityManager,\n container: AwilixContainer,\n options: Omit<SendSignalOptions, 'instanceId'> & { correlationKey: string }\n): Promise<number> {\n const { correlationKey, signalName, payload, userId, tenantId, organizationId } = options\n\n // Find all paused instances with this correlation key\n const instances = await findWithDecryption(\n em as PostgreSqlEntityManager,\n WorkflowInstance,\n {\n correlationKey,\n status: 'PAUSED',\n tenantId,\n organizationId,\n },\n undefined,\n { tenantId, organizationId },\n )\n\n let signalsProcessed = 0\n\n for (const instance of instances) {\n try {\n await sendSignal(em, container, {\n instanceId: instance.id,\n signalName,\n payload,\n userId,\n tenantId,\n organizationId,\n })\n signalsProcessed++\n } catch (error) {\n // Log error but continue processing other instances\n console.error(`Failed to send signal to instance ${instance.id}:`, error)\n }\n }\n\n return signalsProcessed\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|