@open-mercato/core 0.6.3-develop.3876.1.d40fe4ec2d → 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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
+
import { parseDuration } from '../lib/duration'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Workflows Module - Zod Validators
|
|
@@ -8,6 +9,40 @@ import { z } from 'zod'
|
|
|
8
9
|
|
|
9
10
|
const uuid = z.uuid()
|
|
10
11
|
|
|
12
|
+
// Variable interpolation tokens (e.g., {{context.timeout}}) are resolved at
|
|
13
|
+
// run time, so we must skip strict syntax checks on them at save time.
|
|
14
|
+
const containsTemplate = (value: string) => value.includes('{{')
|
|
15
|
+
|
|
16
|
+
export function isValidDurationString(value: unknown): boolean {
|
|
17
|
+
if (typeof value !== 'string' || value.length === 0) return false
|
|
18
|
+
if (containsTemplate(value)) return true
|
|
19
|
+
try {
|
|
20
|
+
const ms = parseDuration(value)
|
|
21
|
+
return Number.isFinite(ms) && ms > 0
|
|
22
|
+
} catch {
|
|
23
|
+
return false
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isValidIsoDateString(value: unknown): boolean {
|
|
28
|
+
if (typeof value !== 'string' || value.length === 0) return false
|
|
29
|
+
if (containsTemplate(value)) return true
|
|
30
|
+
const d = new Date(value)
|
|
31
|
+
return !Number.isNaN(d.getTime())
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isFutureIsoDateString(value: unknown): boolean {
|
|
35
|
+
if (typeof value !== 'string' || value.length === 0) return false
|
|
36
|
+
if (containsTemplate(value)) return true
|
|
37
|
+
const d = new Date(value)
|
|
38
|
+
if (Number.isNaN(d.getTime())) return false
|
|
39
|
+
return d.getTime() > Date.now()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const DURATION_ERROR = 'Invalid duration. Use ISO 8601 (e.g., PT5M, PT1H, P1D) or simple format (5m, 1h, 3d)'
|
|
43
|
+
const UNTIL_ERROR = 'Invalid "until". Provide an ISO 8601 datetime string'
|
|
44
|
+
const UNTIL_PAST_ERROR = '"until" must be a future datetime'
|
|
45
|
+
|
|
11
46
|
// ============================================================================
|
|
12
47
|
// Enum Schemas - Workflow Types and Statuses
|
|
13
48
|
// ============================================================================
|
|
@@ -169,6 +204,49 @@ export const activityDefinitionSchema = z.object({
|
|
|
169
204
|
activityId: z.string().min(1), // ID of compensation activity
|
|
170
205
|
automatic: z.boolean().default(true).optional() // Auto-trigger on failure
|
|
171
206
|
}).optional(), // Compensation configuration (Phase 8.2)
|
|
207
|
+
}).superRefine((activity, ctx) => {
|
|
208
|
+
if (activity.activityType !== 'WAIT') return
|
|
209
|
+
const config = activity.config || {}
|
|
210
|
+
const hasDuration = config.duration != null && config.duration !== ''
|
|
211
|
+
const hasUntil = config.until != null && config.until !== ''
|
|
212
|
+
if (!hasDuration && !hasUntil) {
|
|
213
|
+
ctx.addIssue({
|
|
214
|
+
code: 'custom',
|
|
215
|
+
path: ['config'],
|
|
216
|
+
message: 'WAIT activity requires "duration" or "until"',
|
|
217
|
+
})
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
if (hasDuration && hasUntil) {
|
|
221
|
+
ctx.addIssue({
|
|
222
|
+
code: 'custom',
|
|
223
|
+
path: ['config'],
|
|
224
|
+
message: 'WAIT activity accepts "duration" OR "until", not both',
|
|
225
|
+
})
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
if (hasDuration && !isValidDurationString(config.duration)) {
|
|
229
|
+
ctx.addIssue({
|
|
230
|
+
code: 'custom',
|
|
231
|
+
path: ['config', 'duration'],
|
|
232
|
+
message: DURATION_ERROR,
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
if (hasUntil) {
|
|
236
|
+
if (!isValidIsoDateString(config.until)) {
|
|
237
|
+
ctx.addIssue({
|
|
238
|
+
code: 'custom',
|
|
239
|
+
path: ['config', 'until'],
|
|
240
|
+
message: UNTIL_ERROR,
|
|
241
|
+
})
|
|
242
|
+
} else if (!isFutureIsoDateString(config.until)) {
|
|
243
|
+
ctx.addIssue({
|
|
244
|
+
code: 'custom',
|
|
245
|
+
path: ['config', 'until'],
|
|
246
|
+
message: UNTIL_PAST_ERROR,
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
}
|
|
172
250
|
})
|
|
173
251
|
|
|
174
252
|
// Localized validation message schema (for START step pre-conditions)
|
|
@@ -201,6 +279,49 @@ export const workflowStepSchema = z.object({
|
|
|
201
279
|
retryPolicy: retryPolicySchema.optional(),
|
|
202
280
|
// Pre-conditions for START step (business rules to validate before workflow can be started)
|
|
203
281
|
preConditions: z.array(startPreConditionSchema).optional(),
|
|
282
|
+
}).superRefine((step, ctx) => {
|
|
283
|
+
if (step.stepType !== 'WAIT_FOR_TIMER') return
|
|
284
|
+
const config = step.config || {}
|
|
285
|
+
const hasDuration = config.duration != null && config.duration !== ''
|
|
286
|
+
const hasUntil = config.until != null && config.until !== ''
|
|
287
|
+
if (!hasDuration && !hasUntil) {
|
|
288
|
+
ctx.addIssue({
|
|
289
|
+
code: 'custom',
|
|
290
|
+
path: ['config'],
|
|
291
|
+
message: 'WAIT_FOR_TIMER step requires "duration" or "until"',
|
|
292
|
+
})
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
if (hasDuration && hasUntil) {
|
|
296
|
+
ctx.addIssue({
|
|
297
|
+
code: 'custom',
|
|
298
|
+
path: ['config'],
|
|
299
|
+
message: 'WAIT_FOR_TIMER step accepts "duration" OR "until", not both',
|
|
300
|
+
})
|
|
301
|
+
return
|
|
302
|
+
}
|
|
303
|
+
if (hasDuration && !isValidDurationString(config.duration)) {
|
|
304
|
+
ctx.addIssue({
|
|
305
|
+
code: 'custom',
|
|
306
|
+
path: ['config', 'duration'],
|
|
307
|
+
message: DURATION_ERROR,
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
if (hasUntil) {
|
|
311
|
+
if (!isValidIsoDateString(config.until)) {
|
|
312
|
+
ctx.addIssue({
|
|
313
|
+
code: 'custom',
|
|
314
|
+
path: ['config', 'until'],
|
|
315
|
+
message: UNTIL_ERROR,
|
|
316
|
+
})
|
|
317
|
+
} else if (!isFutureIsoDateString(config.until)) {
|
|
318
|
+
ctx.addIssue({
|
|
319
|
+
code: 'custom',
|
|
320
|
+
path: ['config', 'until'],
|
|
321
|
+
message: UNTIL_PAST_ERROR,
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
}
|
|
204
325
|
})
|
|
205
326
|
|
|
206
327
|
// Transition condition (reference to business rule)
|
|
@@ -11,6 +11,8 @@ import * as stepHandler from './lib/step-handler'
|
|
|
11
11
|
import * as transitionHandler from './lib/transition-handler'
|
|
12
12
|
import * as activityExecutor from './lib/activity-executor'
|
|
13
13
|
import * as eventLogger from './lib/event-logger'
|
|
14
|
+
import * as signalHandler from './lib/signal-handler'
|
|
15
|
+
import * as timerHandler from './lib/timer-handler'
|
|
14
16
|
|
|
15
17
|
export function register(container: AwilixContainer): void {
|
|
16
18
|
container.register({
|
|
@@ -19,5 +21,7 @@ export function register(container: AwilixContainer): void {
|
|
|
19
21
|
transitionHandler: asFunction(() => transitionHandler).scoped(),
|
|
20
22
|
activityExecutor: asFunction(() => activityExecutor).scoped(),
|
|
21
23
|
eventLogger: asFunction(() => eventLogger).scoped(),
|
|
24
|
+
signalHandler: asFunction(() => signalHandler).scoped(),
|
|
25
|
+
timerHandler: asFunction(() => timerHandler).scoped(),
|
|
22
26
|
})
|
|
23
27
|
}
|
|
@@ -93,7 +93,13 @@
|
|
|
93
93
|
"workflows.activities.types.EXECUTE_FUNCTION": "Funktion ausführen",
|
|
94
94
|
"workflows.activities.types.SEND_EMAIL": "E-Mail senden",
|
|
95
95
|
"workflows.activities.types.UPDATE_ENTITY": "Entität aktualisieren",
|
|
96
|
-
"workflows.activities.types.WAIT": "Warten",
|
|
96
|
+
"workflows.activities.types.WAIT": "Warten / Verzögerung",
|
|
97
|
+
"workflows.activities.waitDuration": "Dauer",
|
|
98
|
+
"workflows.activities.waitDurationDescription": "ISO 8601 Dauer (z.B. PT5M für 5 Minuten, PT1H für 1 Stunde, P1D für 1 Tag) oder einfaches Format (5m, 1h, 3d)",
|
|
99
|
+
"workflows.activities.waitDurationPlaceholder": "PT5M",
|
|
100
|
+
"workflows.activities.waitOr": "oder",
|
|
101
|
+
"workflows.activities.waitUntil": "Warten bis",
|
|
102
|
+
"workflows.activities.waitUntilDescription": "Bestimmtes Datum und Uhrzeit, bis zu dem gewartet werden soll",
|
|
97
103
|
"workflows.backToList": "Zurück zu Workflows",
|
|
98
104
|
"workflows.backend.definitions.visual_editor.title": "Visueller Workflow-Editor",
|
|
99
105
|
"workflows.checkoutDemo.cart.empty.hint": "Füge Produkte aus dem Dropdown oben hinzu",
|
|
@@ -910,6 +916,7 @@
|
|
|
910
916
|
"workflows.nodeTypes.subWorkflow": "SUB-WORKFLOW",
|
|
911
917
|
"workflows.nodeTypes.userTask": "BENUTZERAUFGABE",
|
|
912
918
|
"workflows.nodeTypes.waitForSignal": "AUF SIGNAL WARTEN",
|
|
919
|
+
"workflows.nodeTypes.waitForTimer": "AUF TIMER WARTEN",
|
|
913
920
|
"workflows.notifications.task.assigned.body": "Ihnen wurde die Aufgabe \"{taskName}\" im Workflow \"{workflowName}\" zugewiesen{dueDate, select, other { (fällig: {dueDate})}}",
|
|
914
921
|
"workflows.notifications.task.assigned.title": "Aufgabe zugewiesen",
|
|
915
922
|
"workflows.orderApproval.approveButton": "Genehmigen",
|
|
@@ -1135,6 +1142,7 @@
|
|
|
1135
1142
|
"workflows.triggers.status.disabled": "Deaktiviert",
|
|
1136
1143
|
"workflows.triggers.title": "Ereignis-Trigger",
|
|
1137
1144
|
"workflows.validation.descriptionMaxLength": "Beschreibung darf maximal 5000 Zeichen lang sein",
|
|
1145
|
+
"workflows.validation.invalidDuration": "Ungültige Dauer. Verwenden Sie ISO 8601 (z. B. PT5M, PT1H, P1D) oder ein einfaches Format (5m, 1h, 3d).",
|
|
1138
1146
|
"workflows.validation.invalidFormat": "Ungültiges Format",
|
|
1139
1147
|
"workflows.validation.invalidTransition": "Ungültige Übergangskonfiguration",
|
|
1140
1148
|
"workflows.validation.invalidWorkflowId": "Workflow-ID darf nur Kleinbuchstaben, Zahlen, Bindestriche und Unterstriche enthalten",
|
|
@@ -1144,6 +1152,7 @@
|
|
|
1144
1152
|
"workflows.validation.noOrphanSteps": "Alle Schritte müssen verbunden sein",
|
|
1145
1153
|
"workflows.validation.required": "Dieses Feld ist erforderlich",
|
|
1146
1154
|
"workflows.validation.uniqueId": "ID muss eindeutig sein",
|
|
1155
|
+
"workflows.validation.untilMustBeFuture": "„Warten bis“ muss ein Datum und eine Uhrzeit in der Zukunft sein.",
|
|
1147
1156
|
"workflows.validation.workflowIdFormat": "Workflow-ID darf nur Kleinbuchstaben, Zahlen, Bindestriche und Unterstriche enthalten",
|
|
1148
1157
|
"workflows.validation.workflowIdMaxLength": "Workflow-ID darf maximal 50 Zeichen lang sein",
|
|
1149
1158
|
"workflows.validation.workflowIdRequired": "Workflow-ID ist erforderlich",
|
|
@@ -93,7 +93,13 @@
|
|
|
93
93
|
"workflows.activities.types.EXECUTE_FUNCTION": "Execute Function",
|
|
94
94
|
"workflows.activities.types.SEND_EMAIL": "Send Email",
|
|
95
95
|
"workflows.activities.types.UPDATE_ENTITY": "Update Entity",
|
|
96
|
-
"workflows.activities.types.WAIT": "Wait",
|
|
96
|
+
"workflows.activities.types.WAIT": "Wait / Delay",
|
|
97
|
+
"workflows.activities.waitDuration": "Duration",
|
|
98
|
+
"workflows.activities.waitDurationDescription": "ISO 8601 duration (e.g., PT5M for 5 minutes, PT1H for 1 hour, P1D for 1 day) or simple format (5m, 1h, 3d)",
|
|
99
|
+
"workflows.activities.waitDurationPlaceholder": "PT5M",
|
|
100
|
+
"workflows.activities.waitOr": "or",
|
|
101
|
+
"workflows.activities.waitUntil": "Wait Until",
|
|
102
|
+
"workflows.activities.waitUntilDescription": "Specific date and time to wait until",
|
|
97
103
|
"workflows.backToList": "Back to workflows",
|
|
98
104
|
"workflows.backend.definitions.visual_editor.title": "Visual Workflow Editor",
|
|
99
105
|
"workflows.checkoutDemo.cart.empty.hint": "Add products from the dropdown above",
|
|
@@ -910,6 +916,7 @@
|
|
|
910
916
|
"workflows.nodeTypes.subWorkflow": "SUB-WORKFLOW",
|
|
911
917
|
"workflows.nodeTypes.userTask": "USER TASK",
|
|
912
918
|
"workflows.nodeTypes.waitForSignal": "WAIT FOR SIGNAL",
|
|
919
|
+
"workflows.nodeTypes.waitForTimer": "WAIT FOR TIMER",
|
|
913
920
|
"workflows.notifications.task.assigned.body": "You have been assigned to task \"{taskName}\" in workflow \"{workflowName}\"{dueDate, select, other { (due: {dueDate})}}",
|
|
914
921
|
"workflows.notifications.task.assigned.title": "Task Assigned",
|
|
915
922
|
"workflows.orderApproval.approveButton": "Approve",
|
|
@@ -1135,6 +1142,7 @@
|
|
|
1135
1142
|
"workflows.triggers.status.disabled": "Disabled",
|
|
1136
1143
|
"workflows.triggers.title": "Event Triggers",
|
|
1137
1144
|
"workflows.validation.descriptionMaxLength": "Description must be 5000 characters or less",
|
|
1145
|
+
"workflows.validation.invalidDuration": "Invalid duration. Use ISO 8601 (e.g., PT5M, PT1H, P1D) or simple format (5m, 1h, 3d).",
|
|
1138
1146
|
"workflows.validation.invalidFormat": "Invalid format",
|
|
1139
1147
|
"workflows.validation.invalidTransition": "Invalid transition configuration",
|
|
1140
1148
|
"workflows.validation.invalidWorkflowId": "Workflow ID can only contain lowercase letters, numbers, hyphens, and underscores",
|
|
@@ -1144,6 +1152,7 @@
|
|
|
1144
1152
|
"workflows.validation.noOrphanSteps": "All steps must be connected",
|
|
1145
1153
|
"workflows.validation.required": "This field is required",
|
|
1146
1154
|
"workflows.validation.uniqueId": "ID must be unique",
|
|
1155
|
+
"workflows.validation.untilMustBeFuture": "Wait Until must be a date and time in the future.",
|
|
1147
1156
|
"workflows.validation.workflowIdFormat": "Workflow ID must contain only lowercase letters, numbers, hyphens, and underscores",
|
|
1148
1157
|
"workflows.validation.workflowIdMaxLength": "Workflow ID must be 50 characters or less",
|
|
1149
1158
|
"workflows.validation.workflowIdRequired": "Workflow ID is required",
|
|
@@ -93,7 +93,13 @@
|
|
|
93
93
|
"workflows.activities.types.EXECUTE_FUNCTION": "Ejecutar funcion",
|
|
94
94
|
"workflows.activities.types.SEND_EMAIL": "Enviar correo electronico",
|
|
95
95
|
"workflows.activities.types.UPDATE_ENTITY": "Actualizar entidad",
|
|
96
|
-
"workflows.activities.types.WAIT": "Esperar",
|
|
96
|
+
"workflows.activities.types.WAIT": "Esperar / Retraso",
|
|
97
|
+
"workflows.activities.waitDuration": "Duración",
|
|
98
|
+
"workflows.activities.waitDurationDescription": "Duración ISO 8601 (ej. PT5M para 5 minutos, PT1H para 1 hora, P1D para 1 día) o formato simple (5m, 1h, 3d)",
|
|
99
|
+
"workflows.activities.waitDurationPlaceholder": "PT5M",
|
|
100
|
+
"workflows.activities.waitOr": "o",
|
|
101
|
+
"workflows.activities.waitUntil": "Esperar hasta",
|
|
102
|
+
"workflows.activities.waitUntilDescription": "Fecha y hora específica hasta la cual esperar",
|
|
97
103
|
"workflows.backToList": "Volver a flujos de trabajo",
|
|
98
104
|
"workflows.backend.definitions.visual_editor.title": "Editor visual de flujos de trabajo",
|
|
99
105
|
"workflows.checkoutDemo.cart.empty.hint": "Añade productos desde el menú desplegable de arriba",
|
|
@@ -910,6 +916,7 @@
|
|
|
910
916
|
"workflows.nodeTypes.subWorkflow": "SUBFLUJO DE TRABAJO",
|
|
911
917
|
"workflows.nodeTypes.userTask": "TAREA DE USUARIO",
|
|
912
918
|
"workflows.nodeTypes.waitForSignal": "ESPERAR SENAL",
|
|
919
|
+
"workflows.nodeTypes.waitForTimer": "ESPERAR TEMPORIZADOR",
|
|
913
920
|
"workflows.notifications.task.assigned.body": "Se te ha asignado la tarea \"{taskName}\" en el flujo de trabajo \"{workflowName}\"{dueDate, select, other { (vence: {dueDate})}}",
|
|
914
921
|
"workflows.notifications.task.assigned.title": "Tarea asignada",
|
|
915
922
|
"workflows.orderApproval.approveButton": "Aprobar",
|
|
@@ -1135,6 +1142,7 @@
|
|
|
1135
1142
|
"workflows.triggers.status.disabled": "Deshabilitado",
|
|
1136
1143
|
"workflows.triggers.title": "Disparadores de eventos",
|
|
1137
1144
|
"workflows.validation.descriptionMaxLength": "La descripcion debe tener 5000 caracteres o menos",
|
|
1145
|
+
"workflows.validation.invalidDuration": "Duracion no valida. Use ISO 8601 (p. ej. PT5M, PT1H, P1D) o formato simple (5m, 1h, 3d).",
|
|
1138
1146
|
"workflows.validation.invalidFormat": "Formato no valido",
|
|
1139
1147
|
"workflows.validation.invalidTransition": "Configuracion de transicion no valida",
|
|
1140
1148
|
"workflows.validation.invalidWorkflowId": "El ID del flujo de trabajo solo puede contener letras minusculas, numeros, guiones y guiones bajos",
|
|
@@ -1144,6 +1152,7 @@
|
|
|
1144
1152
|
"workflows.validation.noOrphanSteps": "Todos los pasos deben estar conectados",
|
|
1145
1153
|
"workflows.validation.required": "Este campo es obligatorio",
|
|
1146
1154
|
"workflows.validation.uniqueId": "El ID debe ser unico",
|
|
1155
|
+
"workflows.validation.untilMustBeFuture": "Esperar hasta debe ser una fecha y hora futura.",
|
|
1147
1156
|
"workflows.validation.workflowIdFormat": "El ID del flujo de trabajo solo puede contener letras minusculas, numeros, guiones y guiones bajos",
|
|
1148
1157
|
"workflows.validation.workflowIdMaxLength": "El ID del flujo de trabajo debe tener 50 caracteres o menos",
|
|
1149
1158
|
"workflows.validation.workflowIdRequired": "El ID del flujo de trabajo es obligatorio",
|
|
@@ -93,7 +93,13 @@
|
|
|
93
93
|
"workflows.activities.types.EXECUTE_FUNCTION": "Wykonaj Funkcję",
|
|
94
94
|
"workflows.activities.types.SEND_EMAIL": "Wyślij Email",
|
|
95
95
|
"workflows.activities.types.UPDATE_ENTITY": "Zaktualizuj Encję",
|
|
96
|
-
"workflows.activities.types.WAIT": "Czekaj",
|
|
96
|
+
"workflows.activities.types.WAIT": "Czekaj / Opóźnienie",
|
|
97
|
+
"workflows.activities.waitDuration": "Czas trwania",
|
|
98
|
+
"workflows.activities.waitDurationDescription": "Format ISO 8601 (np. PT5M dla 5 minut, PT1H dla 1 godziny, P1D dla 1 dnia) lub prosty format (5m, 1h, 3d)",
|
|
99
|
+
"workflows.activities.waitDurationPlaceholder": "PT5M",
|
|
100
|
+
"workflows.activities.waitOr": "lub",
|
|
101
|
+
"workflows.activities.waitUntil": "Czekaj do",
|
|
102
|
+
"workflows.activities.waitUntilDescription": "Konkretna data i godzina, do której czekać",
|
|
97
103
|
"workflows.backToList": "Powrót do przepływów",
|
|
98
104
|
"workflows.backend.definitions.visual_editor.title": "Wizualny Edytor Przepływów Pracy",
|
|
99
105
|
"workflows.checkoutDemo.cart.empty.hint": "Dodaj produkty z listy powyżej",
|
|
@@ -910,6 +916,7 @@
|
|
|
910
916
|
"workflows.nodeTypes.subWorkflow": "PODPRZEPŁYW",
|
|
911
917
|
"workflows.nodeTypes.userTask": "ZADANIE UŻYTKOWNIKA",
|
|
912
918
|
"workflows.nodeTypes.waitForSignal": "CZEKAJ NA SYGNAŁ",
|
|
919
|
+
"workflows.nodeTypes.waitForTimer": "CZEKAJ NA TIMER",
|
|
913
920
|
"workflows.notifications.task.assigned.body": "Przypisano Ci zadanie \"{taskName}\" w przepływie \"{workflowName}\"{dueDate, select, other { (termin: {dueDate})}}",
|
|
914
921
|
"workflows.notifications.task.assigned.title": "Przypisano Zadanie",
|
|
915
922
|
"workflows.orderApproval.approveButton": "Zatwierdź",
|
|
@@ -1135,6 +1142,7 @@
|
|
|
1135
1142
|
"workflows.triggers.status.disabled": "Wyłączony",
|
|
1136
1143
|
"workflows.triggers.title": "Wyzwalacze Zdarzeń",
|
|
1137
1144
|
"workflows.validation.descriptionMaxLength": "Opis musi mieć 5000 znaków lub mniej",
|
|
1145
|
+
"workflows.validation.invalidDuration": "Nieprawidłowy czas trwania. Użyj formatu ISO 8601 (np. PT5M, PT1H, P1D) lub uproszczonego (5m, 1h, 3d).",
|
|
1138
1146
|
"workflows.validation.invalidFormat": "Nieprawidłowy format",
|
|
1139
1147
|
"workflows.validation.invalidTransition": "Nieprawidłowa konfiguracja przejścia",
|
|
1140
1148
|
"workflows.validation.invalidWorkflowId": "ID przepływu może zawierać tylko małe litery, cyfry, myślniki i podkreślenia",
|
|
@@ -1144,6 +1152,7 @@
|
|
|
1144
1152
|
"workflows.validation.noOrphanSteps": "Wszystkie kroki muszą być połączone",
|
|
1145
1153
|
"workflows.validation.required": "To pole jest wymagane",
|
|
1146
1154
|
"workflows.validation.uniqueId": "ID musi być unikalne",
|
|
1155
|
+
"workflows.validation.untilMustBeFuture": "„Czekaj do” musi być datą i godziną w przyszłości.",
|
|
1147
1156
|
"workflows.validation.workflowIdFormat": "ID przepływu może zawierać tylko małe litery, cyfry, myślniki i podkreślenia",
|
|
1148
1157
|
"workflows.validation.workflowIdMaxLength": "ID przepływu musi mieć 50 znaków lub mniej",
|
|
1149
1158
|
"workflows.validation.workflowIdRequired": "ID przepływu jest wymagane",
|
|
@@ -25,6 +25,7 @@ import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'
|
|
|
25
25
|
import { callWebhookConfigSchema } from '../data/validators'
|
|
26
26
|
import { WorkflowActivityJob, WORKFLOW_ACTIVITIES_QUEUE_NAME } from './activity-queue-types'
|
|
27
27
|
import { logWorkflowEvent } from './event-logger'
|
|
28
|
+
import { parseDuration } from './duration'
|
|
28
29
|
|
|
29
30
|
export { isPrivateUrl } from '@open-mercato/shared/lib/network'
|
|
30
31
|
|
|
@@ -54,6 +55,7 @@ export type ActivityType =
|
|
|
54
55
|
| 'UPDATE_ENTITY'
|
|
55
56
|
| 'CALL_WEBHOOK'
|
|
56
57
|
| 'EXECUTE_FUNCTION'
|
|
58
|
+
| 'WAIT'
|
|
57
59
|
|
|
58
60
|
export interface ActivityDefinition {
|
|
59
61
|
activityId: string // Unique identifier for activity
|
|
@@ -164,9 +166,12 @@ export async function enqueueActivity(
|
|
|
164
166
|
userId: context.userId,
|
|
165
167
|
}
|
|
166
168
|
|
|
167
|
-
// Enqueue to queue
|
|
169
|
+
// Enqueue to queue (WAIT activities use delayMs for the actual delay)
|
|
168
170
|
const queue = getActivityQueue()
|
|
169
|
-
const
|
|
171
|
+
const enqueueOptions = activity.activityType === 'WAIT' && (interpolatedConfig.duration || interpolatedConfig.until)
|
|
172
|
+
? { delayMs: calculateWaitDelayMs(interpolatedConfig) }
|
|
173
|
+
: undefined
|
|
174
|
+
const jobId = await queue.enqueue(job, enqueueOptions)
|
|
170
175
|
|
|
171
176
|
// Log event
|
|
172
177
|
await logWorkflowEvent(em, {
|
|
@@ -187,6 +192,41 @@ export async function enqueueActivity(
|
|
|
187
192
|
return jobId
|
|
188
193
|
}
|
|
189
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Enqueue a delayed timer job for a WAIT_FOR_TIMER step.
|
|
197
|
+
*
|
|
198
|
+
* The activity worker handles `kind: 'timer'` jobs by calling
|
|
199
|
+
* `timerHandler.fireTimer`, which resumes the paused workflow instance.
|
|
200
|
+
*/
|
|
201
|
+
export async function enqueueTimerJob(params: {
|
|
202
|
+
workflowInstanceId: string
|
|
203
|
+
stepInstanceId: string
|
|
204
|
+
tenantId: string
|
|
205
|
+
organizationId: string
|
|
206
|
+
userId?: string
|
|
207
|
+
fireAt: string
|
|
208
|
+
delayMs: number
|
|
209
|
+
}): Promise<string> {
|
|
210
|
+
const { workflowInstanceId, stepInstanceId, tenantId, organizationId, userId, fireAt, delayMs } =
|
|
211
|
+
params
|
|
212
|
+
|
|
213
|
+
const queue = getActivityQueue()
|
|
214
|
+
const jobId = await queue.enqueue(
|
|
215
|
+
{
|
|
216
|
+
kind: 'timer',
|
|
217
|
+
workflowInstanceId,
|
|
218
|
+
stepInstanceId,
|
|
219
|
+
tenantId,
|
|
220
|
+
organizationId,
|
|
221
|
+
userId,
|
|
222
|
+
fireAt,
|
|
223
|
+
},
|
|
224
|
+
{ delayMs: delayMs > 0 ? delayMs : undefined }
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
return jobId
|
|
228
|
+
}
|
|
229
|
+
|
|
190
230
|
// ============================================================================
|
|
191
231
|
// Main Activity Execution Functions
|
|
192
232
|
// ============================================================================
|
|
@@ -377,6 +417,9 @@ async function executeActivityByType(
|
|
|
377
417
|
case 'EXECUTE_FUNCTION':
|
|
378
418
|
return await executeFunction(interpolatedConfig, context, container)
|
|
379
419
|
|
|
420
|
+
case 'WAIT':
|
|
421
|
+
return await executeWait(interpolatedConfig)
|
|
422
|
+
|
|
380
423
|
default:
|
|
381
424
|
throw new ActivityExecutionError(
|
|
382
425
|
`Unknown activity type: ${activity.activityType}`,
|
|
@@ -750,6 +793,47 @@ export async function executeFunction(
|
|
|
750
793
|
}
|
|
751
794
|
}
|
|
752
795
|
|
|
796
|
+
/**
|
|
797
|
+
* Calculate delay in milliseconds from a WAIT activity config.
|
|
798
|
+
* Supports either `duration` (relative, e.g. "PT5M") or `until` (absolute ISO 8601 datetime).
|
|
799
|
+
*/
|
|
800
|
+
function calculateWaitDelayMs(config: { duration?: string; until?: string }): number {
|
|
801
|
+
if (config.until) {
|
|
802
|
+
const targetDate = new Date(config.until)
|
|
803
|
+
if (isNaN(targetDate.getTime())) {
|
|
804
|
+
throw new Error(`WAIT activity: invalid "until" datetime: ${config.until}`)
|
|
805
|
+
}
|
|
806
|
+
const delayMs = targetDate.getTime() - Date.now()
|
|
807
|
+
return Math.max(0, delayMs)
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (config.duration) {
|
|
811
|
+
return parseDuration(config.duration)
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
throw new Error('WAIT activity requires "duration" (e.g., "PT5M", "1h") or "until" (ISO 8601 datetime)')
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* WAIT activity handler
|
|
819
|
+
*
|
|
820
|
+
* Delays workflow execution for a configured duration or until a specific datetime.
|
|
821
|
+
* - `duration`: relative delay (e.g. "PT5M", "1h", "30s")
|
|
822
|
+
* - `until`: absolute datetime (e.g. "2026-04-15T10:00:00Z")
|
|
823
|
+
* - Sync mode: blocks via sleep (suitable for short delays)
|
|
824
|
+
* - Async mode: delay is handled by the queue's delayMs option;
|
|
825
|
+
* this handler returns immediately when called from the worker
|
|
826
|
+
*/
|
|
827
|
+
async function executeWait(config: any): Promise<any> {
|
|
828
|
+
const durationMs = calculateWaitDelayMs(config)
|
|
829
|
+
|
|
830
|
+
// In sync mode, actually sleep for the duration
|
|
831
|
+
// In async mode (called from worker), the delay already happened via queue scheduling
|
|
832
|
+
await sleep(durationMs)
|
|
833
|
+
|
|
834
|
+
return { waited: true, durationMs }
|
|
835
|
+
}
|
|
836
|
+
|
|
753
837
|
/**
|
|
754
838
|
* CALL_API activity handler
|
|
755
839
|
*
|
|
@@ -2,25 +2,31 @@
|
|
|
2
2
|
* Workflow Activity Queue Types
|
|
3
3
|
*
|
|
4
4
|
* Type definitions for async activity execution via queue system.
|
|
5
|
+
* Jobs are discriminated by the optional `kind` field:
|
|
6
|
+
* - `'activity'` (default, back-compat): background execution of a workflow activity
|
|
7
|
+
* - `'timer'`: delayed fire-timer job for a WAIT_FOR_TIMER step
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
|
-
export interface
|
|
8
|
-
// Execution context
|
|
10
|
+
export interface WorkflowActivityJobBase {
|
|
9
11
|
workflowInstanceId: string
|
|
10
12
|
stepInstanceId?: string
|
|
13
|
+
tenantId: string
|
|
14
|
+
organizationId: string
|
|
15
|
+
userId?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface WorkflowActivityJobActivity extends WorkflowActivityJobBase {
|
|
19
|
+
kind?: 'activity'
|
|
11
20
|
transitionId?: string
|
|
12
21
|
|
|
13
|
-
// Activity definition
|
|
14
22
|
activityId: string
|
|
15
23
|
activityName: string
|
|
16
24
|
activityType: string
|
|
17
25
|
activityConfig: any
|
|
18
26
|
|
|
19
|
-
// Workflow context (for execution)
|
|
20
27
|
workflowContext: Record<string, any>
|
|
21
28
|
stepContext?: Record<string, any>
|
|
22
29
|
|
|
23
|
-
// Retry & timeout config
|
|
24
30
|
retryPolicy?: {
|
|
25
31
|
maxAttempts: number
|
|
26
32
|
initialIntervalMs: number
|
|
@@ -28,13 +34,14 @@ export interface WorkflowActivityJob {
|
|
|
28
34
|
maxIntervalMs: number
|
|
29
35
|
}
|
|
30
36
|
timeoutMs?: number
|
|
37
|
+
}
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Metadata
|
|
37
|
-
userId?: string
|
|
39
|
+
export interface WorkflowActivityJobTimer extends WorkflowActivityJobBase {
|
|
40
|
+
kind: 'timer'
|
|
41
|
+
stepInstanceId: string
|
|
42
|
+
fireAt: string // ISO 8601 timestamp for when the timer should fire
|
|
38
43
|
}
|
|
39
44
|
|
|
45
|
+
export type WorkflowActivityJob = WorkflowActivityJobActivity | WorkflowActivityJobTimer
|
|
46
|
+
|
|
40
47
|
export const WORKFLOW_ACTIVITIES_QUEUE_NAME = 'workflow-activities'
|
|
@@ -34,6 +34,31 @@ export function createActivityWorkerHandler(
|
|
|
34
34
|
const { payload } = job
|
|
35
35
|
const startTime = Date.now()
|
|
36
36
|
|
|
37
|
+
// Timer jobs (kind: 'timer') are a distinct flow — they resume a paused
|
|
38
|
+
// workflow instance rather than executing an activity. Handle them first.
|
|
39
|
+
if (payload.kind === 'timer') {
|
|
40
|
+
console.log(
|
|
41
|
+
`[ActivityWorker] Firing timer for instance ${payload.workflowInstanceId} (job ${ctx.jobId})`
|
|
42
|
+
)
|
|
43
|
+
try {
|
|
44
|
+
const { fireTimer } = await import('./timer-handler')
|
|
45
|
+
await fireTimer(em, container, {
|
|
46
|
+
instanceId: payload.workflowInstanceId,
|
|
47
|
+
stepInstanceId: payload.stepInstanceId,
|
|
48
|
+
tenantId: payload.tenantId,
|
|
49
|
+
organizationId: payload.organizationId,
|
|
50
|
+
userId: payload.userId,
|
|
51
|
+
})
|
|
52
|
+
} catch (error: any) {
|
|
53
|
+
console.error(
|
|
54
|
+
`[ActivityWorker] Failed to fire timer for instance ${payload.workflowInstanceId}:`,
|
|
55
|
+
error.message
|
|
56
|
+
)
|
|
57
|
+
throw error
|
|
58
|
+
}
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
37
62
|
console.log(
|
|
38
63
|
`[ActivityWorker] Processing activity ${payload.activityId} (job ${ctx.jobId})`
|
|
39
64
|
)
|
|
@@ -79,6 +104,10 @@ export function createActivityWorkerHandler(
|
|
|
79
104
|
return await executeCallWebhook(payload.activityConfig, activityContext)
|
|
80
105
|
case 'EXECUTE_FUNCTION':
|
|
81
106
|
return await executeFunction(payload.activityConfig, activityContext, container)
|
|
107
|
+
case 'WAIT':
|
|
108
|
+
// Delay already applied by the queue via delayMs; the worker
|
|
109
|
+
// only needs to record completion so the workflow can resume.
|
|
110
|
+
return { waited: true, async: true }
|
|
82
111
|
default:
|
|
83
112
|
throw new Error(`Unsupported activity type: ${payload.activityType}`)
|
|
84
113
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse ISO 8601 duration to milliseconds
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - ISO 8601: PT5M (5 minutes), PT1H (1 hour), P1D (1 day), P3D (3 days)
|
|
6
|
+
* - Simple formats: 5m, 1h, 3d, 30s
|
|
7
|
+
*
|
|
8
|
+
* @param duration - Duration string
|
|
9
|
+
* @returns Duration in milliseconds
|
|
10
|
+
*/
|
|
11
|
+
export function parseDuration(duration: string): number {
|
|
12
|
+
// Try ISO 8601 format first: P[n]DT[n]H[n]M[n]S
|
|
13
|
+
const iso8601Regex = /P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?/
|
|
14
|
+
const iso8601Match = duration.match(iso8601Regex)
|
|
15
|
+
|
|
16
|
+
if (iso8601Match && iso8601Match[0] === duration) {
|
|
17
|
+
const days = parseInt(iso8601Match[1] || '0')
|
|
18
|
+
const hours = parseInt(iso8601Match[2] || '0')
|
|
19
|
+
const minutes = parseInt(iso8601Match[3] || '0')
|
|
20
|
+
const seconds = parseInt(iso8601Match[4] || '0')
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
days * 24 * 60 * 60 * 1000 +
|
|
24
|
+
hours * 60 * 60 * 1000 +
|
|
25
|
+
minutes * 60 * 1000 +
|
|
26
|
+
seconds * 1000
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Try simple format: 1d, 5h, 30m, 45s
|
|
31
|
+
const simpleRegex = /^(\d+)(d|h|m|s)$/
|
|
32
|
+
const simpleMatch = duration.match(simpleRegex)
|
|
33
|
+
|
|
34
|
+
if (simpleMatch) {
|
|
35
|
+
const value = parseInt(simpleMatch[1])
|
|
36
|
+
const unit = simpleMatch[2]
|
|
37
|
+
|
|
38
|
+
switch (unit) {
|
|
39
|
+
case 'd':
|
|
40
|
+
return value * 24 * 60 * 60 * 1000
|
|
41
|
+
case 'h':
|
|
42
|
+
return value * 60 * 60 * 1000
|
|
43
|
+
case 'm':
|
|
44
|
+
return value * 60 * 1000
|
|
45
|
+
case 's':
|
|
46
|
+
return value * 1000
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
throw new Error(`Invalid duration format: ${duration}`)
|
|
51
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
type ZodIssueLite = {
|
|
2
|
+
path?: Array<string | number>
|
|
3
|
+
message?: string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
type ApiErrorBody = {
|
|
7
|
+
error?: string
|
|
8
|
+
details?: ZodIssueLite[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Format an API validation error body into a user-readable message.
|
|
13
|
+
*
|
|
14
|
+
* The workflow definitions API returns `{ error: 'Validation failed', details: ZodIssue[] }`
|
|
15
|
+
* for schema failures. The generic `error` string is useless to the user — the actionable
|
|
16
|
+
* information lives in `details[0].path` and `details[0].message`. This helper mirrors the
|
|
17
|
+
* visual editor's `Schema error: <path> - <message>` format so both editors surface the
|
|
18
|
+
* same diagnostic.
|
|
19
|
+
*/
|
|
20
|
+
export function formatWorkflowValidationError(
|
|
21
|
+
body: ApiErrorBody | null | undefined,
|
|
22
|
+
fallback: string,
|
|
23
|
+
): string {
|
|
24
|
+
const issue = body?.details?.[0]
|
|
25
|
+
if (issue?.message) {
|
|
26
|
+
const path = (issue.path ?? []).join('.')
|
|
27
|
+
return path ? `${path} - ${issue.message}` : issue.message
|
|
28
|
+
}
|
|
29
|
+
return body?.error || fallback
|
|
30
|
+
}
|
|
@@ -452,6 +452,7 @@ function mapNodeTypeToStepType(nodeType: string): string {
|
|
|
452
452
|
automated: 'AUTOMATED',
|
|
453
453
|
decision: 'DECISION',
|
|
454
454
|
waitForSignal: 'WAIT_FOR_SIGNAL',
|
|
455
|
+
waitForTimer: 'WAIT_FOR_TIMER',
|
|
455
456
|
}
|
|
456
457
|
return mapping[nodeType] || 'AUTOMATED'
|
|
457
458
|
}
|
|
@@ -467,6 +468,7 @@ function mapStepTypeToNodeType(stepType: string): string {
|
|
|
467
468
|
AUTOMATED: 'automated',
|
|
468
469
|
DECISION: 'decision',
|
|
469
470
|
WAIT_FOR_SIGNAL: 'waitForSignal',
|
|
471
|
+
WAIT_FOR_TIMER: 'waitForTimer',
|
|
470
472
|
}
|
|
471
473
|
return mapping[stepType] || 'automated'
|
|
472
474
|
}
|
|
@@ -482,6 +484,7 @@ function getBadgeForNodeType(nodeType: string): string {
|
|
|
482
484
|
automated: 'Automated',
|
|
483
485
|
decision: 'Decision',
|
|
484
486
|
waitForSignal: 'Wait for Signal',
|
|
487
|
+
waitForTimer: 'Wait for Timer',
|
|
485
488
|
}
|
|
486
489
|
return badges[nodeType] || 'Task'
|
|
487
490
|
}
|