@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.
Files changed (102) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/step_instance/index.js +2 -0
  3. package/dist/generated/entities/step_instance/index.js.map +2 -2
  4. package/dist/generated/entities/user_task/index.js +2 -0
  5. package/dist/generated/entities/user_task/index.js.map +2 -2
  6. package/dist/generated/entities/workflow_branch_instance/index.js +39 -0
  7. package/dist/generated/entities/workflow_branch_instance/index.js.map +7 -0
  8. package/dist/generated/entities/workflow_event/index.js +2 -0
  9. package/dist/generated/entities/workflow_event/index.js.map +2 -2
  10. package/dist/generated/entities/workflow_instance/index.js +2 -0
  11. package/dist/generated/entities/workflow_instance/index.js.map +2 -2
  12. package/dist/generated/entities.ids.generated.js +1 -0
  13. package/dist/generated/entities.ids.generated.js.map +2 -2
  14. package/dist/generated/entity-fields-registry.js +24 -0
  15. package/dist/generated/entity-fields-registry.js.map +2 -2
  16. package/dist/modules/progress/api/jobs/[id]/route.js +7 -1
  17. package/dist/modules/progress/api/jobs/[id]/route.js.map +2 -2
  18. package/dist/modules/shipping_carriers/api/cancel/route.js +2 -2
  19. package/dist/modules/shipping_carriers/api/cancel/route.js.map +2 -2
  20. package/dist/modules/shipping_carriers/lib/status-sync.js +8 -1
  21. package/dist/modules/shipping_carriers/lib/status-sync.js.map +2 -2
  22. package/dist/modules/workflows/components/NodeEditDialog.js +3 -1
  23. package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
  24. package/dist/modules/workflows/components/WorkflowGraphImpl.js +4 -2
  25. package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
  26. package/dist/modules/workflows/components/nodes/ParallelForkNode.js +49 -0
  27. package/dist/modules/workflows/components/nodes/ParallelForkNode.js.map +7 -0
  28. package/dist/modules/workflows/components/nodes/ParallelJoinNode.js +49 -0
  29. package/dist/modules/workflows/components/nodes/ParallelJoinNode.js.map +7 -0
  30. package/dist/modules/workflows/components/nodes/index.js +4 -0
  31. package/dist/modules/workflows/components/nodes/index.js.map +2 -2
  32. package/dist/modules/workflows/data/entities.js +81 -0
  33. package/dist/modules/workflows/data/entities.js.map +2 -2
  34. package/dist/modules/workflows/data/validators.js +146 -1
  35. package/dist/modules/workflows/data/validators.js.map +2 -2
  36. package/dist/modules/workflows/events.js +7 -1
  37. package/dist/modules/workflows/events.js.map +2 -2
  38. package/dist/modules/workflows/lib/activity-executor.js +4 -2
  39. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  40. package/dist/modules/workflows/lib/activity-queue-types.js.map +2 -2
  41. package/dist/modules/workflows/lib/event-logger.js +2 -0
  42. package/dist/modules/workflows/lib/event-logger.js.map +2 -2
  43. package/dist/modules/workflows/lib/execution-token.js +98 -0
  44. package/dist/modules/workflows/lib/execution-token.js.map +7 -0
  45. package/dist/modules/workflows/lib/node-type-icons.js +14 -5
  46. package/dist/modules/workflows/lib/node-type-icons.js.map +2 -2
  47. package/dist/modules/workflows/lib/parallel-handler.js +364 -0
  48. package/dist/modules/workflows/lib/parallel-handler.js.map +7 -0
  49. package/dist/modules/workflows/lib/signal-handler.js +63 -1
  50. package/dist/modules/workflows/lib/signal-handler.js.map +2 -2
  51. package/dist/modules/workflows/lib/step-handler.js +74 -30
  52. package/dist/modules/workflows/lib/step-handler.js.map +2 -2
  53. package/dist/modules/workflows/lib/task-handler.js +26 -0
  54. package/dist/modules/workflows/lib/task-handler.js.map +2 -2
  55. package/dist/modules/workflows/lib/timer-handler.js +26 -1
  56. package/dist/modules/workflows/lib/timer-handler.js.map +2 -2
  57. package/dist/modules/workflows/lib/transition-handler.js +33 -21
  58. package/dist/modules/workflows/lib/transition-handler.js.map +2 -2
  59. package/dist/modules/workflows/lib/workflow-executor.js +39 -1
  60. package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
  61. package/dist/modules/workflows/migrations/Migration20260602120000.js +24 -0
  62. package/dist/modules/workflows/migrations/Migration20260602120000.js.map +7 -0
  63. package/dist/modules/workflows/workers/workflow-activities.worker.js +8 -4
  64. package/dist/modules/workflows/workers/workflow-activities.worker.js.map +2 -2
  65. package/generated/entities/step_instance/index.ts +1 -0
  66. package/generated/entities/user_task/index.ts +1 -0
  67. package/generated/entities/workflow_branch_instance/index.ts +18 -0
  68. package/generated/entities/workflow_event/index.ts +1 -0
  69. package/generated/entities/workflow_instance/index.ts +1 -0
  70. package/generated/entities.ids.generated.ts +1 -0
  71. package/generated/entity-fields-registry.ts +24 -0
  72. package/package.json +7 -7
  73. package/src/modules/progress/api/jobs/[id]/route.ts +7 -0
  74. package/src/modules/shipping_carriers/api/cancel/route.ts +2 -2
  75. package/src/modules/shipping_carriers/lib/status-sync.ts +19 -0
  76. package/src/modules/workflows/components/NodeEditDialog.tsx +2 -0
  77. package/src/modules/workflows/components/WorkflowGraphImpl.tsx +3 -1
  78. package/src/modules/workflows/components/nodes/ParallelForkNode.tsx +66 -0
  79. package/src/modules/workflows/components/nodes/ParallelJoinNode.tsx +66 -0
  80. package/src/modules/workflows/components/nodes/index.ts +6 -0
  81. package/src/modules/workflows/data/entities.ts +109 -0
  82. package/src/modules/workflows/data/validators.ts +223 -0
  83. package/src/modules/workflows/events.ts +7 -0
  84. package/src/modules/workflows/i18n/de.json +12 -0
  85. package/src/modules/workflows/i18n/en.json +12 -0
  86. package/src/modules/workflows/i18n/es.json +12 -0
  87. package/src/modules/workflows/i18n/pl.json +12 -0
  88. package/src/modules/workflows/lib/activity-executor.ts +8 -2
  89. package/src/modules/workflows/lib/activity-queue-types.ts +3 -0
  90. package/src/modules/workflows/lib/event-logger.ts +3 -0
  91. package/src/modules/workflows/lib/execution-token.ts +166 -0
  92. package/src/modules/workflows/lib/node-type-icons.ts +11 -2
  93. package/src/modules/workflows/lib/parallel-handler.ts +575 -0
  94. package/src/modules/workflows/lib/signal-handler.ts +72 -1
  95. package/src/modules/workflows/lib/step-handler.ts +94 -34
  96. package/src/modules/workflows/lib/task-handler.ts +32 -0
  97. package/src/modules/workflows/lib/timer-handler.ts +30 -1
  98. package/src/modules/workflows/lib/transition-handler.ts +56 -24
  99. package/src/modules/workflows/lib/workflow-executor.ts +53 -1
  100. package/src/modules/workflows/migrations/.snapshot-open-mercato.json +263 -0
  101. package/src/modules/workflows/migrations/Migration20260602120000.ts +25 -0
  102. package/src/modules/workflows/workers/workflow-activities.worker.ts +9 -4
@@ -913,6 +913,8 @@
913
913
  "workflows.nodeTypes.automated": "AUTOMATIZADO",
914
914
  "workflows.nodeTypes.decision": "DECISIÓN",
915
915
  "workflows.nodeTypes.end": "FIN",
916
+ "workflows.nodeTypes.parallelFork": "Bifurcación paralela",
917
+ "workflows.nodeTypes.parallelJoin": "Unión paralela",
916
918
  "workflows.nodeTypes.start": "INICIO",
917
919
  "workflows.nodeTypes.subWorkflow": "SUBFLUJO DE TRABAJO",
918
920
  "workflows.nodeTypes.userTask": "TAREA DE USUARIO",
@@ -937,6 +939,16 @@
937
939
  "workflows.orderApproval.requestApproval": "Solicitar aprobación",
938
940
  "workflows.orderApproval.startError": "Error al iniciar el flujo de aprobación.",
939
941
  "workflows.orderApproval.submitDecision": "Enviar decisión",
942
+ "workflows.parallel.branch.label": "Rama",
943
+ "workflows.parallel.branch.status.ACTIVE": "Activa",
944
+ "workflows.parallel.branch.status.CANCELLED": "Cancelada",
945
+ "workflows.parallel.branch.status.COMPLETED": "Completada",
946
+ "workflows.parallel.branch.status.FAILED": "Fallida",
947
+ "workflows.parallel.branch.status.PAUSED": "En pausa",
948
+ "workflows.parallel.branch.status.WAITING_FOR_ACTIVITIES": "Esperando actividades",
949
+ "workflows.parallel.fork.label": "Bifurcación paralela",
950
+ "workflows.parallel.join.label": "Unión paralela",
951
+ "workflows.parallel.validation.title": "La validación de la bifurcación/unión paralela falló",
940
952
  "workflows.signals.awaiting": "Esperando senal",
941
953
  "workflows.signals.correlationKey": "Clave de correlacion",
942
954
  "workflows.signals.payload": "Carga de la senal",
@@ -913,6 +913,8 @@
913
913
  "workflows.nodeTypes.automated": "AUTOMATYCZNE",
914
914
  "workflows.nodeTypes.decision": "DECYZJA",
915
915
  "workflows.nodeTypes.end": "KONIEC",
916
+ "workflows.nodeTypes.parallelFork": "Rozgałęzienie równoległe",
917
+ "workflows.nodeTypes.parallelJoin": "Połączenie równoległe",
916
918
  "workflows.nodeTypes.start": "START",
917
919
  "workflows.nodeTypes.subWorkflow": "PODPRZEPŁYW",
918
920
  "workflows.nodeTypes.userTask": "ZADANIE UŻYTKOWNIKA",
@@ -937,6 +939,16 @@
937
939
  "workflows.orderApproval.requestApproval": "Poproś o zatwierdzenie",
938
940
  "workflows.orderApproval.startError": "Nie udało się uruchomić przepływu zatwierdzania.",
939
941
  "workflows.orderApproval.submitDecision": "Zatwierdź decyzję",
942
+ "workflows.parallel.branch.label": "Gałąź",
943
+ "workflows.parallel.branch.status.ACTIVE": "Aktywna",
944
+ "workflows.parallel.branch.status.CANCELLED": "Anulowana",
945
+ "workflows.parallel.branch.status.COMPLETED": "Zakończona",
946
+ "workflows.parallel.branch.status.FAILED": "Niepowodzenie",
947
+ "workflows.parallel.branch.status.PAUSED": "Wstrzymana",
948
+ "workflows.parallel.branch.status.WAITING_FOR_ACTIVITIES": "Oczekuje na aktywności",
949
+ "workflows.parallel.fork.label": "Rozgałęzienie równoległe",
950
+ "workflows.parallel.join.label": "Połączenie równoległe",
951
+ "workflows.parallel.validation.title": "Walidacja równoległego rozgałęzienia/połączenia nie powiodła się",
940
952
  "workflows.signals.awaiting": "Oczekiwanie na sygnał",
941
953
  "workflows.signals.correlationKey": "Klucz Korelacji",
942
954
  "workflows.signals.payload": "Zawartość Sygnału",
@@ -80,6 +80,9 @@ export interface ActivityContext {
80
80
  workflowContext: Record<string, any>
81
81
  stepContext?: Record<string, any>
82
82
  stepInstanceId?: string
83
+ // Set when the activity runs inside a parallel branch; carried on the queue
84
+ // payload so async resume targets the branch rather than the instance.
85
+ branchInstanceId?: string | null
83
86
  transitionId?: string
84
87
  userId?: string
85
88
  }
@@ -142,7 +145,7 @@ export async function enqueueActivity(
142
145
  activity: ActivityDefinition,
143
146
  context: ActivityContext
144
147
  ): Promise<string> {
145
- const { workflowInstance, workflowContext, stepContext, transitionId, stepInstanceId } =
148
+ const { workflowInstance, workflowContext, stepContext, transitionId, stepInstanceId, branchInstanceId } =
146
149
  context
147
150
 
148
151
  // Interpolate config variables NOW (before queuing)
@@ -152,6 +155,7 @@ export async function enqueueActivity(
152
155
  const job: WorkflowActivityJob = {
153
156
  workflowInstanceId: workflowInstance.id,
154
157
  stepInstanceId,
158
+ branchInstanceId: branchInstanceId ?? undefined,
155
159
  transitionId,
156
160
  activityId: activity.activityId,
157
161
  activityName: activity.activityName || activity.activityType,
@@ -201,13 +205,14 @@ export async function enqueueActivity(
201
205
  export async function enqueueTimerJob(params: {
202
206
  workflowInstanceId: string
203
207
  stepInstanceId: string
208
+ branchInstanceId?: string | null
204
209
  tenantId: string
205
210
  organizationId: string
206
211
  userId?: string
207
212
  fireAt: string
208
213
  delayMs: number
209
214
  }): Promise<string> {
210
- const { workflowInstanceId, stepInstanceId, tenantId, organizationId, userId, fireAt, delayMs } =
215
+ const { workflowInstanceId, stepInstanceId, branchInstanceId, tenantId, organizationId, userId, fireAt, delayMs } =
211
216
  params
212
217
 
213
218
  const queue = getActivityQueue()
@@ -216,6 +221,7 @@ export async function enqueueTimerJob(params: {
216
221
  kind: 'timer',
217
222
  workflowInstanceId,
218
223
  stepInstanceId,
224
+ branchInstanceId: branchInstanceId ?? undefined,
219
225
  tenantId,
220
226
  organizationId,
221
227
  userId,
@@ -10,6 +10,9 @@
10
10
  export interface WorkflowActivityJobBase {
11
11
  workflowInstanceId: string
12
12
  stepInstanceId?: string
13
+ // Set when the job belongs to a parallel branch; resume targets that branch.
14
+ // Absent on jobs enqueued before parallel support shipped → instance-level resume.
15
+ branchInstanceId?: string | null
13
16
  tenantId: string
14
17
  organizationId: string
15
18
  userId?: string
@@ -87,6 +87,7 @@ export type WorkflowEventType = typeof WorkflowEventTypes[keyof typeof WorkflowE
87
87
  export interface WorkflowEventInput {
88
88
  workflowInstanceId: string
89
89
  stepInstanceId?: string
90
+ branchInstanceId?: string | null
90
91
  eventType: WorkflowEventType | string
91
92
  eventData: any
92
93
  userId?: string
@@ -128,6 +129,7 @@ export async function logWorkflowEvent(
128
129
  const workflowEvent = em.create(WorkflowEvent, {
129
130
  workflowInstanceId: event.workflowInstanceId,
130
131
  stepInstanceId: event.stepInstanceId || null,
132
+ branchInstanceId: event.branchInstanceId ?? null,
131
133
  eventType: event.eventType,
132
134
  eventData: event.eventData || {},
133
135
  userId: event.userId || null,
@@ -156,6 +158,7 @@ export async function logWorkflowEvents(
156
158
  em.create(WorkflowEvent, {
157
159
  workflowInstanceId: event.workflowInstanceId,
158
160
  stepInstanceId: event.stepInstanceId || null,
161
+ branchInstanceId: event.branchInstanceId ?? null,
159
162
  eventType: event.eventType,
160
163
  eventData: event.eventData || {},
161
164
  userId: event.userId || null,
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Workflows Module - Execution Token Abstraction
3
+ *
4
+ * An execution token is the cursor the engine advances: it holds a
5
+ * `currentStepId`, a read/write context scope, a status, and an optional
6
+ * pending async transition. Today there are two kinds:
7
+ *
8
+ * - **root token** — backed directly by the `WorkflowInstance`. Used for all
9
+ * single-token (FORK-less) execution. Every accessor maps 1:1 onto the
10
+ * instance fields, so the legacy single-token path is behaviourally
11
+ * unchanged.
12
+ * - **branch token** — backed by a `WorkflowBranchInstance` created by a
13
+ * PARALLEL_FORK. The branch shares the parent instance's identity (id,
14
+ * definition, tenant/org) for step-instance/event scoping, but owns its own
15
+ * `currentStepId`, private context namespace, status and pending transition.
16
+ *
17
+ * The handlers operate on tokens via these functional accessors so the same
18
+ * step/transition logic serves both kinds.
19
+ */
20
+
21
+ import type {
22
+ WorkflowInstance,
23
+ WorkflowBranchInstance,
24
+ } from '../data/entities'
25
+
26
+ export interface PendingTransitionState {
27
+ toStepId: string
28
+ activityResults: any[]
29
+ timestamp: Date
30
+ }
31
+
32
+ export type ExecutionToken =
33
+ | { kind: 'root'; instance: WorkflowInstance }
34
+ | { kind: 'branch'; instance: WorkflowInstance; branch: WorkflowBranchInstance }
35
+
36
+ /** Build a root token backed by the instance (single-token execution). */
37
+ export function rootToken(instance: WorkflowInstance): ExecutionToken {
38
+ return { kind: 'root', instance }
39
+ }
40
+
41
+ /** Build a branch token backed by a branch instance of the given parent. */
42
+ export function branchToken(
43
+ instance: WorkflowInstance,
44
+ branch: WorkflowBranchInstance,
45
+ ): ExecutionToken {
46
+ return { kind: 'branch', instance, branch }
47
+ }
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Identity (shared between root and branch — step instances and events are
51
+ // always scoped to the parent WorkflowInstance, tagged with branchInstanceId).
52
+ // ---------------------------------------------------------------------------
53
+
54
+ export function tokenInstanceId(token: ExecutionToken): string {
55
+ return token.instance.id
56
+ }
57
+
58
+ export function tokenDefinitionId(token: ExecutionToken): string {
59
+ return token.instance.definitionId
60
+ }
61
+
62
+ export function tokenTenantId(token: ExecutionToken): string {
63
+ return token.instance.tenantId
64
+ }
65
+
66
+ export function tokenOrganizationId(token: ExecutionToken): string {
67
+ return token.instance.organizationId
68
+ }
69
+
70
+ /** The branch id this token represents, or null for the root token. */
71
+ export function tokenBranchInstanceId(token: ExecutionToken): string | null {
72
+ return token.kind === 'branch' ? token.branch.id : null
73
+ }
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Cursor + context
77
+ // ---------------------------------------------------------------------------
78
+
79
+ export function tokenCurrentStepId(token: ExecutionToken): string {
80
+ return token.kind === 'branch' ? token.branch.currentStepId : token.instance.currentStepId
81
+ }
82
+
83
+ export function setTokenCurrentStepId(token: ExecutionToken, stepId: string): void {
84
+ if (token.kind === 'branch') token.branch.currentStepId = stepId
85
+ else token.instance.currentStepId = stepId
86
+ }
87
+
88
+ /**
89
+ * Effective read context: for a branch this is the instance context (snapshot)
90
+ * overlaid with the branch's private namespace, so a branch sees fork-time
91
+ * instance state plus its own writes. For the root it is the instance context.
92
+ */
93
+ export function tokenReadContext(token: ExecutionToken): Record<string, any> {
94
+ if (token.kind === 'branch') {
95
+ return { ...(token.instance.context || {}), ...(token.branch.contextNamespace || {}) }
96
+ }
97
+ return token.instance.context || {}
98
+ }
99
+
100
+ /**
101
+ * Apply context writes after a transition. The root path is identical to the
102
+ * legacy behaviour: merge the passed workflowContext and activity outputs into
103
+ * `instance.context`. A branch writes only activity outputs (and any explicit
104
+ * deltas the caller already folded into its namespace) into its private
105
+ * namespace, never the shared instance snapshot — this prevents cross-branch
106
+ * key collisions.
107
+ */
108
+ export function applyTokenContextWrites(
109
+ token: ExecutionToken,
110
+ workflowContext: Record<string, any>,
111
+ activityOutputs: Record<string, any>,
112
+ ): void {
113
+ if (token.kind === 'branch') {
114
+ token.branch.contextNamespace = {
115
+ ...(token.branch.contextNamespace || {}),
116
+ ...activityOutputs,
117
+ }
118
+ return
119
+ }
120
+ token.instance.context = {
121
+ ...(token.instance.context || {}),
122
+ ...workflowContext,
123
+ ...activityOutputs,
124
+ }
125
+ }
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // Status / pause state machine
129
+ // ---------------------------------------------------------------------------
130
+
131
+ export function setTokenWaitingForActivities(token: ExecutionToken): void {
132
+ if (token.kind === 'branch') token.branch.status = 'WAITING_FOR_ACTIVITIES'
133
+ else token.instance.status = 'WAITING_FOR_ACTIVITIES'
134
+ }
135
+
136
+ export function setTokenPaused(token: ExecutionToken, at: Date): void {
137
+ if (token.kind === 'branch') {
138
+ token.branch.status = 'PAUSED'
139
+ } else {
140
+ token.instance.status = 'PAUSED'
141
+ token.instance.pausedAt = at
142
+ }
143
+ }
144
+
145
+ export function getTokenPendingTransition(token: ExecutionToken): PendingTransitionState | null {
146
+ return (token.kind === 'branch' ? token.branch.pendingTransition : token.instance.pendingTransition) ?? null
147
+ }
148
+
149
+ export function setTokenPendingTransition(token: ExecutionToken, pending: PendingTransitionState | null): void {
150
+ if (token.kind === 'branch') token.branch.pendingTransition = pending
151
+ else token.instance.pendingTransition = pending
152
+ }
153
+
154
+ export function touchToken(token: ExecutionToken, now: Date): void {
155
+ if (token.kind === 'branch') token.branch.updatedAt = now
156
+ else token.instance.updatedAt = now
157
+ }
158
+
159
+ /** Merge a patch into the token's own write scope (instance.context or branch namespace). */
160
+ export function mergeTokenContext(token: ExecutionToken, patch: Record<string, any>): void {
161
+ if (token.kind === 'branch') {
162
+ token.branch.contextNamespace = { ...(token.branch.contextNamespace || {}), ...patch }
163
+ } else {
164
+ token.instance.context = { ...(token.instance.context || {}), ...patch }
165
+ }
166
+ }
@@ -1,6 +1,6 @@
1
- import { CircleDot, CircleStop, User, Zap, Workflow, Clock, Timer, LucideIcon } from 'lucide-react'
1
+ import { CircleDot, CircleStop, User, Zap, Workflow, Clock, Timer, Split, Merge, LucideIcon } from 'lucide-react'
2
2
 
3
- export type NodeType = 'start' | 'end' | 'userTask' | 'automated' | 'subWorkflow' | 'waitForSignal' | 'waitForTimer'
3
+ export type NodeType = 'start' | 'end' | 'userTask' | 'automated' | 'subWorkflow' | 'waitForSignal' | 'waitForTimer' | 'parallelFork' | 'parallelJoin'
4
4
 
5
5
  export const NODE_TYPE_ICONS: Record<NodeType, LucideIcon> = {
6
6
  start: CircleDot,
@@ -10,6 +10,8 @@ export const NODE_TYPE_ICONS: Record<NodeType, LucideIcon> = {
10
10
  subWorkflow: Workflow,
11
11
  waitForSignal: Clock,
12
12
  waitForTimer: Timer,
13
+ parallelFork: Split,
14
+ parallelJoin: Merge,
13
15
  }
14
16
 
15
17
  export const NODE_TYPE_COLORS: Record<NodeType, string> = {
@@ -20,6 +22,9 @@ export const NODE_TYPE_COLORS: Record<NodeType, string> = {
20
22
  subWorkflow: 'text-purple-500',
21
23
  waitForSignal: 'text-purple-500',
22
24
  waitForTimer: 'text-cyan-500',
25
+ // New nodes use a semantic token (DS rule: no hardcoded color shades).
26
+ parallelFork: 'text-primary',
27
+ parallelJoin: 'text-primary',
23
28
  }
24
29
 
25
30
  export const NODE_TYPE_LABELS: Record<NodeType, { title: string; description: string }> = {
@@ -30,6 +35,8 @@ export const NODE_TYPE_LABELS: Record<NodeType, { title: string; description: st
30
35
  subWorkflow: { title: 'SUB-WORKFLOW', description: 'Invoke workflow' },
31
36
  waitForSignal: { title: 'WAIT FOR SIGNAL', description: 'Pause for external event' },
32
37
  waitForTimer: { title: 'WAIT FOR TIMER', description: 'Pause for a duration' },
38
+ parallelFork: { title: 'PARALLEL FORK', description: 'Split into parallel branches' },
39
+ parallelJoin: { title: 'PARALLEL JOIN', description: 'Wait for all branches' },
33
40
  }
34
41
 
35
42
  const STEP_TYPE_TO_NODE_TYPE: Record<string, NodeType> = {
@@ -40,6 +47,8 @@ const STEP_TYPE_TO_NODE_TYPE: Record<string, NodeType> = {
40
47
  SUB_WORKFLOW: 'subWorkflow',
41
48
  WAIT_FOR_SIGNAL: 'waitForSignal',
42
49
  WAIT_FOR_TIMER: 'waitForTimer',
50
+ PARALLEL_FORK: 'parallelFork',
51
+ PARALLEL_JOIN: 'parallelJoin',
43
52
  }
44
53
 
45
54
  export function stepTypeToNodeType(stepType: string): NodeType {