@open-mercato/core 0.6.3-develop.3857.1.da89d7530c → 0.6.3-develop.3881.1.0b590ac4eb
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 +1 -1
- package/dist/modules/attachments/api/file/[id]/route.js +7 -2
- package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +7 -4
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
- package/dist/modules/audit_logs/services/accessLogService.js +127 -8
- package/dist/modules/audit_logs/services/accessLogService.js.map +2 -2
- package/dist/modules/auth/di.js +17 -3
- package/dist/modules/auth/di.js.map +2 -2
- package/dist/modules/auth/services/rbacDefaultCache.js +110 -0
- package/dist/modules/auth/services/rbacDefaultCache.js.map +7 -0
- package/dist/modules/currencies/api/currencies/route.js +3 -4
- package/dist/modules/currencies/api/currencies/route.js.map +2 -2
- package/dist/modules/currencies/api/exchange-rates/route.js +3 -4
- package/dist/modules/currencies/api/exchange-rates/route.js.map +2 -2
- package/dist/modules/customers/api/people/route.js +26 -24
- package/dist/modules/customers/api/people/route.js.map +2 -2
- package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js +26 -0
- package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js.map +7 -0
- package/dist/modules/directory/utils/organizationScope.js +85 -0
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/[id]/page.js +2 -1
- package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/create/page.js +4 -2
- package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js +20 -3
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
- package/dist/modules/workflows/components/ActivitiesEditor.js +34 -1
- package/dist/modules/workflows/components/ActivitiesEditor.js.map +2 -2
- package/dist/modules/workflows/components/NodeEditDialog.js +153 -17
- package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
- package/dist/modules/workflows/components/StepsEditor.js +31 -0
- package/dist/modules/workflows/components/StepsEditor.js.map +2 -2
- package/dist/modules/workflows/components/WorkflowGraph.js +3 -2
- package/dist/modules/workflows/components/WorkflowGraph.js.map +2 -2
- package/dist/modules/workflows/components/nodes/WaitForTimerNode.js +54 -0
- package/dist/modules/workflows/components/nodes/WaitForTimerNode.js.map +7 -0
- package/dist/modules/workflows/components/nodes/index.js +3 -1
- package/dist/modules/workflows/components/nodes/index.js.map +2 -2
- package/dist/modules/workflows/data/validators.js +117 -0
- package/dist/modules/workflows/data/validators.js.map +2 -2
- package/dist/modules/workflows/di.js +5 -1
- package/dist/modules/workflows/di.js.map +2 -2
- package/dist/modules/workflows/lib/activity-executor.js +42 -1
- 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/activity-worker-handler.js +24 -0
- package/dist/modules/workflows/lib/activity-worker-handler.js.map +2 -2
- package/dist/modules/workflows/lib/duration.js +32 -0
- package/dist/modules/workflows/lib/duration.js.map +7 -0
- package/dist/modules/workflows/lib/event-logger.js +1 -0
- package/dist/modules/workflows/lib/event-logger.js.map +2 -2
- package/dist/modules/workflows/lib/format-validation-error.js +12 -0
- package/dist/modules/workflows/lib/format-validation-error.js.map +7 -0
- package/dist/modules/workflows/lib/graph-utils.js +6 -3
- package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
- package/dist/modules/workflows/lib/node-type-icons.js +9 -5
- package/dist/modules/workflows/lib/node-type-icons.js.map +2 -2
- package/dist/modules/workflows/lib/signal-handler.js +55 -23
- package/dist/modules/workflows/lib/signal-handler.js.map +2 -2
- package/dist/modules/workflows/lib/step-handler.js +79 -29
- package/dist/modules/workflows/lib/step-handler.js.map +2 -2
- package/dist/modules/workflows/lib/timer-handler.js +159 -0
- package/dist/modules/workflows/lib/timer-handler.js.map +7 -0
- package/dist/modules/workflows/lib/workflow-executor.js +1 -1
- package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
- package/dist/modules/workflows/workers/workflow-activities.worker.js +20 -4
- package/dist/modules/workflows/workers/workflow-activities.worker.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/attachments/api/file/[id]/route.ts +7 -2
- package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +7 -4
- package/src/modules/audit_logs/services/accessLogService.ts +179 -15
- package/src/modules/auth/di.ts +26 -3
- package/src/modules/auth/services/rbacDefaultCache.ts +145 -0
- package/src/modules/currencies/api/currencies/route.ts +3 -4
- package/src/modules/currencies/api/exchange-rates/route.ts +3 -4
- package/src/modules/customers/api/people/route.ts +27 -25
- package/src/modules/directory/subscribers/invalidateOrgScopeCache.ts +39 -0
- package/src/modules/directory/utils/organizationScope.ts +121 -0
- package/src/modules/workflows/backend/definitions/[id]/page.tsx +3 -2
- package/src/modules/workflows/backend/definitions/create/page.tsx +4 -2
- package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +18 -1
- package/src/modules/workflows/components/ActivitiesEditor.tsx +40 -0
- package/src/modules/workflows/components/NodeEditDialog.tsx +218 -30
- package/src/modules/workflows/components/StepsEditor.tsx +36 -0
- package/src/modules/workflows/components/WorkflowGraph.tsx +2 -1
- package/src/modules/workflows/components/nodes/WaitForTimerNode.tsx +70 -0
- package/src/modules/workflows/components/nodes/index.ts +3 -0
- package/src/modules/workflows/data/validators.ts +121 -0
- package/src/modules/workflows/di.ts +4 -0
- package/src/modules/workflows/i18n/de.json +10 -1
- package/src/modules/workflows/i18n/en.json +10 -1
- package/src/modules/workflows/i18n/es.json +10 -1
- package/src/modules/workflows/i18n/pl.json +10 -1
- package/src/modules/workflows/lib/activity-executor.ts +86 -2
- package/src/modules/workflows/lib/activity-queue-types.ts +18 -11
- package/src/modules/workflows/lib/activity-worker-handler.ts +29 -0
- package/src/modules/workflows/lib/duration.ts +51 -0
- package/src/modules/workflows/lib/event-logger.ts +1 -0
- package/src/modules/workflows/lib/format-validation-error.ts +30 -0
- package/src/modules/workflows/lib/graph-utils.ts +3 -0
- package/src/modules/workflows/lib/node-type-icons.ts +6 -2
- package/src/modules/workflows/lib/signal-handler.ts +62 -24
- package/src/modules/workflows/lib/step-handler.ts +107 -50
- package/src/modules/workflows/lib/timer-handler.ts +213 -0
- package/src/modules/workflows/lib/workflow-executor.ts +1 -1
- package/src/modules/workflows/workers/workflow-activities.worker.ts +33 -7
|
@@ -285,7 +285,8 @@ function mapNodeTypeToStepType(nodeType) {
|
|
|
285
285
|
userTask: "USER_TASK",
|
|
286
286
|
automated: "AUTOMATED",
|
|
287
287
|
decision: "DECISION",
|
|
288
|
-
waitForSignal: "WAIT_FOR_SIGNAL"
|
|
288
|
+
waitForSignal: "WAIT_FOR_SIGNAL",
|
|
289
|
+
waitForTimer: "WAIT_FOR_TIMER"
|
|
289
290
|
};
|
|
290
291
|
return mapping[nodeType] || "AUTOMATED";
|
|
291
292
|
}
|
|
@@ -296,7 +297,8 @@ function mapStepTypeToNodeType(stepType) {
|
|
|
296
297
|
USER_TASK: "userTask",
|
|
297
298
|
AUTOMATED: "automated",
|
|
298
299
|
DECISION: "decision",
|
|
299
|
-
WAIT_FOR_SIGNAL: "waitForSignal"
|
|
300
|
+
WAIT_FOR_SIGNAL: "waitForSignal",
|
|
301
|
+
WAIT_FOR_TIMER: "waitForTimer"
|
|
300
302
|
};
|
|
301
303
|
return mapping[stepType] || "automated";
|
|
302
304
|
}
|
|
@@ -307,7 +309,8 @@ function getBadgeForNodeType(nodeType) {
|
|
|
307
309
|
userTask: "User Task",
|
|
308
310
|
automated: "Automated",
|
|
309
311
|
decision: "Decision",
|
|
310
|
-
waitForSignal: "Wait for Signal"
|
|
312
|
+
waitForSignal: "Wait for Signal",
|
|
313
|
+
waitForTimer: "Wait for Timer"
|
|
311
314
|
};
|
|
312
315
|
return badges[nodeType] || "Task";
|
|
313
316
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/workflows/lib/graph-utils.ts"],
|
|
4
|
-
"sourcesContent": ["import { Node, Edge } from '@xyflow/react'\nimport type { WorkflowDefinition } from '../data/entities'\n\n/**\n * Graph Utilities for Visual Workflow Editor\n *\n * Converts between ReactFlow graph representation and workflow definition JSON\n */\n\nexport interface GraphToDefinitionOptions {\n includePositions?: boolean\n}\n\nexport interface DefinitionToGraphOptions {\n autoLayout?: boolean\n layoutSpacing?: { vertical: number; horizontal: number }\n}\n\n/**\n * Convert ReactFlow graph (nodes + edges) to workflow definition JSON\n */\nexport function graphToDefinition(\n nodes: Node[],\n edges: Edge[],\n options: GraphToDefinitionOptions = {}\n): WorkflowDefinition['definition'] {\n // Extract steps from nodes\n const steps = nodes.map((node) => {\n const step: any = {\n stepId: node.id,\n stepName: node.data.label || node.id,\n stepType: mapNodeTypeToStepType(node.type || 'automated'),\n }\n\n // Add step-specific configuration\n if (node.data.description) {\n step.description = node.data.description\n }\n\n // Add timeout if present\n if (node.data.timeout) {\n step.timeout = node.data.timeout\n }\n\n // Add retryPolicy if present\n if (node.data.retryPolicy) {\n step.retryPolicy = node.data.retryPolicy\n }\n\n // Add generic config if present\n if (node.data.config) {\n step.config = node.data.config\n }\n\n // User task configuration\n if (node.type === 'userTask' && node.data) {\n step.userTaskConfig = {\n assignedTo: node.data.assignedTo,\n assignedToRoles: node.data.assignedToRoles || [],\n formKey: node.data.formKey,\n allowedActions: node.data.allowedActions || ['complete', 'cancel'],\n }\n\n // Add form schema if present\n if ((node.data as any).formSchema || (node.data as any).userTaskConfig?.formSchema) {\n step.userTaskConfig.formSchema = (node.data as any).formSchema || (node.data as any).userTaskConfig.formSchema\n }\n\n // Add advanced fields if present\n if ((node.data as any).assignmentRule || (node.data as any).userTaskConfig?.assignmentRule) {\n step.userTaskConfig.assignmentRule = (node.data as any).assignmentRule || (node.data as any).userTaskConfig.assignmentRule\n }\n\n if ((node.data as any).slaDuration || (node.data as any).userTaskConfig?.slaDuration) {\n step.userTaskConfig.slaDuration = (node.data as any).slaDuration || (node.data as any).userTaskConfig.slaDuration\n }\n\n if ((node.data as any).escalationRules || (node.data as any).userTaskConfig?.escalationRules) {\n step.userTaskConfig.escalationRules = (node.data as any).escalationRules || (node.data as any).userTaskConfig.escalationRules\n }\n }\n\n // Wait for signal configuration\n if (node.type === 'waitForSignal' && node.data.signalConfig) {\n step.signalConfig = node.data.signalConfig\n }\n\n // Step activities (for AUTOMATED steps)\n if (node.type === 'automated' && node.data.activities) {\n step.activities = node.data.activities\n }\n\n // Pre-conditions (for START steps)\n if (node.type === 'start' && (node.data as any).preConditions && (node.data as any).preConditions.length > 0) {\n step.preConditions = (node.data as any).preConditions\n }\n\n // Store position for visual editor\n if (options.includePositions && node.position) {\n step._editorPosition = {\n x: node.position.x,\n y: node.position.y,\n }\n }\n\n return step\n })\n\n // Extract transitions from edges\n const transitions = edges.map((edge) => {\n const edgeData = edge.data as any\n const transition: any = {\n transitionId: edge.id,\n fromStepId: edge.source,\n toStepId: edge.target,\n trigger: edgeData?.trigger || 'auto',\n }\n\n // Add transition name if present\n if (edgeData?.transitionName) {\n transition.transitionName = edgeData.transitionName\n }\n\n // Add priority if present (default 0)\n if (edgeData?.priority !== undefined) {\n transition.priority = edgeData.priority\n }\n\n // Add continueOnActivityFailure if present (default false)\n if (edgeData?.continueOnActivityFailure !== undefined) {\n transition.continueOnActivityFailure = edgeData.continueOnActivityFailure\n }\n\n // Add conditions if present\n if (edgeData?.preConditions && edgeData.preConditions.length > 0) {\n transition.preConditions = edgeData.preConditions\n }\n\n if (edgeData?.postConditions && edgeData.postConditions.length > 0) {\n transition.postConditions = edgeData.postConditions\n }\n\n // Add activities if present in edge data\n if (edgeData?.activities && edgeData.activities.length > 0) {\n transition.activities = edgeData.activities.map((activity: any) => ({\n activityId: activity.activityId,\n activityName: activity.activityName,\n activityType: activity.activityType,\n config: activity.config || {},\n // Include all optional fields\n ...(activity.async !== undefined && { async: activity.async }),\n ...(activity.timeout && { timeout: activity.timeout }),\n ...(activity.retryPolicy && { retryPolicy: activity.retryPolicy }),\n ...(activity.compensate !== undefined && { compensate: activity.compensate }),\n }))\n } else {\n // Check if source node is automated and has activity data\n // If so, place the activity in this transition\n const sourceNode = nodes.find(n => n.id === edge.source)\n if (sourceNode && sourceNode.type === 'automated' && sourceNode.data) {\n if (sourceNode.data.activityType || sourceNode.data.activityId) {\n const activity: any = {\n activityId: sourceNode.data.activityId || `activity_${sourceNode.id}`,\n activityName: sourceNode.data.activityName || sourceNode.data.label || 'Automated Activity',\n activityType: sourceNode.data.activityType || 'CALL_API',\n config: sourceNode.data.activityConfig || {},\n }\n // Include optional activity fields from node data\n if ((sourceNode.data as any).activityAsync !== undefined) {\n activity.async = (sourceNode.data as any).activityAsync\n }\n if ((sourceNode.data as any).activityTimeout) {\n activity.timeout = (sourceNode.data as any).activityTimeout\n }\n if ((sourceNode.data as any).activityRetryPolicy) {\n activity.retryPolicy = (sourceNode.data as any).activityRetryPolicy\n }\n if ((sourceNode.data as any).activityCompensate !== undefined) {\n activity.compensate = (sourceNode.data as any).activityCompensate\n }\n transition.activities = [activity]\n }\n }\n }\n\n // Add label if present (legacy field, transitionName is preferred)\n if (edgeData?.label && !transition.transitionName) {\n transition.transitionName = edgeData.label\n }\n\n return transition\n })\n\n return {\n steps,\n transitions,\n activities: [], // Global activities can be added later\n }\n}\n\n/**\n * Convert workflow definition JSON to ReactFlow graph (nodes + edges)\n */\nexport function definitionToGraph(\n definition: WorkflowDefinition['definition'],\n options: DefinitionToGraphOptions = {}\n): { nodes: Node[]; edges: Edge[] } {\n const { autoLayout = true, layoutSpacing = { vertical: 200, horizontal: 300 } } = options\n\n // Build step map for quick lookup\n const stepMap = new Map(definition.steps.map(step => [step.stepId, step]))\n\n // Calculate smart layout positions if autoLayout is enabled\n const positions = autoLayout\n ? calculateSmartLayout(definition.steps, definition.transitions, layoutSpacing)\n : null\n\n // Convert steps to nodes\n const nodes: Node[] = definition.steps.map((step, index) => {\n // Determine position\n let position = positions?.get(step.stepId) || { x: 250, y: 50 + index * layoutSpacing.vertical }\n\n // Use stored position if available and not auto-layouting\n if (!autoLayout && (step as any)._editorPosition) {\n position = (step as any)._editorPosition\n }\n\n // Map step type to node type\n const nodeType = mapStepTypeToNodeType(step.stepType)\n\n // Build node data\n const nodeData: any = {\n label: step.stepName,\n description: (step as any).description,\n stepNumber: index > 0 ? index : undefined,\n }\n\n // Add timeout if present\n if ((step as any).timeout) {\n nodeData.timeout = (step as any).timeout\n }\n\n // Add retryPolicy if present\n if ((step as any).retryPolicy) {\n nodeData.retryPolicy = (step as any).retryPolicy\n }\n\n // Add generic config if present\n if ((step as any).config) {\n nodeData.config = (step as any).config\n }\n\n // Add user task data\n if (step.stepType === 'USER_TASK' && step.userTaskConfig) {\n nodeData.assignedTo = step.userTaskConfig.assignedTo\n nodeData.assignedToRoles = step.userTaskConfig.assignedToRoles || []\n nodeData.formKey = step.userTaskConfig.formKey\n nodeData.allowedActions = step.userTaskConfig.allowedActions\n\n // Store full userTaskConfig for advanced fields\n nodeData.userTaskConfig = step.userTaskConfig\n\n // Add form schema if present\n if (step.userTaskConfig.formSchema) {\n nodeData.formSchema = step.userTaskConfig.formSchema\n }\n\n // Add advanced fields if present\n if (step.userTaskConfig.assignmentRule) {\n nodeData.assignmentRule = step.userTaskConfig.assignmentRule\n }\n\n if (step.userTaskConfig.slaDuration) {\n nodeData.slaDuration = step.userTaskConfig.slaDuration\n }\n\n if (step.userTaskConfig.escalationRules) {\n nodeData.escalationRules = step.userTaskConfig.escalationRules\n }\n }\n\n // Add wait for signal data\n if (step.stepType === 'WAIT_FOR_SIGNAL' && (step as any).signalConfig) {\n nodeData.signalConfig = (step as any).signalConfig\n }\n\n // Add step activities data (for AUTOMATED steps)\n if (step.stepType === 'AUTOMATED' && (step as any).activities) {\n nodeData.activities = (step as any).activities\n }\n\n // Add pre-conditions data (for START steps)\n if (step.stepType === 'START' && (step as any).preConditions) {\n nodeData.preConditions = (step as any).preConditions\n }\n\n // Set badge based on type\n nodeData.badge = getBadgeForNodeType(nodeType)\n\n // Default status is pending\n nodeData.status = 'pending'\n\n return {\n id: step.stepId,\n type: nodeType,\n position,\n data: nodeData,\n }\n })\n\n // Convert transitions to edges\n const edges: Edge[] = definition.transitions.map((transition) => {\n return {\n id: transition.transitionId,\n source: transition.fromStepId,\n target: transition.toStepId,\n type: 'workflowTransition',\n data: {\n trigger: transition.trigger,\n transitionName: (transition as any).transitionName,\n priority: (transition as any).priority !== undefined ? (transition as any).priority : 0,\n continueOnActivityFailure: (transition as any).continueOnActivityFailure !== undefined\n ? (transition as any).continueOnActivityFailure\n : false,\n preConditions: transition.preConditions || [],\n postConditions: transition.postConditions || [],\n activities: transition.activities || [],\n label: (transition as any).transitionName || (transition as any).label, // Backward compat\n state: (transition as any).state || 'pending', // Default edge state\n },\n }\n })\n\n return { nodes, edges }\n}\n\n/**\n * Calculate smart layout positions for workflow nodes\n * Uses a layered/hierarchical layout algorithm that:\n * 1. Assigns levels (ranks) to nodes based on graph topology\n * 2. Spreads sibling nodes horizontally at the same level\n * 3. Centers merge points below their incoming nodes\n */\nfunction calculateSmartLayout(\n steps: any[],\n transitions: any[],\n spacing: { vertical: number; horizontal: number }\n): Map<string, { x: number; y: number }> {\n const positions = new Map<string, { x: number; y: number }>()\n\n if (steps.length === 0) return positions\n\n // Build adjacency lists\n const outgoing = new Map<string, string[]>() // node -> children\n const incoming = new Map<string, string[]>() // node -> parents\n\n for (const step of steps) {\n outgoing.set(step.stepId, [])\n incoming.set(step.stepId, [])\n }\n\n for (const t of transitions) {\n const children = outgoing.get(t.fromStepId) || []\n children.push(t.toStepId)\n outgoing.set(t.fromStepId, children)\n\n const parents = incoming.get(t.toStepId) || []\n parents.push(t.fromStepId)\n incoming.set(t.toStepId, parents)\n }\n\n // Find start node(s) - nodes with no incoming edges\n const startNodes = steps.filter(s => (incoming.get(s.stepId) || []).length === 0)\n if (startNodes.length === 0) {\n // Fallback: use first step as start\n startNodes.push(steps[0])\n }\n\n // Assign levels using BFS (longest path from start)\n const levels = new Map<string, number>()\n const queue: Array<{ id: string; level: number }> = []\n\n for (const start of startNodes) {\n queue.push({ id: start.stepId, level: 0 })\n }\n\n while (queue.length > 0) {\n const { id, level } = queue.shift()!\n const currentLevel = levels.get(id)\n\n // Take the maximum level (longest path)\n if (currentLevel === undefined || level > currentLevel) {\n levels.set(id, level)\n }\n\n const children = outgoing.get(id) || []\n for (const child of children) {\n queue.push({ id: child, level: level + 1 })\n }\n }\n\n // Group nodes by level\n const nodesByLevel = new Map<number, string[]>()\n for (const [nodeId, level] of levels) {\n const nodesAtLevel = nodesByLevel.get(level) || []\n nodesAtLevel.push(nodeId)\n nodesByLevel.set(level, nodesAtLevel)\n }\n\n // Calculate positions\n const centerX = 400 // Center line for the graph\n const startY = 50\n\n for (const [level, nodeIds] of nodesByLevel) {\n const count = nodeIds.length\n const y = startY + level * spacing.vertical\n\n if (count === 1) {\n // Single node at this level - center it\n positions.set(nodeIds[0], { x: centerX, y })\n } else {\n // Multiple nodes at this level - spread them horizontally\n const totalWidth = (count - 1) * spacing.horizontal\n const startX = centerX - totalWidth / 2\n\n // Sort nodes by their parent's position for consistent ordering\n nodeIds.sort((a, b) => {\n const parentsA = incoming.get(a) || []\n const parentsB = incoming.get(b) || []\n const parentPosA = parentsA.length > 0 ? (positions.get(parentsA[0])?.x || 0) : 0\n const parentPosB = parentsB.length > 0 ? (positions.get(parentsB[0])?.x || 0) : 0\n return parentPosA - parentPosB\n })\n\n nodeIds.forEach((nodeId, idx) => {\n positions.set(nodeId, { x: startX + idx * spacing.horizontal, y })\n })\n }\n }\n\n return positions\n}\n\n/**\n * Map node type to step type (for graph \u2192 definition)\n */\nfunction mapNodeTypeToStepType(nodeType: string): string {\n const mapping: Record<string, string> = {\n start: 'START',\n end: 'END',\n userTask: 'USER_TASK',\n automated: 'AUTOMATED',\n decision: 'DECISION',\n waitForSignal: 'WAIT_FOR_SIGNAL',\n }\n return mapping[nodeType] || 'AUTOMATED'\n}\n\n/**\n * Map step type to node type (for definition \u2192 graph)\n */\nfunction mapStepTypeToNodeType(stepType: string): string {\n const mapping: Record<string, string> = {\n START: 'start',\n END: 'end',\n USER_TASK: 'userTask',\n AUTOMATED: 'automated',\n DECISION: 'decision',\n WAIT_FOR_SIGNAL: 'waitForSignal',\n }\n return mapping[stepType] || 'automated'\n}\n\n/**\n * Get badge text for node type\n */\nfunction getBadgeForNodeType(nodeType: string): string {\n const badges: Record<string, string> = {\n start: 'Start',\n end: 'End',\n userTask: 'User Task',\n automated: 'Automated',\n decision: 'Decision',\n waitForSignal: 'Wait for Signal',\n }\n return badges[nodeType] || 'Task'\n}\n\n/**\n * Validate workflow graph\n */\nexport interface ValidationError {\n type: 'error' | 'warning'\n message: string\n nodeId?: string\n edgeId?: string\n}\n\nexport function validateWorkflowGraph(nodes: Node[], edges: Edge[]): ValidationError[] {\n const errors: ValidationError[] = []\n\n // Check for at least one start node\n const startNodes = nodes.filter((n) => n.type === 'start')\n if (startNodes.length === 0) {\n errors.push({\n type: 'error',\n message: 'Workflow must have at least one START node',\n })\n }\n if (startNodes.length > 1) {\n errors.push({\n type: 'warning',\n message: 'Workflow has multiple START nodes',\n })\n }\n\n // Check for at least one end node\n const endNodes = nodes.filter((n) => n.type === 'end')\n if (endNodes.length === 0) {\n errors.push({\n type: 'error',\n message: 'Workflow must have at least one END node',\n })\n }\n\n // Check for orphan nodes (no incoming or outgoing edges)\n for (const node of nodes) {\n if (node.type === 'start') continue // Start nodes don't need incoming edges\n if (node.type === 'end') continue // End nodes don't need outgoing edges\n\n const hasIncoming = edges.some((e) => e.target === node.id)\n const hasOutgoing = edges.some((e) => e.source === node.id)\n\n if (!hasIncoming && !hasOutgoing) {\n errors.push({\n type: 'error',\n message: `Node \"${node.data.label}\" is disconnected`,\n nodeId: node.id,\n })\n } else if (!hasIncoming) {\n errors.push({\n type: 'warning',\n message: `Node \"${node.data.label}\" has no incoming connections`,\n nodeId: node.id,\n })\n } else if (!hasOutgoing) {\n errors.push({\n type: 'warning',\n message: `Node \"${node.data.label}\" has no outgoing connections`,\n nodeId: node.id,\n })\n }\n }\n\n // Check for cycles (simple detection)\n const hasCycle = detectCycle(nodes, edges)\n if (hasCycle) {\n errors.push({\n type: 'warning',\n message: 'Workflow contains cycles (loops)',\n })\n }\n\n // Check for duplicate step IDs\n const stepIds = new Set<string>()\n for (const node of nodes) {\n if (stepIds.has(node.id)) {\n errors.push({\n type: 'error',\n message: `Duplicate step ID: ${node.id}`,\n nodeId: node.id,\n })\n }\n stepIds.add(node.id)\n }\n\n return errors\n}\n\n/**\n * Simple cycle detection using DFS\n */\nfunction detectCycle(nodes: Node[], edges: Edge[]): boolean {\n const adjList = new Map<string, string[]>()\n\n // Build adjacency list\n for (const node of nodes) {\n adjList.set(node.id, [])\n }\n for (const edge of edges) {\n const neighbors = adjList.get(edge.source) || []\n neighbors.push(edge.target)\n adjList.set(edge.source, neighbors)\n }\n\n const visited = new Set<string>()\n const recStack = new Set<string>()\n\n function dfs(nodeId: string): boolean {\n visited.add(nodeId)\n recStack.add(nodeId)\n\n const neighbors = adjList.get(nodeId) || []\n for (const neighbor of neighbors) {\n if (!visited.has(neighbor)) {\n if (dfs(neighbor)) return true\n } else if (recStack.has(neighbor)) {\n return true // Cycle detected\n }\n }\n\n recStack.delete(nodeId)\n return false\n }\n\n for (const node of nodes) {\n if (!visited.has(node.id)) {\n if (dfs(node.id)) return true\n }\n }\n\n return false\n}\n\n/**\n * Sanitize ID to match schema regex: /^[a-z0-9_-]+$/\n * Converts to lowercase, replaces invalid characters with underscores\n */\nexport function sanitizeId(input: string): string {\n return input\n .toLowerCase()\n .replace(/[^a-z0-9_-]/g, '_')\n .replace(/_{2,}/g, '_') // Replace multiple underscores with single\n .replace(/(?:^_|_$)/g, '') // Remove leading/trailing underscores\n}\n\n/**\n * Validate ID matches schema regex: /^[a-z0-9_-]+$/\n */\nexport function validateId(id: string): boolean {\n return /^[a-z0-9_-]+$/.test(id)\n}\n\n/**\n * Generate unique step ID\n */\nexport function generateStepId(prefix: string = 'step'): string {\n const id = `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`\n return sanitizeId(id)\n}\n\n/**\n * Generate unique transition ID\n */\nexport function generateTransitionId(fromStepId: string, toStepId: string): string {\n const id = `e_${fromStepId}_${toStepId}`\n return sanitizeId(id)\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { Node, Edge } from '@xyflow/react'\nimport type { WorkflowDefinition } from '../data/entities'\n\n/**\n * Graph Utilities for Visual Workflow Editor\n *\n * Converts between ReactFlow graph representation and workflow definition JSON\n */\n\nexport interface GraphToDefinitionOptions {\n includePositions?: boolean\n}\n\nexport interface DefinitionToGraphOptions {\n autoLayout?: boolean\n layoutSpacing?: { vertical: number; horizontal: number }\n}\n\n/**\n * Convert ReactFlow graph (nodes + edges) to workflow definition JSON\n */\nexport function graphToDefinition(\n nodes: Node[],\n edges: Edge[],\n options: GraphToDefinitionOptions = {}\n): WorkflowDefinition['definition'] {\n // Extract steps from nodes\n const steps = nodes.map((node) => {\n const step: any = {\n stepId: node.id,\n stepName: node.data.label || node.id,\n stepType: mapNodeTypeToStepType(node.type || 'automated'),\n }\n\n // Add step-specific configuration\n if (node.data.description) {\n step.description = node.data.description\n }\n\n // Add timeout if present\n if (node.data.timeout) {\n step.timeout = node.data.timeout\n }\n\n // Add retryPolicy if present\n if (node.data.retryPolicy) {\n step.retryPolicy = node.data.retryPolicy\n }\n\n // Add generic config if present\n if (node.data.config) {\n step.config = node.data.config\n }\n\n // User task configuration\n if (node.type === 'userTask' && node.data) {\n step.userTaskConfig = {\n assignedTo: node.data.assignedTo,\n assignedToRoles: node.data.assignedToRoles || [],\n formKey: node.data.formKey,\n allowedActions: node.data.allowedActions || ['complete', 'cancel'],\n }\n\n // Add form schema if present\n if ((node.data as any).formSchema || (node.data as any).userTaskConfig?.formSchema) {\n step.userTaskConfig.formSchema = (node.data as any).formSchema || (node.data as any).userTaskConfig.formSchema\n }\n\n // Add advanced fields if present\n if ((node.data as any).assignmentRule || (node.data as any).userTaskConfig?.assignmentRule) {\n step.userTaskConfig.assignmentRule = (node.data as any).assignmentRule || (node.data as any).userTaskConfig.assignmentRule\n }\n\n if ((node.data as any).slaDuration || (node.data as any).userTaskConfig?.slaDuration) {\n step.userTaskConfig.slaDuration = (node.data as any).slaDuration || (node.data as any).userTaskConfig.slaDuration\n }\n\n if ((node.data as any).escalationRules || (node.data as any).userTaskConfig?.escalationRules) {\n step.userTaskConfig.escalationRules = (node.data as any).escalationRules || (node.data as any).userTaskConfig.escalationRules\n }\n }\n\n // Wait for signal configuration\n if (node.type === 'waitForSignal' && node.data.signalConfig) {\n step.signalConfig = node.data.signalConfig\n }\n\n // Step activities (for AUTOMATED steps)\n if (node.type === 'automated' && node.data.activities) {\n step.activities = node.data.activities\n }\n\n // Pre-conditions (for START steps)\n if (node.type === 'start' && (node.data as any).preConditions && (node.data as any).preConditions.length > 0) {\n step.preConditions = (node.data as any).preConditions\n }\n\n // Store position for visual editor\n if (options.includePositions && node.position) {\n step._editorPosition = {\n x: node.position.x,\n y: node.position.y,\n }\n }\n\n return step\n })\n\n // Extract transitions from edges\n const transitions = edges.map((edge) => {\n const edgeData = edge.data as any\n const transition: any = {\n transitionId: edge.id,\n fromStepId: edge.source,\n toStepId: edge.target,\n trigger: edgeData?.trigger || 'auto',\n }\n\n // Add transition name if present\n if (edgeData?.transitionName) {\n transition.transitionName = edgeData.transitionName\n }\n\n // Add priority if present (default 0)\n if (edgeData?.priority !== undefined) {\n transition.priority = edgeData.priority\n }\n\n // Add continueOnActivityFailure if present (default false)\n if (edgeData?.continueOnActivityFailure !== undefined) {\n transition.continueOnActivityFailure = edgeData.continueOnActivityFailure\n }\n\n // Add conditions if present\n if (edgeData?.preConditions && edgeData.preConditions.length > 0) {\n transition.preConditions = edgeData.preConditions\n }\n\n if (edgeData?.postConditions && edgeData.postConditions.length > 0) {\n transition.postConditions = edgeData.postConditions\n }\n\n // Add activities if present in edge data\n if (edgeData?.activities && edgeData.activities.length > 0) {\n transition.activities = edgeData.activities.map((activity: any) => ({\n activityId: activity.activityId,\n activityName: activity.activityName,\n activityType: activity.activityType,\n config: activity.config || {},\n // Include all optional fields\n ...(activity.async !== undefined && { async: activity.async }),\n ...(activity.timeout && { timeout: activity.timeout }),\n ...(activity.retryPolicy && { retryPolicy: activity.retryPolicy }),\n ...(activity.compensate !== undefined && { compensate: activity.compensate }),\n }))\n } else {\n // Check if source node is automated and has activity data\n // If so, place the activity in this transition\n const sourceNode = nodes.find(n => n.id === edge.source)\n if (sourceNode && sourceNode.type === 'automated' && sourceNode.data) {\n if (sourceNode.data.activityType || sourceNode.data.activityId) {\n const activity: any = {\n activityId: sourceNode.data.activityId || `activity_${sourceNode.id}`,\n activityName: sourceNode.data.activityName || sourceNode.data.label || 'Automated Activity',\n activityType: sourceNode.data.activityType || 'CALL_API',\n config: sourceNode.data.activityConfig || {},\n }\n // Include optional activity fields from node data\n if ((sourceNode.data as any).activityAsync !== undefined) {\n activity.async = (sourceNode.data as any).activityAsync\n }\n if ((sourceNode.data as any).activityTimeout) {\n activity.timeout = (sourceNode.data as any).activityTimeout\n }\n if ((sourceNode.data as any).activityRetryPolicy) {\n activity.retryPolicy = (sourceNode.data as any).activityRetryPolicy\n }\n if ((sourceNode.data as any).activityCompensate !== undefined) {\n activity.compensate = (sourceNode.data as any).activityCompensate\n }\n transition.activities = [activity]\n }\n }\n }\n\n // Add label if present (legacy field, transitionName is preferred)\n if (edgeData?.label && !transition.transitionName) {\n transition.transitionName = edgeData.label\n }\n\n return transition\n })\n\n return {\n steps,\n transitions,\n activities: [], // Global activities can be added later\n }\n}\n\n/**\n * Convert workflow definition JSON to ReactFlow graph (nodes + edges)\n */\nexport function definitionToGraph(\n definition: WorkflowDefinition['definition'],\n options: DefinitionToGraphOptions = {}\n): { nodes: Node[]; edges: Edge[] } {\n const { autoLayout = true, layoutSpacing = { vertical: 200, horizontal: 300 } } = options\n\n // Build step map for quick lookup\n const stepMap = new Map(definition.steps.map(step => [step.stepId, step]))\n\n // Calculate smart layout positions if autoLayout is enabled\n const positions = autoLayout\n ? calculateSmartLayout(definition.steps, definition.transitions, layoutSpacing)\n : null\n\n // Convert steps to nodes\n const nodes: Node[] = definition.steps.map((step, index) => {\n // Determine position\n let position = positions?.get(step.stepId) || { x: 250, y: 50 + index * layoutSpacing.vertical }\n\n // Use stored position if available and not auto-layouting\n if (!autoLayout && (step as any)._editorPosition) {\n position = (step as any)._editorPosition\n }\n\n // Map step type to node type\n const nodeType = mapStepTypeToNodeType(step.stepType)\n\n // Build node data\n const nodeData: any = {\n label: step.stepName,\n description: (step as any).description,\n stepNumber: index > 0 ? index : undefined,\n }\n\n // Add timeout if present\n if ((step as any).timeout) {\n nodeData.timeout = (step as any).timeout\n }\n\n // Add retryPolicy if present\n if ((step as any).retryPolicy) {\n nodeData.retryPolicy = (step as any).retryPolicy\n }\n\n // Add generic config if present\n if ((step as any).config) {\n nodeData.config = (step as any).config\n }\n\n // Add user task data\n if (step.stepType === 'USER_TASK' && step.userTaskConfig) {\n nodeData.assignedTo = step.userTaskConfig.assignedTo\n nodeData.assignedToRoles = step.userTaskConfig.assignedToRoles || []\n nodeData.formKey = step.userTaskConfig.formKey\n nodeData.allowedActions = step.userTaskConfig.allowedActions\n\n // Store full userTaskConfig for advanced fields\n nodeData.userTaskConfig = step.userTaskConfig\n\n // Add form schema if present\n if (step.userTaskConfig.formSchema) {\n nodeData.formSchema = step.userTaskConfig.formSchema\n }\n\n // Add advanced fields if present\n if (step.userTaskConfig.assignmentRule) {\n nodeData.assignmentRule = step.userTaskConfig.assignmentRule\n }\n\n if (step.userTaskConfig.slaDuration) {\n nodeData.slaDuration = step.userTaskConfig.slaDuration\n }\n\n if (step.userTaskConfig.escalationRules) {\n nodeData.escalationRules = step.userTaskConfig.escalationRules\n }\n }\n\n // Add wait for signal data\n if (step.stepType === 'WAIT_FOR_SIGNAL' && (step as any).signalConfig) {\n nodeData.signalConfig = (step as any).signalConfig\n }\n\n // Add step activities data (for AUTOMATED steps)\n if (step.stepType === 'AUTOMATED' && (step as any).activities) {\n nodeData.activities = (step as any).activities\n }\n\n // Add pre-conditions data (for START steps)\n if (step.stepType === 'START' && (step as any).preConditions) {\n nodeData.preConditions = (step as any).preConditions\n }\n\n // Set badge based on type\n nodeData.badge = getBadgeForNodeType(nodeType)\n\n // Default status is pending\n nodeData.status = 'pending'\n\n return {\n id: step.stepId,\n type: nodeType,\n position,\n data: nodeData,\n }\n })\n\n // Convert transitions to edges\n const edges: Edge[] = definition.transitions.map((transition) => {\n return {\n id: transition.transitionId,\n source: transition.fromStepId,\n target: transition.toStepId,\n type: 'workflowTransition',\n data: {\n trigger: transition.trigger,\n transitionName: (transition as any).transitionName,\n priority: (transition as any).priority !== undefined ? (transition as any).priority : 0,\n continueOnActivityFailure: (transition as any).continueOnActivityFailure !== undefined\n ? (transition as any).continueOnActivityFailure\n : false,\n preConditions: transition.preConditions || [],\n postConditions: transition.postConditions || [],\n activities: transition.activities || [],\n label: (transition as any).transitionName || (transition as any).label, // Backward compat\n state: (transition as any).state || 'pending', // Default edge state\n },\n }\n })\n\n return { nodes, edges }\n}\n\n/**\n * Calculate smart layout positions for workflow nodes\n * Uses a layered/hierarchical layout algorithm that:\n * 1. Assigns levels (ranks) to nodes based on graph topology\n * 2. Spreads sibling nodes horizontally at the same level\n * 3. Centers merge points below their incoming nodes\n */\nfunction calculateSmartLayout(\n steps: any[],\n transitions: any[],\n spacing: { vertical: number; horizontal: number }\n): Map<string, { x: number; y: number }> {\n const positions = new Map<string, { x: number; y: number }>()\n\n if (steps.length === 0) return positions\n\n // Build adjacency lists\n const outgoing = new Map<string, string[]>() // node -> children\n const incoming = new Map<string, string[]>() // node -> parents\n\n for (const step of steps) {\n outgoing.set(step.stepId, [])\n incoming.set(step.stepId, [])\n }\n\n for (const t of transitions) {\n const children = outgoing.get(t.fromStepId) || []\n children.push(t.toStepId)\n outgoing.set(t.fromStepId, children)\n\n const parents = incoming.get(t.toStepId) || []\n parents.push(t.fromStepId)\n incoming.set(t.toStepId, parents)\n }\n\n // Find start node(s) - nodes with no incoming edges\n const startNodes = steps.filter(s => (incoming.get(s.stepId) || []).length === 0)\n if (startNodes.length === 0) {\n // Fallback: use first step as start\n startNodes.push(steps[0])\n }\n\n // Assign levels using BFS (longest path from start)\n const levels = new Map<string, number>()\n const queue: Array<{ id: string; level: number }> = []\n\n for (const start of startNodes) {\n queue.push({ id: start.stepId, level: 0 })\n }\n\n while (queue.length > 0) {\n const { id, level } = queue.shift()!\n const currentLevel = levels.get(id)\n\n // Take the maximum level (longest path)\n if (currentLevel === undefined || level > currentLevel) {\n levels.set(id, level)\n }\n\n const children = outgoing.get(id) || []\n for (const child of children) {\n queue.push({ id: child, level: level + 1 })\n }\n }\n\n // Group nodes by level\n const nodesByLevel = new Map<number, string[]>()\n for (const [nodeId, level] of levels) {\n const nodesAtLevel = nodesByLevel.get(level) || []\n nodesAtLevel.push(nodeId)\n nodesByLevel.set(level, nodesAtLevel)\n }\n\n // Calculate positions\n const centerX = 400 // Center line for the graph\n const startY = 50\n\n for (const [level, nodeIds] of nodesByLevel) {\n const count = nodeIds.length\n const y = startY + level * spacing.vertical\n\n if (count === 1) {\n // Single node at this level - center it\n positions.set(nodeIds[0], { x: centerX, y })\n } else {\n // Multiple nodes at this level - spread them horizontally\n const totalWidth = (count - 1) * spacing.horizontal\n const startX = centerX - totalWidth / 2\n\n // Sort nodes by their parent's position for consistent ordering\n nodeIds.sort((a, b) => {\n const parentsA = incoming.get(a) || []\n const parentsB = incoming.get(b) || []\n const parentPosA = parentsA.length > 0 ? (positions.get(parentsA[0])?.x || 0) : 0\n const parentPosB = parentsB.length > 0 ? (positions.get(parentsB[0])?.x || 0) : 0\n return parentPosA - parentPosB\n })\n\n nodeIds.forEach((nodeId, idx) => {\n positions.set(nodeId, { x: startX + idx * spacing.horizontal, y })\n })\n }\n }\n\n return positions\n}\n\n/**\n * Map node type to step type (for graph \u2192 definition)\n */\nfunction mapNodeTypeToStepType(nodeType: string): string {\n const mapping: Record<string, string> = {\n start: 'START',\n end: 'END',\n userTask: 'USER_TASK',\n automated: 'AUTOMATED',\n decision: 'DECISION',\n waitForSignal: 'WAIT_FOR_SIGNAL',\n waitForTimer: 'WAIT_FOR_TIMER',\n }\n return mapping[nodeType] || 'AUTOMATED'\n}\n\n/**\n * Map step type to node type (for definition \u2192 graph)\n */\nfunction mapStepTypeToNodeType(stepType: string): string {\n const mapping: Record<string, string> = {\n START: 'start',\n END: 'end',\n USER_TASK: 'userTask',\n AUTOMATED: 'automated',\n DECISION: 'decision',\n WAIT_FOR_SIGNAL: 'waitForSignal',\n WAIT_FOR_TIMER: 'waitForTimer',\n }\n return mapping[stepType] || 'automated'\n}\n\n/**\n * Get badge text for node type\n */\nfunction getBadgeForNodeType(nodeType: string): string {\n const badges: Record<string, string> = {\n start: 'Start',\n end: 'End',\n userTask: 'User Task',\n automated: 'Automated',\n decision: 'Decision',\n waitForSignal: 'Wait for Signal',\n waitForTimer: 'Wait for Timer',\n }\n return badges[nodeType] || 'Task'\n}\n\n/**\n * Validate workflow graph\n */\nexport interface ValidationError {\n type: 'error' | 'warning'\n message: string\n nodeId?: string\n edgeId?: string\n}\n\nexport function validateWorkflowGraph(nodes: Node[], edges: Edge[]): ValidationError[] {\n const errors: ValidationError[] = []\n\n // Check for at least one start node\n const startNodes = nodes.filter((n) => n.type === 'start')\n if (startNodes.length === 0) {\n errors.push({\n type: 'error',\n message: 'Workflow must have at least one START node',\n })\n }\n if (startNodes.length > 1) {\n errors.push({\n type: 'warning',\n message: 'Workflow has multiple START nodes',\n })\n }\n\n // Check for at least one end node\n const endNodes = nodes.filter((n) => n.type === 'end')\n if (endNodes.length === 0) {\n errors.push({\n type: 'error',\n message: 'Workflow must have at least one END node',\n })\n }\n\n // Check for orphan nodes (no incoming or outgoing edges)\n for (const node of nodes) {\n if (node.type === 'start') continue // Start nodes don't need incoming edges\n if (node.type === 'end') continue // End nodes don't need outgoing edges\n\n const hasIncoming = edges.some((e) => e.target === node.id)\n const hasOutgoing = edges.some((e) => e.source === node.id)\n\n if (!hasIncoming && !hasOutgoing) {\n errors.push({\n type: 'error',\n message: `Node \"${node.data.label}\" is disconnected`,\n nodeId: node.id,\n })\n } else if (!hasIncoming) {\n errors.push({\n type: 'warning',\n message: `Node \"${node.data.label}\" has no incoming connections`,\n nodeId: node.id,\n })\n } else if (!hasOutgoing) {\n errors.push({\n type: 'warning',\n message: `Node \"${node.data.label}\" has no outgoing connections`,\n nodeId: node.id,\n })\n }\n }\n\n // Check for cycles (simple detection)\n const hasCycle = detectCycle(nodes, edges)\n if (hasCycle) {\n errors.push({\n type: 'warning',\n message: 'Workflow contains cycles (loops)',\n })\n }\n\n // Check for duplicate step IDs\n const stepIds = new Set<string>()\n for (const node of nodes) {\n if (stepIds.has(node.id)) {\n errors.push({\n type: 'error',\n message: `Duplicate step ID: ${node.id}`,\n nodeId: node.id,\n })\n }\n stepIds.add(node.id)\n }\n\n return errors\n}\n\n/**\n * Simple cycle detection using DFS\n */\nfunction detectCycle(nodes: Node[], edges: Edge[]): boolean {\n const adjList = new Map<string, string[]>()\n\n // Build adjacency list\n for (const node of nodes) {\n adjList.set(node.id, [])\n }\n for (const edge of edges) {\n const neighbors = adjList.get(edge.source) || []\n neighbors.push(edge.target)\n adjList.set(edge.source, neighbors)\n }\n\n const visited = new Set<string>()\n const recStack = new Set<string>()\n\n function dfs(nodeId: string): boolean {\n visited.add(nodeId)\n recStack.add(nodeId)\n\n const neighbors = adjList.get(nodeId) || []\n for (const neighbor of neighbors) {\n if (!visited.has(neighbor)) {\n if (dfs(neighbor)) return true\n } else if (recStack.has(neighbor)) {\n return true // Cycle detected\n }\n }\n\n recStack.delete(nodeId)\n return false\n }\n\n for (const node of nodes) {\n if (!visited.has(node.id)) {\n if (dfs(node.id)) return true\n }\n }\n\n return false\n}\n\n/**\n * Sanitize ID to match schema regex: /^[a-z0-9_-]+$/\n * Converts to lowercase, replaces invalid characters with underscores\n */\nexport function sanitizeId(input: string): string {\n return input\n .toLowerCase()\n .replace(/[^a-z0-9_-]/g, '_')\n .replace(/_{2,}/g, '_') // Replace multiple underscores with single\n .replace(/(?:^_|_$)/g, '') // Remove leading/trailing underscores\n}\n\n/**\n * Validate ID matches schema regex: /^[a-z0-9_-]+$/\n */\nexport function validateId(id: string): boolean {\n return /^[a-z0-9_-]+$/.test(id)\n}\n\n/**\n * Generate unique step ID\n */\nexport function generateStepId(prefix: string = 'step'): string {\n const id = `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`\n return sanitizeId(id)\n}\n\n/**\n * Generate unique transition ID\n */\nexport function generateTransitionId(fromStepId: string, toStepId: string): string {\n const id = `e_${fromStepId}_${toStepId}`\n return sanitizeId(id)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { CircleDot, CircleStop, User, Zap, Workflow, Clock } from "lucide-react";
|
|
1
|
+
import { CircleDot, CircleStop, User, Zap, Workflow, Clock, Timer } from "lucide-react";
|
|
2
2
|
const NODE_TYPE_ICONS = {
|
|
3
3
|
start: CircleDot,
|
|
4
4
|
end: CircleStop,
|
|
5
5
|
userTask: User,
|
|
6
6
|
automated: Zap,
|
|
7
7
|
subWorkflow: Workflow,
|
|
8
|
-
waitForSignal: Clock
|
|
8
|
+
waitForSignal: Clock,
|
|
9
|
+
waitForTimer: Timer
|
|
9
10
|
};
|
|
10
11
|
const NODE_TYPE_COLORS = {
|
|
11
12
|
start: "text-emerald-500",
|
|
@@ -13,7 +14,8 @@ const NODE_TYPE_COLORS = {
|
|
|
13
14
|
userTask: "text-blue-500",
|
|
14
15
|
automated: "text-amber-500",
|
|
15
16
|
subWorkflow: "text-purple-500",
|
|
16
|
-
waitForSignal: "text-purple-500"
|
|
17
|
+
waitForSignal: "text-purple-500",
|
|
18
|
+
waitForTimer: "text-cyan-500"
|
|
17
19
|
};
|
|
18
20
|
const NODE_TYPE_LABELS = {
|
|
19
21
|
start: { title: "START", description: "Workflow trigger" },
|
|
@@ -21,7 +23,8 @@ const NODE_TYPE_LABELS = {
|
|
|
21
23
|
userTask: { title: "USER TASK", description: "Manual action" },
|
|
22
24
|
automated: { title: "AUTOMATED", description: "System task" },
|
|
23
25
|
subWorkflow: { title: "SUB-WORKFLOW", description: "Invoke workflow" },
|
|
24
|
-
waitForSignal: { title: "WAIT FOR SIGNAL", description: "Pause for external event" }
|
|
26
|
+
waitForSignal: { title: "WAIT FOR SIGNAL", description: "Pause for external event" },
|
|
27
|
+
waitForTimer: { title: "WAIT FOR TIMER", description: "Pause for a duration" }
|
|
25
28
|
};
|
|
26
29
|
const STEP_TYPE_TO_NODE_TYPE = {
|
|
27
30
|
START: "start",
|
|
@@ -29,7 +32,8 @@ const STEP_TYPE_TO_NODE_TYPE = {
|
|
|
29
32
|
USER_TASK: "userTask",
|
|
30
33
|
AUTOMATED: "automated",
|
|
31
34
|
SUB_WORKFLOW: "subWorkflow",
|
|
32
|
-
WAIT_FOR_SIGNAL: "waitForSignal"
|
|
35
|
+
WAIT_FOR_SIGNAL: "waitForSignal",
|
|
36
|
+
WAIT_FOR_TIMER: "waitForTimer"
|
|
33
37
|
};
|
|
34
38
|
function stepTypeToNodeType(stepType) {
|
|
35
39
|
return STEP_TYPE_TO_NODE_TYPE[stepType] || "automated";
|
|
@@ -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, LucideIcon } from 'lucide-react'\n\nexport type NodeType = 'start' | 'end' | 'userTask' | 'automated' | 'subWorkflow' | 'waitForSignal'\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}\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}\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}\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}\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,aAAyB;
|
|
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;AAI9E,MAAM,kBAAgD;AAAA,EAC3D,OAAO;AAAA,EACP,KAAK;AAAA,EACL,UAAU;AAAA,EACV,WAAW;AAAA,EACX,aAAa;AAAA,EACb,eAAe;AAAA,EACf,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;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;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;AAClB;AAEO,SAAS,mBAAmB,UAA4B;AAC7D,SAAO,uBAAuB,QAAQ,KAAK;AAC7C;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
+
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
1
2
|
import { WorkflowInstance, WorkflowDefinition, StepInstance } from "../data/entities.js";
|
|
2
|
-
import { logWorkflowEvent } from "./event-logger.js";
|
|
3
|
-
import { executeWorkflow } from "./workflow-executor.js";
|
|
4
|
-
import * as stepHandler from "./step-handler.js";
|
|
5
|
-
import * as transitionHandler from "./transition-handler.js";
|
|
6
3
|
class SignalError extends Error {
|
|
7
4
|
constructor(message, code, details) {
|
|
8
5
|
super(message);
|
|
@@ -13,11 +10,21 @@ class SignalError extends Error {
|
|
|
13
10
|
}
|
|
14
11
|
async function sendSignal(em, container, options) {
|
|
15
12
|
const { instanceId, signalName, payload, userId, tenantId, organizationId } = options;
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
const eventLogger = container.resolve("eventLogger");
|
|
14
|
+
const stepHandler = container.resolve("stepHandler");
|
|
15
|
+
const transitionHandler = container.resolve("transitionHandler");
|
|
16
|
+
const workflowExecutor = container.resolve("workflowExecutor");
|
|
17
|
+
const instance = await findOneWithDecryption(
|
|
18
|
+
em,
|
|
19
|
+
WorkflowInstance,
|
|
20
|
+
{
|
|
21
|
+
id: instanceId,
|
|
22
|
+
tenantId,
|
|
23
|
+
organizationId
|
|
24
|
+
},
|
|
25
|
+
void 0,
|
|
26
|
+
{ tenantId, organizationId }
|
|
27
|
+
);
|
|
21
28
|
if (!instance) {
|
|
22
29
|
throw new SignalError(
|
|
23
30
|
"Workflow instance not found",
|
|
@@ -32,7 +39,18 @@ async function sendSignal(em, container, options) {
|
|
|
32
39
|
{ instanceId, status: instance.status }
|
|
33
40
|
);
|
|
34
41
|
}
|
|
35
|
-
const definition = await
|
|
42
|
+
const definition = await findOneWithDecryption(
|
|
43
|
+
em,
|
|
44
|
+
WorkflowDefinition,
|
|
45
|
+
{
|
|
46
|
+
id: instance.definitionId,
|
|
47
|
+
tenantId: instance.tenantId,
|
|
48
|
+
organizationId: instance.organizationId,
|
|
49
|
+
deletedAt: null
|
|
50
|
+
},
|
|
51
|
+
void 0,
|
|
52
|
+
{ tenantId: instance.tenantId, organizationId: instance.organizationId }
|
|
53
|
+
);
|
|
36
54
|
if (!definition) {
|
|
37
55
|
throw new SignalError(
|
|
38
56
|
"Workflow definition not found",
|
|
@@ -68,7 +86,7 @@ async function sendSignal(em, container, options) {
|
|
|
68
86
|
};
|
|
69
87
|
}
|
|
70
88
|
instance.updatedAt = now;
|
|
71
|
-
await logWorkflowEvent(em, {
|
|
89
|
+
await eventLogger.logWorkflowEvent(em, {
|
|
72
90
|
workflowInstanceId: instance.id,
|
|
73
91
|
eventType: "SIGNAL_RECEIVED",
|
|
74
92
|
eventData: {
|
|
@@ -79,11 +97,19 @@ async function sendSignal(em, container, options) {
|
|
|
79
97
|
tenantId: instance.tenantId,
|
|
80
98
|
organizationId: instance.organizationId
|
|
81
99
|
});
|
|
82
|
-
const stepInstance = await
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
100
|
+
const stepInstance = await findOneWithDecryption(
|
|
101
|
+
em,
|
|
102
|
+
StepInstance,
|
|
103
|
+
{
|
|
104
|
+
workflowInstanceId: instance.id,
|
|
105
|
+
stepId: instance.currentStepId,
|
|
106
|
+
status: "ACTIVE",
|
|
107
|
+
tenantId: instance.tenantId,
|
|
108
|
+
organizationId: instance.organizationId
|
|
109
|
+
},
|
|
110
|
+
void 0,
|
|
111
|
+
{ tenantId: instance.tenantId, organizationId: instance.organizationId }
|
|
112
|
+
);
|
|
87
113
|
if (stepInstance) {
|
|
88
114
|
await stepHandler.exitStep(em, stepInstance, {
|
|
89
115
|
signalName,
|
|
@@ -129,16 +155,22 @@ async function sendSignal(em, container, options) {
|
|
|
129
155
|
{ error: transitionResult.error }
|
|
130
156
|
);
|
|
131
157
|
}
|
|
132
|
-
await executeWorkflow(em, container, instance.id, { userId });
|
|
158
|
+
await workflowExecutor.executeWorkflow(em, container, instance.id, { userId });
|
|
133
159
|
}
|
|
134
160
|
async function sendSignalByCorrelationKey(em, container, options) {
|
|
135
161
|
const { correlationKey, signalName, payload, userId, tenantId, organizationId } = options;
|
|
136
|
-
const instances = await
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
162
|
+
const instances = await findWithDecryption(
|
|
163
|
+
em,
|
|
164
|
+
WorkflowInstance,
|
|
165
|
+
{
|
|
166
|
+
correlationKey,
|
|
167
|
+
status: "PAUSED",
|
|
168
|
+
tenantId,
|
|
169
|
+
organizationId
|
|
170
|
+
},
|
|
171
|
+
void 0,
|
|
172
|
+
{ tenantId, organizationId }
|
|
173
|
+
);
|
|
142
174
|
let signalsProcessed = 0;
|
|
143
175
|
for (const instance of instances) {
|
|
144
176
|
try {
|
|
@@ -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 { AwilixContainer } from 'awilix'\nimport { WorkflowInstance, WorkflowDefinition, StepInstance } from '../data/entities'\nimport
|
|
5
|
-
"mappings": "
|
|
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": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
UserTask,
|
|
5
5
|
WorkflowEvent
|
|
6
6
|
} from "../data/entities.js";
|
|
7
|
+
import { parseDuration } from "./duration.js";
|
|
8
|
+
import { logWorkflowEvent } from "./event-logger.js";
|
|
7
9
|
class StepExecutionError extends Error {
|
|
8
10
|
constructor(message, code, details) {
|
|
9
11
|
super(message);
|
|
@@ -178,9 +180,10 @@ async function executeStepByType(em, instance, stepInstance, stepDef, context, c
|
|
|
178
180
|
return await handleSubWorkflowStep(em, container, instance, stepInstance, stepDef, context);
|
|
179
181
|
case "WAIT_FOR_SIGNAL":
|
|
180
182
|
return await handleWaitForSignalStep(em, instance, stepInstance, stepDef, context);
|
|
183
|
+
case "WAIT_FOR_TIMER":
|
|
184
|
+
return await handleWaitForTimerStep(em, instance, stepInstance, stepDef, context);
|
|
181
185
|
case "PARALLEL_FORK":
|
|
182
186
|
case "PARALLEL_JOIN":
|
|
183
|
-
case "WAIT_FOR_TIMER":
|
|
184
187
|
throw new StepExecutionError(
|
|
185
188
|
`Step type not yet implemented: ${stepType}`,
|
|
186
189
|
"STEP_TYPE_NOT_IMPLEMENTED",
|
|
@@ -477,37 +480,84 @@ async function handleWaitForSignalStep(em, instance, stepInstance, stepDef, cont
|
|
|
477
480
|
}
|
|
478
481
|
};
|
|
479
482
|
}
|
|
480
|
-
function
|
|
481
|
-
const
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
483
|
+
async function handleWaitForTimerStep(em, instance, stepInstance, stepDef, context) {
|
|
484
|
+
const timerConfig = stepDef.config || stepDef.timerConfig || {};
|
|
485
|
+
const duration = timerConfig.duration;
|
|
486
|
+
const until = timerConfig.until;
|
|
487
|
+
if (!duration && !until) {
|
|
488
|
+
throw new StepExecutionError(
|
|
489
|
+
'WAIT_FOR_TIMER requires either "duration" (e.g., "PT5M") or "until" (ISO 8601 datetime)',
|
|
490
|
+
"TIMER_CONFIG_MISSING",
|
|
491
|
+
{ stepId: stepDef.stepId }
|
|
492
|
+
);
|
|
489
493
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
return value * 60 * 60 * 1e3;
|
|
500
|
-
case "m":
|
|
501
|
-
return value * 60 * 1e3;
|
|
502
|
-
case "s":
|
|
503
|
-
return value * 1e3;
|
|
494
|
+
let fireAtMs;
|
|
495
|
+
if (until) {
|
|
496
|
+
const targetDate = new Date(until);
|
|
497
|
+
if (isNaN(targetDate.getTime())) {
|
|
498
|
+
throw new StepExecutionError(
|
|
499
|
+
`WAIT_FOR_TIMER invalid "until" datetime: ${until}`,
|
|
500
|
+
"TIMER_CONFIG_INVALID",
|
|
501
|
+
{ until }
|
|
502
|
+
);
|
|
504
503
|
}
|
|
504
|
+
fireAtMs = targetDate.getTime();
|
|
505
|
+
} else {
|
|
506
|
+
fireAtMs = Date.now() + parseDuration(duration);
|
|
507
|
+
}
|
|
508
|
+
const delayMs = fireAtMs - Date.now();
|
|
509
|
+
const fireAt = new Date(fireAtMs);
|
|
510
|
+
if (delayMs <= 0) {
|
|
511
|
+
return {
|
|
512
|
+
status: "COMPLETED",
|
|
513
|
+
outputData: {
|
|
514
|
+
stepType: "WAIT_FOR_TIMER",
|
|
515
|
+
timerFiredImmediately: true,
|
|
516
|
+
fireAt,
|
|
517
|
+
duration,
|
|
518
|
+
until
|
|
519
|
+
}
|
|
520
|
+
};
|
|
505
521
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
522
|
+
const now = /* @__PURE__ */ new Date();
|
|
523
|
+
const { enqueueTimerJob } = await import("./activity-executor.js");
|
|
524
|
+
const jobId = await enqueueTimerJob({
|
|
525
|
+
workflowInstanceId: instance.id,
|
|
526
|
+
stepInstanceId: stepInstance.id,
|
|
527
|
+
tenantId: instance.tenantId,
|
|
528
|
+
organizationId: instance.organizationId,
|
|
529
|
+
userId: context.userId,
|
|
530
|
+
fireAt: fireAt.toISOString(),
|
|
531
|
+
delayMs
|
|
532
|
+
});
|
|
533
|
+
await logWorkflowEvent(em, {
|
|
534
|
+
workflowInstanceId: instance.id,
|
|
535
|
+
stepInstanceId: stepInstance.id,
|
|
536
|
+
eventType: "TIMER_AWAITING",
|
|
537
|
+
eventData: {
|
|
538
|
+
fireAt: fireAt.toISOString(),
|
|
539
|
+
duration: duration || null,
|
|
540
|
+
until: until || null,
|
|
541
|
+
jobId
|
|
542
|
+
},
|
|
543
|
+
userId: context.userId,
|
|
544
|
+
tenantId: instance.tenantId,
|
|
545
|
+
organizationId: instance.organizationId
|
|
546
|
+
});
|
|
547
|
+
instance.status = "PAUSED";
|
|
548
|
+
instance.pausedAt = now;
|
|
549
|
+
instance.updatedAt = now;
|
|
550
|
+
await em.flush();
|
|
551
|
+
return {
|
|
552
|
+
status: "WAITING",
|
|
553
|
+
waitReason: "TIMER",
|
|
554
|
+
outputData: {
|
|
555
|
+
fireAt,
|
|
556
|
+
duration,
|
|
557
|
+
until,
|
|
558
|
+
jobId
|
|
559
|
+
}
|
|
560
|
+
};
|
|
511
561
|
}
|
|
512
562
|
async function logStepEvent(em, event) {
|
|
513
563
|
const workflowEvent = em.create(WorkflowEvent, {
|