@open-mercato/core 0.6.2-develop.3400.2.d948268f50 → 0.6.2-develop.3406.1.2b403f40da

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 (95) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/workflow_definition/index.js +2 -0
  3. package/dist/generated/entities/workflow_definition/index.js.map +2 -2
  4. package/dist/generated/entity-fields-registry.js +1 -0
  5. package/dist/generated/entity-fields-registry.js.map +2 -2
  6. package/dist/modules/sales/workflows.js +180 -0
  7. package/dist/modules/sales/workflows.js.map +7 -0
  8. package/dist/modules/workflows/api/definitions/[id]/customize/route.js +172 -0
  9. package/dist/modules/workflows/api/definitions/[id]/customize/route.js.map +7 -0
  10. package/dist/modules/workflows/api/definitions/[id]/reset-to-code/route.js +205 -0
  11. package/dist/modules/workflows/api/definitions/[id]/reset-to-code/route.js.map +7 -0
  12. package/dist/modules/workflows/api/definitions/[id]/route.js +91 -2
  13. package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
  14. package/dist/modules/workflows/api/definitions/route.js +32 -9
  15. package/dist/modules/workflows/api/definitions/route.js.map +2 -2
  16. package/dist/modules/workflows/api/definitions/serialize.js +36 -1
  17. package/dist/modules/workflows/api/definitions/serialize.js.map +2 -2
  18. package/dist/modules/workflows/backend/definitions/[id]/page.js +161 -52
  19. package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
  20. package/dist/modules/workflows/backend/definitions/page.js +37 -34
  21. package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
  22. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +134 -41
  23. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  24. package/dist/modules/workflows/cli.js +19 -121
  25. package/dist/modules/workflows/cli.js.map +2 -2
  26. package/dist/modules/workflows/components/formConfig.js +1 -1
  27. package/dist/modules/workflows/components/formConfig.js.map +2 -2
  28. package/dist/modules/workflows/data/entities.js +3 -0
  29. package/dist/modules/workflows/data/entities.js.map +2 -2
  30. package/dist/modules/workflows/data/validators.js +2 -2
  31. package/dist/modules/workflows/data/validators.js.map +2 -2
  32. package/dist/modules/workflows/events.js +2 -0
  33. package/dist/modules/workflows/events.js.map +2 -2
  34. package/dist/modules/workflows/examples/guard-rules-example.json +6 -6
  35. package/dist/modules/workflows/examples/order-approval-guard-rules.json +2 -2
  36. package/dist/modules/workflows/frontend/checkout-demo/page.js +4 -4
  37. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  38. package/dist/modules/workflows/lib/code-registry.js +40 -0
  39. package/dist/modules/workflows/lib/code-registry.js.map +7 -0
  40. package/dist/modules/workflows/lib/find-definition.js +58 -0
  41. package/dist/modules/workflows/lib/find-definition.js.map +7 -0
  42. package/dist/modules/workflows/lib/seeds.js +0 -3
  43. package/dist/modules/workflows/lib/seeds.js.map +2 -2
  44. package/dist/modules/workflows/lib/start-validator.js +1 -20
  45. package/dist/modules/workflows/lib/start-validator.js.map +2 -2
  46. package/dist/modules/workflows/lib/workflow-executor.js +1 -19
  47. package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
  48. package/dist/modules/workflows/migrations/Migration20260414120000.js +13 -0
  49. package/dist/modules/workflows/migrations/Migration20260414120000.js.map +7 -0
  50. package/dist/modules/workflows/migrations/Migration20260428102318.js +32 -0
  51. package/dist/modules/workflows/migrations/Migration20260428102318.js.map +7 -0
  52. package/dist/modules/workflows/widgets/injection/order-approval/widget.client.js +1 -1
  53. package/dist/modules/workflows/widgets/injection/order-approval/widget.client.js.map +1 -1
  54. package/dist/modules/workflows/workflows.js +260 -0
  55. package/dist/modules/workflows/workflows.js.map +7 -0
  56. package/generated/entities/workflow_definition/index.ts +1 -0
  57. package/generated/entity-fields-registry.ts +1 -0
  58. package/package.json +7 -7
  59. package/src/modules/sales/workflows.ts +184 -0
  60. package/src/modules/workflows/api/definitions/[id]/customize/route.ts +197 -0
  61. package/src/modules/workflows/api/definitions/[id]/reset-to-code/route.ts +252 -0
  62. package/src/modules/workflows/api/definitions/[id]/route.ts +108 -4
  63. package/src/modules/workflows/api/definitions/route.ts +54 -10
  64. package/src/modules/workflows/api/definitions/serialize.ts +42 -0
  65. package/src/modules/workflows/backend/definitions/[id]/page.tsx +135 -11
  66. package/src/modules/workflows/backend/definitions/page.tsx +45 -33
  67. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +222 -97
  68. package/src/modules/workflows/cli.ts +32 -155
  69. package/src/modules/workflows/components/formConfig.tsx +2 -2
  70. package/src/modules/workflows/data/entities.ts +4 -1
  71. package/src/modules/workflows/data/validators.ts +4 -3
  72. package/src/modules/workflows/events.ts +2 -0
  73. package/src/modules/workflows/examples/guard-rules-example.json +6 -6
  74. package/src/modules/workflows/examples/order-approval-guard-rules.json +2 -2
  75. package/src/modules/workflows/frontend/checkout-demo/page.tsx +4 -4
  76. package/src/modules/workflows/i18n/de.json +16 -0
  77. package/src/modules/workflows/i18n/en.json +16 -0
  78. package/src/modules/workflows/i18n/es.json +16 -0
  79. package/src/modules/workflows/i18n/pl.json +16 -0
  80. package/src/modules/workflows/lib/code-registry.ts +65 -0
  81. package/src/modules/workflows/lib/find-definition.ts +100 -0
  82. package/src/modules/workflows/lib/seeds.ts +4 -3
  83. package/src/modules/workflows/lib/start-validator.ts +2 -35
  84. package/src/modules/workflows/lib/workflow-executor.ts +2 -39
  85. package/src/modules/workflows/migrations/.snapshot-open-mercato.json +10 -0
  86. package/src/modules/workflows/migrations/Migration20260414120000.ts +13 -0
  87. package/src/modules/workflows/migrations/Migration20260428102318.ts +57 -0
  88. package/src/modules/workflows/widgets/injection/order-approval/widget.client.tsx +1 -1
  89. package/src/modules/workflows/workflows.ts +278 -0
  90. package/dist/modules/workflows/examples/checkout-demo-definition.json +0 -486
  91. package/dist/modules/workflows/examples/order-approval-definition.json +0 -257
  92. package/dist/modules/workflows/examples/simple-approval-definition.json +0 -134
  93. package/src/modules/workflows/examples/checkout-demo-definition.json +0 -486
  94. package/src/modules/workflows/examples/order-approval-definition.json +0 -257
  95. package/src/modules/workflows/examples/simple-approval-definition.json +0 -134
@@ -0,0 +1,65 @@
1
+ /**
2
+ * In-Memory Code Workflow Registry
3
+ *
4
+ * Stores code-based workflow definitions populated at bootstrap from
5
+ * the generated workflows.generated.ts. Definitions live purely in memory —
6
+ * no DB row is created until a user customizes the definition.
7
+ */
8
+
9
+ import type { CodeWorkflowDefinition } from '@open-mercato/shared/modules/workflows'
10
+ import { workflowDefinitionDataSchema } from '../data/validators'
11
+
12
+ const codeWorkflowRegistry = new Map<string, CodeWorkflowDefinition>()
13
+
14
+ /**
15
+ * Register code-based workflow definitions at bootstrap time.
16
+ * Validates each definition against the Zod schema and warns on failures.
17
+ */
18
+ export function registerCodeWorkflows(workflows: CodeWorkflowDefinition[]): void {
19
+ for (const wf of workflows) {
20
+ const validation = workflowDefinitionDataSchema.safeParse(wf.definition)
21
+ if (!validation.success) {
22
+ console.warn(
23
+ `[workflows] Code workflow "${wf.workflowId}" failed validation:`,
24
+ validation.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; '),
25
+ )
26
+ continue
27
+ }
28
+
29
+ if (codeWorkflowRegistry.has(wf.workflowId)) {
30
+ console.warn(
31
+ `[workflows] Duplicate code workflow ID "${wf.workflowId}" from module "${wf.moduleId}" — overwriting`,
32
+ )
33
+ }
34
+
35
+ codeWorkflowRegistry.set(wf.workflowId, wf)
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Get a single code workflow definition by workflowId.
41
+ */
42
+ export function getCodeWorkflow(workflowId: string): CodeWorkflowDefinition | undefined {
43
+ return codeWorkflowRegistry.get(workflowId)
44
+ }
45
+
46
+ /**
47
+ * Get all registered code workflow definitions.
48
+ */
49
+ export function getAllCodeWorkflows(): CodeWorkflowDefinition[] {
50
+ return Array.from(codeWorkflowRegistry.values())
51
+ }
52
+
53
+ /**
54
+ * Check if a workflowId is a code-based definition.
55
+ */
56
+ export function isCodeWorkflow(workflowId: string): boolean {
57
+ return codeWorkflowRegistry.has(workflowId)
58
+ }
59
+
60
+ /**
61
+ * Clear the registry (for testing).
62
+ */
63
+ export function clearCodeWorkflowRegistry(): void {
64
+ codeWorkflowRegistry.clear()
65
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Unified Workflow Definition Lookup
3
+ *
4
+ * Finds workflow definitions from the database first, falling back to the
5
+ * in-memory code workflow registry. This enables code-based definitions to
6
+ * be used seamlessly by the executor and validators.
7
+ */
8
+
9
+ import { createHash } from 'node:crypto'
10
+ import { EntityManager } from '@mikro-orm/core'
11
+ import { WorkflowDefinition, type WorkflowDefinitionData } from '../data/entities'
12
+ import { getCodeWorkflow } from './code-registry'
13
+
14
+ /**
15
+ * Generate a deterministic UUID for a code workflow definition.
16
+ * Uses SHA-256 truncated to UUID v4 format so the same workflowId always
17
+ * produces the same UUID across restarts and horizontal replicas.
18
+ */
19
+ export function codeWorkflowUuid(workflowId: string): string {
20
+ const hash = createHash('sha256').update(`code-workflow:${workflowId}`).digest('hex')
21
+ // Format as UUID v4 (set version nibble to 4, variant bits to 10xx)
22
+ return [
23
+ hash.slice(0, 8),
24
+ hash.slice(8, 12),
25
+ `4${hash.slice(13, 16)}`,
26
+ `${(parseInt(hash[16], 16) & 0x3 | 0x8).toString(16)}${hash.slice(17, 20)}`,
27
+ hash.slice(20, 32),
28
+ ].join('-')
29
+ }
30
+
31
+ /**
32
+ * Find a workflow definition by workflowId, checking the database first
33
+ * and falling back to the code registry.
34
+ *
35
+ * When a DB row exists with `codeWorkflowId` set, it means the user has
36
+ * customized the code definition — the DB version takes precedence.
37
+ *
38
+ * When no DB row exists but a code definition is registered, a virtual
39
+ * WorkflowDefinition-like object is constructed with a deterministic UUID.
40
+ */
41
+ export async function findWorkflowDefinition(
42
+ em: EntityManager,
43
+ options: {
44
+ workflowId: string
45
+ version?: number
46
+ tenantId: string
47
+ organizationId: string
48
+ },
49
+ ): Promise<WorkflowDefinition | null> {
50
+ const { workflowId, version, tenantId, organizationId } = options
51
+
52
+ // 1. Try database first
53
+ const where: Record<string, unknown> = {
54
+ workflowId,
55
+ tenantId,
56
+ organizationId,
57
+ deletedAt: null,
58
+ }
59
+
60
+ if (version !== undefined) {
61
+ where.version = version
62
+ }
63
+
64
+ if (version === undefined) {
65
+ where.enabled = true
66
+ const dbDef = await em.findOne(WorkflowDefinition, where, {
67
+ orderBy: { version: 'DESC' },
68
+ })
69
+ if (dbDef) return dbDef
70
+ } else {
71
+ const dbDef = await em.findOne(WorkflowDefinition, where)
72
+ if (dbDef) return dbDef
73
+ }
74
+
75
+ // 2. Fall back to code registry (version filter not applicable to code defs)
76
+ const codeDef = getCodeWorkflow(workflowId)
77
+ if (!codeDef) return null
78
+
79
+ // When no version was requested, mirror the DB branch's `enabled = true` filter
80
+ // so disabled code workflows aren't silently executable.
81
+ if (version === undefined && !codeDef.enabled) return null
82
+
83
+ // Construct a virtual WorkflowDefinition object (not persisted)
84
+ const virtual = new WorkflowDefinition()
85
+ virtual.id = codeWorkflowUuid(workflowId)
86
+ virtual.workflowId = codeDef.workflowId
87
+ virtual.workflowName = codeDef.workflowName
88
+ virtual.description = codeDef.description ?? null
89
+ virtual.version = codeDef.version
90
+ virtual.definition = codeDef.definition as WorkflowDefinitionData
91
+ virtual.metadata = codeDef.metadata ?? null
92
+ virtual.enabled = codeDef.enabled
93
+ virtual.codeWorkflowId = codeDef.workflowId
94
+ virtual.tenantId = tenantId
95
+ virtual.organizationId = organizationId
96
+ virtual.createdAt = new Date(0) // code defs have no creation date
97
+ virtual.updatedAt = new Date(0)
98
+
99
+ return virtual
100
+ }
@@ -190,10 +190,11 @@ async function seedGuardRules(
190
190
  }
191
191
 
192
192
  export async function seedExampleWorkflows(em: EntityManager, scope: WorkflowSeedScope): Promise<void> {
193
- await seedWorkflowDefinition(em, scope, 'checkout-demo-definition.json')
193
+ // workflows.checkout-demo and workflows.simple-approval are now code-defined
194
+ // (see packages/core/src/modules/workflows/workflows.ts). Seeding DB rows for
195
+ // them would shadow the code definitions in the merge layer, so they are no
196
+ // longer seeded here. Existing tenants are migrated via Migration20260428102318.
194
197
  await seedGuardRules(em, scope, 'guard-rules-example.json')
195
198
  await seedWorkflowDefinition(em, scope, 'sales-pipeline-definition.json')
196
- await seedWorkflowDefinition(em, scope, 'simple-approval-definition.json')
197
199
  await seedGuardRules(em, scope, 'order-approval-guard-rules.json')
198
- await seedWorkflowDefinition(em, scope, 'order-approval-definition.json')
199
200
  }
@@ -9,6 +9,7 @@
9
9
  import { EntityManager } from '@mikro-orm/core'
10
10
  import { WorkflowDefinition } from '../data/entities'
11
11
  import * as ruleEngine from '../../business_rules/lib/rule-engine'
12
+ import { findWorkflowDefinition } from './find-definition'
12
13
  import type { StartPreCondition } from '../data/validators'
13
14
 
14
15
  // ============================================================================
@@ -258,38 +259,4 @@ function getLocalizedMessage(
258
259
  return defaultMessage
259
260
  }
260
261
 
261
- /**
262
- * Find workflow definition by ID and optional version
263
- */
264
- async function findWorkflowDefinition(
265
- em: EntityManager,
266
- options: {
267
- workflowId: string
268
- version?: number
269
- tenantId: string
270
- organizationId: string
271
- }
272
- ): Promise<WorkflowDefinition | null> {
273
- const { workflowId, version, tenantId, organizationId } = options
274
-
275
- const where: any = {
276
- workflowId,
277
- tenantId,
278
- organizationId,
279
- deletedAt: null,
280
- }
281
-
282
- if (version !== undefined) {
283
- where.version = version
284
- }
285
-
286
- // If no version specified, get latest enabled version
287
- if (version === undefined) {
288
- where.enabled = true
289
- return em.findOne(WorkflowDefinition, where, {
290
- orderBy: { version: 'DESC' },
291
- })
292
- }
293
-
294
- return em.findOne(WorkflowDefinition, where)
295
- }
262
+ // findWorkflowDefinition is imported from ./find-definition
@@ -19,6 +19,7 @@ import {
19
19
  type WorkflowInstanceStatus,
20
20
  } from '../data/entities'
21
21
  import { compensateWorkflow } from './compensation-handler'
22
+ import { findWorkflowDefinition } from './find-definition'
22
23
 
23
24
  // ============================================================================
24
25
  // Types and Interfaces
@@ -903,45 +904,7 @@ export async function updateWorkflowContext(
903
904
  await em.flush()
904
905
  }
905
906
 
906
- /**
907
- * Find workflow definition by ID and optional version
908
- *
909
- * @param em - Entity manager
910
- * @param options - Search options
911
- * @returns Workflow definition or null
912
- */
913
- async function findWorkflowDefinition(
914
- em: EntityManager,
915
- options: {
916
- workflowId: string
917
- version?: number
918
- tenantId: string
919
- organizationId: string
920
- }
921
- ): Promise<WorkflowDefinition | null> {
922
- const { workflowId, version, tenantId, organizationId } = options
923
-
924
- const where: any = {
925
- workflowId,
926
- tenantId,
927
- organizationId,
928
- deletedAt: null,
929
- }
930
-
931
- if (version !== undefined) {
932
- where.version = version
933
- }
934
-
935
- // If no version specified, get latest enabled version
936
- if (version === undefined) {
937
- where.enabled = true
938
- return em.findOne(WorkflowDefinition, where, {
939
- orderBy: { version: 'DESC' },
940
- })
941
- }
942
-
943
- return em.findOne(WorkflowDefinition, where)
944
- }
907
+ // findWorkflowDefinition is imported from ./find-definition
945
908
 
946
909
  /**
947
910
  * Log workflow event to event sourcing table
@@ -518,6 +518,16 @@
518
518
  "length": 100,
519
519
  "mappedType": "string"
520
520
  },
521
+ "code_workflow_id": {
522
+ "name": "code_workflow_id",
523
+ "type": "varchar(100)",
524
+ "unsigned": false,
525
+ "autoincrement": false,
526
+ "primary": false,
527
+ "nullable": true,
528
+ "length": 100,
529
+ "mappedType": "string"
530
+ },
521
531
  "workflow_name": {
522
532
  "name": "workflow_name",
523
533
  "type": "varchar(255)",
@@ -0,0 +1,13 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+
3
+ export class Migration20260414120000 extends Migration {
4
+
5
+ override async up(): Promise<void> {
6
+ this.addSql(`alter table "workflow_definitions" add column if not exists "code_workflow_id" varchar(100) null;`);
7
+ }
8
+
9
+ override async down(): Promise<void> {
10
+ this.addSql(`alter table "workflow_definitions" drop column if exists "code_workflow_id";`);
11
+ }
12
+
13
+ }
@@ -0,0 +1,57 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+
3
+ const RENAMES: Array<{ from: string; to: string }> = [
4
+ { from: 'checkout_simple_v1', to: 'workflows.checkout-demo' },
5
+ { from: 'sales_order_approval_v1', to: 'sales.order-approval' },
6
+ { from: 'simple_approval_v1', to: 'workflows.simple-approval' },
7
+ ];
8
+
9
+ export class Migration20260428102318 extends Migration {
10
+
11
+ override async up(): Promise<void> {
12
+ for (const { from, to } of RENAMES) {
13
+ this.addSql(
14
+ `update "workflow_definitions" ` +
15
+ `set "workflow_id" = '${to}' ` +
16
+ `where "workflow_id" = '${from}' ` +
17
+ `and not exists (` +
18
+ `select 1 from "workflow_definitions" d2 ` +
19
+ `where d2."workflow_id" = '${to}' and d2."tenant_id" = "workflow_definitions"."tenant_id"` +
20
+ `);`
21
+ );
22
+ this.addSql(
23
+ `update "workflow_instances" ` +
24
+ `set "workflow_id" = '${to}' ` +
25
+ `where "workflow_id" = '${from}' ` +
26
+ `and exists (` +
27
+ `select 1 from "workflow_definitions" d ` +
28
+ `where d."id" = "workflow_instances"."definition_id" and d."workflow_id" = '${to}'` +
29
+ `);`
30
+ );
31
+ }
32
+ }
33
+
34
+ override async down(): Promise<void> {
35
+ for (const { from, to } of RENAMES) {
36
+ this.addSql(
37
+ `update "workflow_definitions" ` +
38
+ `set "workflow_id" = '${from}' ` +
39
+ `where "workflow_id" = '${to}' ` +
40
+ `and not exists (` +
41
+ `select 1 from "workflow_definitions" d2 ` +
42
+ `where d2."workflow_id" = '${from}' and d2."tenant_id" = "workflow_definitions"."tenant_id"` +
43
+ `);`
44
+ );
45
+ this.addSql(
46
+ `update "workflow_instances" ` +
47
+ `set "workflow_id" = '${from}' ` +
48
+ `where "workflow_id" = '${to}' ` +
49
+ `and exists (` +
50
+ `select 1 from "workflow_definitions" d ` +
51
+ `where d."id" = "workflow_instances"."definition_id" and d."workflow_id" = '${from}'` +
52
+ `);`
53
+ );
54
+ }
55
+ }
56
+
57
+ }
@@ -42,7 +42,7 @@ type DictionaryEntry = {
42
42
  icon?: string
43
43
  }
44
44
 
45
- const WORKFLOW_ID = 'sales_order_approval_v1'
45
+ const WORKFLOW_ID = 'sales.order-approval'
46
46
 
47
47
  export default function OrderApprovalWidget({ data }: InjectionWidgetComponentProps<unknown, OrderRecord>) {
48
48
  const t = useT()
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Workflows Module — Code-Based Workflow Definitions
3
+ *
4
+ * Demo workflows shipped with the workflows module.
5
+ * These are auto-discovered by the generator and loaded into the in-memory registry.
6
+ */
7
+
8
+ import { defineWorkflow, createWorkflowsModuleConfig } from '@open-mercato/shared/modules/workflows'
9
+
10
+ // ============================================================================
11
+ // Simple Approval Workflow
12
+ // ============================================================================
13
+
14
+ const simpleApproval = defineWorkflow({
15
+ workflowId: 'workflows.simple-approval',
16
+ workflowName: 'Simple Approval Workflow',
17
+ description: 'Basic three-step approval workflow: Start → Approval → End',
18
+ metadata: { category: 'Testing', tags: ['test', 'approval', 'simple'], icon: 'check-circle' },
19
+ steps: [
20
+ { stepId: 'start', stepName: 'Start', stepType: 'START', description: 'Initialize workflow' },
21
+ {
22
+ stepId: 'approval',
23
+ stepName: 'Pending Approval',
24
+ stepType: 'USER_TASK',
25
+ description: 'Approve or reject request',
26
+ userTaskConfig: { assignedTo: 'approver', slaDuration: 'PT1H' },
27
+ },
28
+ { stepId: 'end', stepName: 'Complete', stepType: 'END', description: 'Workflow complete' },
29
+ ] as const,
30
+ transitions: [
31
+ {
32
+ transitionId: 'start_to_approval',
33
+ transitionName: 'Submit for Approval',
34
+ fromStepId: 'start',
35
+ toStepId: 'approval',
36
+ trigger: 'auto',
37
+ priority: 100,
38
+ activities: [
39
+ {
40
+ activityId: 'notify_approver',
41
+ activityName: 'Notify Approver',
42
+ activityType: 'SEND_EMAIL',
43
+ config: {
44
+ to: '{{context.approver.email}}',
45
+ subject: 'Approval Required: {{context.request.title}}',
46
+ template: 'approval_request',
47
+ data: {
48
+ requestId: '{{context.request.id}}',
49
+ title: '{{context.request.title}}',
50
+ requester: '{{context.requester.name}}',
51
+ },
52
+ },
53
+ timeout: 'PT10S',
54
+ async: true,
55
+ retryPolicy: { maxAttempts: 3, initialIntervalMs: 2000, backoffCoefficient: 2, maxIntervalMs: 20000 },
56
+ },
57
+ {
58
+ activityId: 'emit_approval_requested',
59
+ activityName: 'Emit Approval Requested Event',
60
+ activityType: 'EMIT_EVENT',
61
+ config: {
62
+ eventType: 'approval.requested',
63
+ payload: { requestId: '{{context.request.id}}', workflowInstanceId: '{{workflow.instanceId}}' },
64
+ },
65
+ async: true,
66
+ },
67
+ ],
68
+ },
69
+ {
70
+ transitionId: 'approval_to_end',
71
+ transitionName: 'Approval Decision',
72
+ fromStepId: 'approval',
73
+ toStepId: 'end',
74
+ trigger: 'manual',
75
+ priority: 100,
76
+ activities: [
77
+ {
78
+ activityId: 'notify_requester',
79
+ activityName: 'Notify Requester of Decision',
80
+ activityType: 'SEND_EMAIL',
81
+ config: {
82
+ to: '{{context.requester.email}}',
83
+ subject: 'Approval Decision: {{context.request.title}}',
84
+ template: 'approval_decision',
85
+ data: {
86
+ requestId: '{{context.request.id}}',
87
+ title: '{{context.request.title}}',
88
+ decision: '{{task.result.approved}}',
89
+ comments: '{{task.result.comments}}',
90
+ approver: '{{task.completedBy}}',
91
+ },
92
+ },
93
+ timeout: 'PT10S',
94
+ async: true,
95
+ },
96
+ {
97
+ activityId: 'emit_approval_completed',
98
+ activityName: 'Emit Approval Completed Event',
99
+ activityType: 'EMIT_EVENT',
100
+ config: {
101
+ eventType: 'approval.completed',
102
+ payload: {
103
+ requestId: '{{context.request.id}}',
104
+ approved: '{{task.result.approved}}',
105
+ approvedBy: '{{task.completedBy}}',
106
+ timestamp: '{{now}}',
107
+ },
108
+ },
109
+ async: true,
110
+ },
111
+ ],
112
+ },
113
+ ],
114
+ })
115
+
116
+ // ============================================================================
117
+ // Checkout Demo Workflow
118
+ // ============================================================================
119
+
120
+ const checkoutDemo = defineWorkflow({
121
+ workflowId: 'workflows.checkout-demo',
122
+ workflowName: 'Checkout with Payment Webhook',
123
+ description: 'Realistic checkout workflow with signal-based payment confirmation',
124
+ metadata: { category: 'E-commerce', tags: ['checkout', 'demo', 'payments', 'signals'], icon: 'ShoppingCart' },
125
+ steps: [
126
+ {
127
+ stepId: 'start',
128
+ stepName: 'Start',
129
+ stepType: 'START',
130
+ description: 'Checkout process initiated',
131
+ preConditions: [{
132
+ ruleId: 'workflow_checkout_cart_not_empty',
133
+ required: true,
134
+ validationMessage: {
135
+ en: 'Your shopping cart is empty. Please add items before starting checkout.',
136
+ pl: 'Twój koszyk jest pusty. Dodaj produkty przed rozpoczęciem zakupu.',
137
+ },
138
+ }],
139
+ },
140
+ { stepId: 'cart_validation', stepName: 'Cart Validation', stepType: 'AUTOMATED', description: 'Validate cart has items and inventory is available', timeout: 'PT10S' },
141
+ {
142
+ stepId: 'customer_info',
143
+ stepName: 'Customer Information',
144
+ stepType: 'USER_TASK',
145
+ description: 'Collect customer shipping and contact information',
146
+ userTaskConfig: {
147
+ formSchema: {
148
+ type: 'object',
149
+ properties: {
150
+ fullName: { type: 'string', title: 'Full Name', description: "Customer's full legal name" },
151
+ email: { type: 'string', format: 'email', title: 'Email Address', description: 'Email for order confirmation' },
152
+ phone: { type: 'string', title: 'Phone Number', description: 'Contact number for delivery' },
153
+ comment: { type: 'string', title: 'Order Comment', maxLength: 500, description: 'Special instructions or notes' },
154
+ },
155
+ required: ['fullName', 'email'],
156
+ },
157
+ slaDuration: 'PT24H',
158
+ },
159
+ },
160
+ { stepId: 'payment_initiation', stepName: 'Initiate Payment', stepType: 'AUTOMATED', description: 'Send payment request to payment provider', timeout: 'PT10S' },
161
+ {
162
+ stepId: 'wait_payment_confirmation',
163
+ stepName: 'Wait for Payment Confirmation',
164
+ stepType: 'WAIT_FOR_SIGNAL',
165
+ description: 'Waiting for payment provider webhook confirmation',
166
+ signalConfig: { signalName: 'payment_confirmed', timeout: 'PT5M' },
167
+ },
168
+ { stepId: 'order_confirmation', stepName: 'Order Confirmation', stepType: 'AUTOMATED', description: 'Create order record and send confirmation', timeout: 'PT15S' },
169
+ { stepId: 'end', stepName: 'Complete', stepType: 'END', description: 'Checkout completed successfully' },
170
+ ] as const,
171
+ transitions: [
172
+ {
173
+ transitionId: 'start_to_cart',
174
+ transitionName: 'Initialize Checkout',
175
+ fromStepId: 'start',
176
+ toStepId: 'cart_validation',
177
+ trigger: 'auto',
178
+ priority: 100,
179
+ activities: [{
180
+ activityId: 'log_checkout_start',
181
+ activityName: 'Log Checkout Start',
182
+ activityType: 'EMIT_EVENT',
183
+ config: {
184
+ eventName: 'checkout.started',
185
+ payload: { customerId: '{{context.customerId}}', cartId: '{{context.cartId}}', currency: '{{context.currency}}', timestamp: '{{now}}' },
186
+ },
187
+ async: false,
188
+ }],
189
+ },
190
+ {
191
+ transitionId: 'cart_to_customer_info',
192
+ transitionName: 'Collect Customer Information',
193
+ fromStepId: 'cart_validation',
194
+ toStepId: 'customer_info',
195
+ trigger: 'auto',
196
+ priority: 100,
197
+ preConditions: [{ ruleId: 'workflow_checkout_cart_not_empty', required: true }],
198
+ },
199
+ {
200
+ transitionId: 'customer_info_to_payment',
201
+ transitionName: 'Initiate Payment',
202
+ fromStepId: 'customer_info',
203
+ toStepId: 'payment_initiation',
204
+ trigger: 'auto',
205
+ priority: 100,
206
+ },
207
+ {
208
+ transitionId: 'payment_to_wait_confirmation',
209
+ transitionName: 'Wait for Confirmation',
210
+ fromStepId: 'payment_initiation',
211
+ toStepId: 'wait_payment_confirmation',
212
+ trigger: 'auto',
213
+ priority: 100,
214
+ },
215
+ {
216
+ transitionId: 'confirmation_to_order',
217
+ transitionName: 'Payment Confirmed',
218
+ fromStepId: 'wait_payment_confirmation',
219
+ toStepId: 'order_confirmation',
220
+ trigger: 'auto',
221
+ priority: 100,
222
+ },
223
+ {
224
+ transitionId: 'confirmation_to_end',
225
+ transitionName: 'Finalize Order',
226
+ fromStepId: 'order_confirmation',
227
+ toStepId: 'end',
228
+ trigger: 'auto',
229
+ priority: 100,
230
+ activities: [
231
+ {
232
+ activityId: 'create_order',
233
+ activityName: 'Create Order Record',
234
+ activityType: 'CALL_API',
235
+ config: {
236
+ endpoint: '/api/sales/orders',
237
+ method: 'POST',
238
+ body: {
239
+ customerEntityId: '{{context.customer.id}}',
240
+ currencyCode: '{{context.cart.currency}}',
241
+ placedAt: '{{now}}',
242
+ grandTotalGrossAmount: '{{context.cart.total}}',
243
+ },
244
+ validateTenantMatch: true,
245
+ },
246
+ timeout: 'PT10S',
247
+ async: false,
248
+ retryPolicy: { maxAttempts: 3, initialIntervalMs: 2000, backoffCoefficient: 2, maxIntervalMs: 10000 },
249
+ },
250
+ {
251
+ activityId: 'send_confirmation_email',
252
+ activityName: 'Send Order Confirmation Email',
253
+ activityType: 'SEND_EMAIL',
254
+ config: {
255
+ to: '{{context.customer.email}}',
256
+ subject: 'Order Confirmation',
257
+ template: 'order_confirmation',
258
+ data: { customerName: '{{context.customer.name}}', total: '{{context.cart.total}}' },
259
+ },
260
+ timeout: 'PT15S',
261
+ async: true,
262
+ retryPolicy: { maxAttempts: 3, initialIntervalMs: 2000, backoffCoefficient: 2, maxIntervalMs: 20000 },
263
+ },
264
+ ],
265
+ },
266
+ ],
267
+ })
268
+
269
+ // ============================================================================
270
+ // Module Export
271
+ // ============================================================================
272
+
273
+ export const workflowsConfig = createWorkflowsModuleConfig({
274
+ moduleId: 'workflows',
275
+ workflows: [simpleApproval, checkoutDemo],
276
+ })
277
+
278
+ export default workflowsConfig