@open-mercato/core 0.6.3-develop.3876.1.d40fe4ec2d → 0.6.3-develop.3894.1.352abf4240
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/backend/auth/profile/page.js +1 -1
- package/dist/modules/auth/backend/auth/profile/page.js.map +2 -2
- package/dist/modules/auth/backend/profile/change-password/page.js +1 -1
- package/dist/modules/auth/backend/profile/change-password/page.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +1 -1
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/create/page.js +6 -1
- package/dist/modules/auth/backend/users/create/page.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/catalog/backend/catalog/products/[id]/page.js +8 -1
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +3 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js +3 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js.map +2 -2
- package/dist/modules/configs/cli.js +27 -14
- package/dist/modules/configs/cli.js.map +2 -2
- 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/resources/backend/resources/resource-types/[id]/edit/page.js +1 -1
- package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +1 -1
- package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
- package/dist/modules/sales/components/channels/ChannelOfferForm.js +1 -1
- package/dist/modules/sales/components/channels/ChannelOfferForm.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/backend/auth/profile/page.tsx +1 -1
- package/src/modules/auth/backend/profile/change-password/page.tsx +1 -1
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +1 -1
- package/src/modules/auth/backend/users/create/page.tsx +6 -1
- package/src/modules/auth/di.ts +26 -3
- package/src/modules/auth/services/rbacDefaultCache.ts +145 -0
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +8 -1
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +3 -2
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/create/page.tsx +3 -2
- package/src/modules/configs/cli.ts +34 -13
- 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/resources/backend/resources/resource-types/[id]/edit/page.tsx +1 -1
- package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +1 -1
- package/src/modules/sales/components/channels/ChannelOfferForm.tsx +1 -1
- 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
|
@@ -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
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CircleDot, CircleStop, User, Zap, Workflow, Clock, LucideIcon } from 'lucide-react'
|
|
1
|
+
import { CircleDot, CircleStop, User, Zap, Workflow, Clock, Timer, LucideIcon } from 'lucide-react'
|
|
2
2
|
|
|
3
|
-
export type NodeType = 'start' | 'end' | 'userTask' | 'automated' | 'subWorkflow' | 'waitForSignal'
|
|
3
|
+
export type NodeType = 'start' | 'end' | 'userTask' | 'automated' | 'subWorkflow' | 'waitForSignal' | 'waitForTimer'
|
|
4
4
|
|
|
5
5
|
export const NODE_TYPE_ICONS: Record<NodeType, LucideIcon> = {
|
|
6
6
|
start: CircleDot,
|
|
@@ -9,6 +9,7 @@ export const NODE_TYPE_ICONS: Record<NodeType, LucideIcon> = {
|
|
|
9
9
|
automated: Zap,
|
|
10
10
|
subWorkflow: Workflow,
|
|
11
11
|
waitForSignal: Clock,
|
|
12
|
+
waitForTimer: Timer,
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export const NODE_TYPE_COLORS: Record<NodeType, string> = {
|
|
@@ -18,6 +19,7 @@ export const NODE_TYPE_COLORS: Record<NodeType, string> = {
|
|
|
18
19
|
automated: 'text-amber-500',
|
|
19
20
|
subWorkflow: 'text-purple-500',
|
|
20
21
|
waitForSignal: 'text-purple-500',
|
|
22
|
+
waitForTimer: 'text-cyan-500',
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export const NODE_TYPE_LABELS: Record<NodeType, { title: string; description: string }> = {
|
|
@@ -27,6 +29,7 @@ export const NODE_TYPE_LABELS: Record<NodeType, { title: string; description: st
|
|
|
27
29
|
automated: { title: 'AUTOMATED', description: 'System task' },
|
|
28
30
|
subWorkflow: { title: 'SUB-WORKFLOW', description: 'Invoke workflow' },
|
|
29
31
|
waitForSignal: { title: 'WAIT FOR SIGNAL', description: 'Pause for external event' },
|
|
32
|
+
waitForTimer: { title: 'WAIT FOR TIMER', description: 'Pause for a duration' },
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
const STEP_TYPE_TO_NODE_TYPE: Record<string, NodeType> = {
|
|
@@ -36,6 +39,7 @@ const STEP_TYPE_TO_NODE_TYPE: Record<string, NodeType> = {
|
|
|
36
39
|
AUTOMATED: 'automated',
|
|
37
40
|
SUB_WORKFLOW: 'subWorkflow',
|
|
38
41
|
WAIT_FOR_SIGNAL: 'waitForSignal',
|
|
42
|
+
WAIT_FOR_TIMER: 'waitForTimer',
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
export function stepTypeToNodeType(stepType: string): NodeType {
|
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { EntityManager } from '@mikro-orm/core'
|
|
8
|
+
import type { EntityManager as PostgreSqlEntityManager } from '@mikro-orm/postgresql'
|
|
8
9
|
import type { AwilixContainer } from 'awilix'
|
|
10
|
+
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
9
11
|
import { WorkflowInstance, WorkflowDefinition, StepInstance } from '../data/entities'
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import * as
|
|
13
|
-
import * as
|
|
12
|
+
import type * as eventLoggerModule from './event-logger'
|
|
13
|
+
import type * as stepHandlerModule from './step-handler'
|
|
14
|
+
import type * as transitionHandlerModule from './transition-handler'
|
|
15
|
+
import type * as workflowExecutorModule from './workflow-executor'
|
|
14
16
|
|
|
15
17
|
export interface SendSignalOptions {
|
|
16
18
|
/**
|
|
@@ -61,12 +63,23 @@ export async function sendSignal(
|
|
|
61
63
|
): Promise<void> {
|
|
62
64
|
const { instanceId, signalName, payload, userId, tenantId, organizationId } = options
|
|
63
65
|
|
|
66
|
+
const eventLogger = container.resolve<typeof eventLoggerModule>('eventLogger')
|
|
67
|
+
const stepHandler = container.resolve<typeof stepHandlerModule>('stepHandler')
|
|
68
|
+
const transitionHandler = container.resolve<typeof transitionHandlerModule>('transitionHandler')
|
|
69
|
+
const workflowExecutor = container.resolve<typeof workflowExecutorModule>('workflowExecutor')
|
|
70
|
+
|
|
64
71
|
// Fetch workflow instance
|
|
65
|
-
const instance = await
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
const instance = await findOneWithDecryption(
|
|
73
|
+
em as PostgreSqlEntityManager,
|
|
74
|
+
WorkflowInstance,
|
|
75
|
+
{
|
|
76
|
+
id: instanceId,
|
|
77
|
+
tenantId,
|
|
78
|
+
organizationId,
|
|
79
|
+
},
|
|
80
|
+
undefined,
|
|
81
|
+
{ tenantId, organizationId },
|
|
82
|
+
)
|
|
70
83
|
|
|
71
84
|
if (!instance) {
|
|
72
85
|
throw new SignalError(
|
|
@@ -85,8 +98,19 @@ export async function sendSignal(
|
|
|
85
98
|
)
|
|
86
99
|
}
|
|
87
100
|
|
|
88
|
-
// Load workflow definition to check current step
|
|
89
|
-
const definition = await
|
|
101
|
+
// Load workflow definition with tenant/org scope to check current step
|
|
102
|
+
const definition = await findOneWithDecryption(
|
|
103
|
+
em as PostgreSqlEntityManager,
|
|
104
|
+
WorkflowDefinition,
|
|
105
|
+
{
|
|
106
|
+
id: instance.definitionId,
|
|
107
|
+
tenantId: instance.tenantId,
|
|
108
|
+
organizationId: instance.organizationId,
|
|
109
|
+
deletedAt: null,
|
|
110
|
+
},
|
|
111
|
+
undefined,
|
|
112
|
+
{ tenantId: instance.tenantId, organizationId: instance.organizationId },
|
|
113
|
+
)
|
|
90
114
|
if (!definition) {
|
|
91
115
|
throw new SignalError(
|
|
92
116
|
'Workflow definition not found',
|
|
@@ -133,7 +157,7 @@ export async function sendSignal(
|
|
|
133
157
|
instance.updatedAt = now
|
|
134
158
|
|
|
135
159
|
// Log signal received event
|
|
136
|
-
await logWorkflowEvent(em, {
|
|
160
|
+
await eventLogger.logWorkflowEvent(em, {
|
|
137
161
|
workflowInstanceId: instance.id,
|
|
138
162
|
eventType: 'SIGNAL_RECEIVED',
|
|
139
163
|
eventData: {
|
|
@@ -146,11 +170,19 @@ export async function sendSignal(
|
|
|
146
170
|
})
|
|
147
171
|
|
|
148
172
|
// Find active step instance and exit it
|
|
149
|
-
const stepInstance = await
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
173
|
+
const stepInstance = await findOneWithDecryption(
|
|
174
|
+
em as PostgreSqlEntityManager,
|
|
175
|
+
StepInstance,
|
|
176
|
+
{
|
|
177
|
+
workflowInstanceId: instance.id,
|
|
178
|
+
stepId: instance.currentStepId,
|
|
179
|
+
status: 'ACTIVE',
|
|
180
|
+
tenantId: instance.tenantId,
|
|
181
|
+
organizationId: instance.organizationId,
|
|
182
|
+
},
|
|
183
|
+
undefined,
|
|
184
|
+
{ tenantId: instance.tenantId, organizationId: instance.organizationId },
|
|
185
|
+
)
|
|
154
186
|
|
|
155
187
|
if (stepInstance) {
|
|
156
188
|
await stepHandler.exitStep(em, stepInstance, {
|
|
@@ -212,7 +244,7 @@ export async function sendSignal(
|
|
|
212
244
|
}
|
|
213
245
|
|
|
214
246
|
// Resume workflow execution
|
|
215
|
-
await executeWorkflow(em, container, instance.id, { userId })
|
|
247
|
+
await workflowExecutor.executeWorkflow(em, container, instance.id, { userId })
|
|
216
248
|
}
|
|
217
249
|
|
|
218
250
|
/**
|
|
@@ -226,12 +258,18 @@ export async function sendSignalByCorrelationKey(
|
|
|
226
258
|
const { correlationKey, signalName, payload, userId, tenantId, organizationId } = options
|
|
227
259
|
|
|
228
260
|
// Find all paused instances with this correlation key
|
|
229
|
-
const instances = await
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
261
|
+
const instances = await findWithDecryption(
|
|
262
|
+
em as PostgreSqlEntityManager,
|
|
263
|
+
WorkflowInstance,
|
|
264
|
+
{
|
|
265
|
+
correlationKey,
|
|
266
|
+
status: 'PAUSED',
|
|
267
|
+
tenantId,
|
|
268
|
+
organizationId,
|
|
269
|
+
},
|
|
270
|
+
undefined,
|
|
271
|
+
{ tenantId, organizationId },
|
|
272
|
+
)
|
|
235
273
|
|
|
236
274
|
let signalsProcessed = 0
|
|
237
275
|
|
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
type StepInstanceStatus,
|
|
20
20
|
type WorkflowStepType,
|
|
21
21
|
} from '../data/entities'
|
|
22
|
+
import { parseDuration } from './duration'
|
|
23
|
+
import { logWorkflowEvent } from './event-logger'
|
|
22
24
|
|
|
23
25
|
// ============================================================================
|
|
24
26
|
// Types and Interfaces
|
|
@@ -333,9 +335,11 @@ async function executeStepByType(
|
|
|
333
335
|
case 'WAIT_FOR_SIGNAL':
|
|
334
336
|
return await handleWaitForSignalStep(em, instance, stepInstance, stepDef, context)
|
|
335
337
|
|
|
338
|
+
case 'WAIT_FOR_TIMER':
|
|
339
|
+
return await handleWaitForTimerStep(em, instance, stepInstance, stepDef, context)
|
|
340
|
+
|
|
336
341
|
case 'PARALLEL_FORK':
|
|
337
342
|
case 'PARALLEL_JOIN':
|
|
338
|
-
case 'WAIT_FOR_TIMER':
|
|
339
343
|
// These will be implemented in later phases
|
|
340
344
|
throw new StepExecutionError(
|
|
341
345
|
`Step type not yet implemented: ${stepType}`,
|
|
@@ -756,66 +760,119 @@ async function handleWaitForSignalStep(
|
|
|
756
760
|
}
|
|
757
761
|
}
|
|
758
762
|
|
|
759
|
-
// ============================================================================
|
|
760
|
-
// Helper Functions
|
|
761
|
-
// ============================================================================
|
|
762
|
-
|
|
763
763
|
/**
|
|
764
|
-
*
|
|
765
|
-
*
|
|
766
|
-
* Supports:
|
|
767
|
-
* - ISO 8601: PT5M (5 minutes), PT1H (1 hour), P1D (1 day), P3D (3 days)
|
|
768
|
-
* - Simple formats: 5m, 1h, 3d, 30s
|
|
764
|
+
* Handle WAIT_FOR_TIMER step - pause workflow until a timer fires.
|
|
769
765
|
*
|
|
770
|
-
*
|
|
771
|
-
*
|
|
766
|
+
* Reads `duration` (relative, e.g. "PT5M") or `until` (ISO 8601 datetime) from
|
|
767
|
+
* `stepDef.config` (preferred — matches StepsEditor) or `stepDef.timerConfig`.
|
|
768
|
+
* Enqueues a delayed timer job on the workflow-activities queue; when the job
|
|
769
|
+
* is processed by the activity worker, it calls `timerHandler.fireTimer` to
|
|
770
|
+
* resume the workflow.
|
|
772
771
|
*/
|
|
773
|
-
function
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
772
|
+
async function handleWaitForTimerStep(
|
|
773
|
+
em: EntityManager,
|
|
774
|
+
instance: WorkflowInstance,
|
|
775
|
+
stepInstance: StepInstance,
|
|
776
|
+
stepDef: any,
|
|
777
|
+
context: StepExecutionContext
|
|
778
|
+
): Promise<StepExecutionResult> {
|
|
779
|
+
const timerConfig = stepDef.config || stepDef.timerConfig || {}
|
|
780
|
+
const duration: string | undefined = timerConfig.duration
|
|
781
|
+
const until: string | undefined = timerConfig.until
|
|
782
|
+
|
|
783
|
+
if (!duration && !until) {
|
|
784
|
+
throw new StepExecutionError(
|
|
785
|
+
'WAIT_FOR_TIMER requires either "duration" (e.g., "PT5M") or "until" (ISO 8601 datetime)',
|
|
786
|
+
'TIMER_CONFIG_MISSING',
|
|
787
|
+
{ stepId: stepDef.stepId }
|
|
789
788
|
)
|
|
790
789
|
}
|
|
791
790
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
case 'd':
|
|
802
|
-
return value * 24 * 60 * 60 * 1000
|
|
803
|
-
case 'h':
|
|
804
|
-
return value * 60 * 60 * 1000
|
|
805
|
-
case 'm':
|
|
806
|
-
return value * 60 * 1000
|
|
807
|
-
case 's':
|
|
808
|
-
return value * 1000
|
|
791
|
+
let fireAtMs: number
|
|
792
|
+
if (until) {
|
|
793
|
+
const targetDate = new Date(until)
|
|
794
|
+
if (isNaN(targetDate.getTime())) {
|
|
795
|
+
throw new StepExecutionError(
|
|
796
|
+
`WAIT_FOR_TIMER invalid "until" datetime: ${until}`,
|
|
797
|
+
'TIMER_CONFIG_INVALID',
|
|
798
|
+
{ until }
|
|
799
|
+
)
|
|
809
800
|
}
|
|
801
|
+
fireAtMs = targetDate.getTime()
|
|
802
|
+
} else {
|
|
803
|
+
fireAtMs = Date.now() + parseDuration(duration as string)
|
|
810
804
|
}
|
|
811
805
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
)
|
|
806
|
+
const delayMs = fireAtMs - Date.now()
|
|
807
|
+
const fireAt = new Date(fireAtMs)
|
|
808
|
+
|
|
809
|
+
// Immediate-fire path: skip the queue round-trip if the timer is in the past
|
|
810
|
+
if (delayMs <= 0) {
|
|
811
|
+
return {
|
|
812
|
+
status: 'COMPLETED',
|
|
813
|
+
outputData: {
|
|
814
|
+
stepType: 'WAIT_FOR_TIMER',
|
|
815
|
+
timerFiredImmediately: true,
|
|
816
|
+
fireAt,
|
|
817
|
+
duration,
|
|
818
|
+
until,
|
|
819
|
+
},
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const now = new Date()
|
|
824
|
+
|
|
825
|
+
// Enqueue delayed timer job via the shared activity queue.
|
|
826
|
+
// Imported here to avoid a top-level cycle between step-handler and activity-executor.
|
|
827
|
+
const { enqueueTimerJob } = await import('./activity-executor')
|
|
828
|
+
const jobId = await enqueueTimerJob({
|
|
829
|
+
workflowInstanceId: instance.id,
|
|
830
|
+
stepInstanceId: stepInstance.id,
|
|
831
|
+
tenantId: instance.tenantId,
|
|
832
|
+
organizationId: instance.organizationId,
|
|
833
|
+
userId: context.userId,
|
|
834
|
+
fireAt: fireAt.toISOString(),
|
|
835
|
+
delayMs,
|
|
836
|
+
})
|
|
837
|
+
|
|
838
|
+
await logWorkflowEvent(em, {
|
|
839
|
+
workflowInstanceId: instance.id,
|
|
840
|
+
stepInstanceId: stepInstance.id,
|
|
841
|
+
eventType: 'TIMER_AWAITING',
|
|
842
|
+
eventData: {
|
|
843
|
+
fireAt: fireAt.toISOString(),
|
|
844
|
+
duration: duration || null,
|
|
845
|
+
until: until || null,
|
|
846
|
+
jobId,
|
|
847
|
+
},
|
|
848
|
+
userId: context.userId,
|
|
849
|
+
tenantId: instance.tenantId,
|
|
850
|
+
organizationId: instance.organizationId,
|
|
851
|
+
})
|
|
852
|
+
|
|
853
|
+
instance.status = 'PAUSED'
|
|
854
|
+
instance.pausedAt = now
|
|
855
|
+
instance.updatedAt = now
|
|
856
|
+
await em.flush()
|
|
857
|
+
|
|
858
|
+
return {
|
|
859
|
+
status: 'WAITING',
|
|
860
|
+
waitReason: 'TIMER',
|
|
861
|
+
outputData: {
|
|
862
|
+
fireAt,
|
|
863
|
+
duration,
|
|
864
|
+
until,
|
|
865
|
+
jobId,
|
|
866
|
+
},
|
|
867
|
+
}
|
|
817
868
|
}
|
|
818
869
|
|
|
870
|
+
// ============================================================================
|
|
871
|
+
// Helper Functions
|
|
872
|
+
// ============================================================================
|
|
873
|
+
|
|
874
|
+
// parseDuration is imported from ./duration
|
|
875
|
+
|
|
819
876
|
/**
|
|
820
877
|
* Log step-related event to event sourcing table
|
|
821
878
|
*/
|