@open-mercato/core 0.4.2-canary-51881f6bf3 → 0.4.2-canary-5f415b8a44

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 (155) hide show
  1. package/dist/generated/entities/workflow_event_trigger/index.js +33 -0
  2. package/dist/generated/entities/workflow_event_trigger/index.js.map +7 -0
  3. package/dist/generated/entities.ids.generated.js +59 -58
  4. package/dist/generated/entities.ids.generated.js.map +2 -2
  5. package/dist/generated/entity-fields-registry.js +2 -0
  6. package/dist/generated/entity-fields-registry.js.map +2 -2
  7. package/dist/modules/auth/events.js +30 -0
  8. package/dist/modules/auth/events.js.map +7 -0
  9. package/dist/modules/business_rules/api/execute/[ruleId]/route.js +145 -0
  10. package/dist/modules/business_rules/api/execute/[ruleId]/route.js.map +7 -0
  11. package/dist/modules/business_rules/data/validators.js +34 -0
  12. package/dist/modules/business_rules/data/validators.js.map +2 -2
  13. package/dist/modules/business_rules/index.js +21 -1
  14. package/dist/modules/business_rules/index.js.map +2 -2
  15. package/dist/modules/business_rules/lib/rule-engine.js +182 -1
  16. package/dist/modules/business_rules/lib/rule-engine.js.map +2 -2
  17. package/dist/modules/catalog/events.js +34 -0
  18. package/dist/modules/catalog/events.js.map +7 -0
  19. package/dist/modules/customers/events.js +49 -0
  20. package/dist/modules/customers/events.js.map +7 -0
  21. package/dist/modules/directory/events.js +23 -0
  22. package/dist/modules/directory/events.js.map +7 -0
  23. package/dist/modules/sales/acl.js +1 -0
  24. package/dist/modules/sales/acl.js.map +2 -2
  25. package/dist/modules/sales/backend/sales/documents/[id]/page.js +12 -0
  26. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  27. package/dist/modules/sales/commands/documents.js +62 -0
  28. package/dist/modules/sales/commands/documents.js.map +2 -2
  29. package/dist/modules/sales/events.js +63 -0
  30. package/dist/modules/sales/events.js.map +7 -0
  31. package/dist/modules/sales/lib/dictionaries.js +3 -0
  32. package/dist/modules/sales/lib/dictionaries.js.map +2 -2
  33. package/dist/modules/sales/lib/frontend/documentDataEvents.js +25 -0
  34. package/dist/modules/sales/lib/frontend/documentDataEvents.js.map +7 -0
  35. package/dist/modules/workflows/acl.js +2 -0
  36. package/dist/modules/workflows/acl.js.map +2 -2
  37. package/dist/modules/workflows/api/instances/route.js +18 -6
  38. package/dist/modules/workflows/api/instances/route.js.map +2 -2
  39. package/dist/modules/workflows/api/tasks/route.js +6 -1
  40. package/dist/modules/workflows/api/tasks/route.js.map +2 -2
  41. package/dist/modules/workflows/backend/definitions/[id]/page.js +9 -1
  42. package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
  43. package/dist/modules/workflows/backend/definitions/[id]/page.meta.js +1 -1
  44. package/dist/modules/workflows/backend/definitions/[id]/page.meta.js.map +2 -2
  45. package/dist/modules/workflows/backend/definitions/create/page.js +24 -15
  46. package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
  47. package/dist/modules/workflows/backend/definitions/create/page.meta.js +1 -1
  48. package/dist/modules/workflows/backend/definitions/create/page.meta.js.map +2 -2
  49. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +150 -132
  50. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  51. package/dist/modules/workflows/backend/definitions/visual-editor/page.meta.js +1 -1
  52. package/dist/modules/workflows/backend/definitions/visual-editor/page.meta.js.map +2 -2
  53. package/dist/modules/workflows/backend/events/[id]/page.js +1 -1
  54. package/dist/modules/workflows/backend/events/[id]/page.js.map +2 -2
  55. package/dist/modules/workflows/backend/events/[id]/page.meta.js +2 -2
  56. package/dist/modules/workflows/backend/events/[id]/page.meta.js.map +2 -2
  57. package/dist/modules/workflows/backend/instances/[id]/page.meta.js +2 -2
  58. package/dist/modules/workflows/backend/instances/[id]/page.meta.js.map +2 -2
  59. package/dist/modules/workflows/backend/tasks/[id]/page.js +1 -1
  60. package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
  61. package/dist/modules/workflows/backend/tasks/[id]/page.meta.js +2 -2
  62. package/dist/modules/workflows/backend/tasks/[id]/page.meta.js.map +2 -2
  63. package/dist/modules/workflows/backend/tasks/page.js +5 -6
  64. package/dist/modules/workflows/backend/tasks/page.js.map +2 -2
  65. package/dist/modules/workflows/cli.js +81 -3
  66. package/dist/modules/workflows/cli.js.map +3 -3
  67. package/dist/modules/workflows/components/DefinitionTriggersEditor.js +481 -0
  68. package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +7 -0
  69. package/dist/modules/workflows/components/EventTriggersEditor.js +553 -0
  70. package/dist/modules/workflows/components/EventTriggersEditor.js.map +7 -0
  71. package/dist/modules/workflows/data/entities.js +64 -1
  72. package/dist/modules/workflows/data/entities.js.map +2 -2
  73. package/dist/modules/workflows/data/validators.js +115 -0
  74. package/dist/modules/workflows/data/validators.js.map +2 -2
  75. package/dist/modules/workflows/events.js +38 -0
  76. package/dist/modules/workflows/events.js.map +7 -0
  77. package/dist/modules/workflows/examples/checkout-demo-definition.json +1 -5
  78. package/dist/modules/workflows/examples/order-approval-definition.json +257 -0
  79. package/dist/modules/workflows/examples/order-approval-guard-rules.json +32 -0
  80. package/dist/modules/workflows/lib/activity-executor.js +75 -13
  81. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  82. package/dist/modules/workflows/lib/event-trigger-service.js +308 -0
  83. package/dist/modules/workflows/lib/event-trigger-service.js.map +7 -0
  84. package/dist/modules/workflows/lib/graph-utils.js +71 -2
  85. package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
  86. package/dist/modules/workflows/lib/seeds.js +22 -5
  87. package/dist/modules/workflows/lib/seeds.js.map +2 -2
  88. package/dist/modules/workflows/lib/start-validator.js +33 -23
  89. package/dist/modules/workflows/lib/start-validator.js.map +2 -2
  90. package/dist/modules/workflows/lib/transition-handler.js +157 -45
  91. package/dist/modules/workflows/lib/transition-handler.js.map +3 -3
  92. package/dist/modules/workflows/migrations/Migration20260123143500.js +36 -0
  93. package/dist/modules/workflows/migrations/Migration20260123143500.js.map +7 -0
  94. package/dist/modules/workflows/subscribers/event-trigger.js +78 -0
  95. package/dist/modules/workflows/subscribers/event-trigger.js.map +7 -0
  96. package/dist/modules/workflows/widgets/injection/order-approval/widget.client.js +323 -0
  97. package/dist/modules/workflows/widgets/injection/order-approval/widget.client.js.map +7 -0
  98. package/dist/modules/workflows/widgets/injection/order-approval/widget.js +17 -0
  99. package/dist/modules/workflows/widgets/injection/order-approval/widget.js.map +7 -0
  100. package/dist/modules/workflows/widgets/injection-table.js +19 -0
  101. package/dist/modules/workflows/widgets/injection-table.js.map +7 -0
  102. package/generated/entities/workflow_event_trigger/index.ts +15 -0
  103. package/generated/entities.ids.generated.ts +59 -58
  104. package/generated/entity-fields-registry.ts +2 -0
  105. package/package.json +3 -5
  106. package/src/modules/auth/events.ts +39 -0
  107. package/src/modules/business_rules/api/execute/[ruleId]/route.ts +163 -0
  108. package/src/modules/business_rules/data/validators.ts +40 -0
  109. package/src/modules/business_rules/index.ts +25 -0
  110. package/src/modules/business_rules/lib/rule-engine.ts +281 -1
  111. package/src/modules/catalog/events.ts +45 -0
  112. package/src/modules/customers/events.ts +63 -0
  113. package/src/modules/directory/events.ts +31 -0
  114. package/src/modules/sales/acl.ts +1 -0
  115. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +16 -0
  116. package/src/modules/sales/commands/documents.ts +74 -1
  117. package/src/modules/sales/events.ts +82 -0
  118. package/src/modules/sales/lib/dictionaries.ts +3 -0
  119. package/src/modules/sales/lib/frontend/documentDataEvents.ts +28 -0
  120. package/src/modules/workflows/acl.ts +2 -0
  121. package/src/modules/workflows/api/instances/route.ts +21 -7
  122. package/src/modules/workflows/api/tasks/route.ts +7 -1
  123. package/src/modules/workflows/backend/definitions/[id]/page.meta.ts +1 -1
  124. package/src/modules/workflows/backend/definitions/[id]/page.tsx +9 -0
  125. package/src/modules/workflows/backend/definitions/create/page.meta.ts +1 -1
  126. package/src/modules/workflows/backend/definitions/create/page.tsx +9 -0
  127. package/src/modules/workflows/backend/definitions/visual-editor/page.meta.ts +1 -1
  128. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +21 -3
  129. package/src/modules/workflows/backend/events/[id]/page.meta.ts +2 -2
  130. package/src/modules/workflows/backend/events/[id]/page.tsx +1 -1
  131. package/src/modules/workflows/backend/instances/[id]/page.meta.ts +2 -2
  132. package/src/modules/workflows/backend/tasks/[id]/page.meta.ts +2 -2
  133. package/src/modules/workflows/backend/tasks/[id]/page.tsx +1 -1
  134. package/src/modules/workflows/backend/tasks/page.tsx +5 -6
  135. package/src/modules/workflows/cli.ts +111 -0
  136. package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +581 -0
  137. package/src/modules/workflows/components/EventTriggersEditor.tsx +664 -0
  138. package/src/modules/workflows/data/entities.ts +124 -0
  139. package/src/modules/workflows/data/validators.ts +138 -0
  140. package/src/modules/workflows/events.ts +49 -0
  141. package/src/modules/workflows/examples/checkout-demo-definition.json +1 -5
  142. package/src/modules/workflows/examples/order-approval-definition.json +257 -0
  143. package/src/modules/workflows/examples/order-approval-guard-rules.json +32 -0
  144. package/src/modules/workflows/i18n/en.json +71 -0
  145. package/src/modules/workflows/lib/activity-executor.ts +129 -16
  146. package/src/modules/workflows/lib/event-trigger-service.ts +557 -0
  147. package/src/modules/workflows/lib/graph-utils.ts +117 -2
  148. package/src/modules/workflows/lib/seeds.ts +34 -8
  149. package/src/modules/workflows/lib/start-validator.ts +38 -28
  150. package/src/modules/workflows/lib/transition-handler.ts +208 -55
  151. package/src/modules/workflows/migrations/Migration20260123143500.ts +38 -0
  152. package/src/modules/workflows/subscribers/event-trigger.ts +109 -0
  153. package/src/modules/workflows/widgets/injection/order-approval/widget.client.tsx +446 -0
  154. package/src/modules/workflows/widgets/injection/order-approval/widget.ts +16 -0
  155. package/src/modules/workflows/widgets/injection-table.ts +21 -0
@@ -6,11 +6,15 @@ import checkoutDemoDefinition from "../examples/checkout-demo-definition.json";
6
6
  import guardRulesExample from "../examples/guard-rules-example.json";
7
7
  import salesPipelineDefinition from "../examples/sales-pipeline-definition.json";
8
8
  import simpleApprovalDefinition from "../examples/simple-approval-definition.json";
9
+ import orderApprovalDefinition from "../examples/order-approval-definition.json";
10
+ import orderApprovalGuardRules from "../examples/order-approval-guard-rules.json";
9
11
  const embeddedSeeds = {
10
12
  "checkout-demo-definition.json": checkoutDemoDefinition,
11
13
  "guard-rules-example.json": guardRulesExample,
12
14
  "sales-pipeline-definition.json": salesPipelineDefinition,
13
- "simple-approval-definition.json": simpleApprovalDefinition
15
+ "simple-approval-definition.json": simpleApprovalDefinition,
16
+ "order-approval-definition.json": orderApprovalDefinition,
17
+ "order-approval-guard-rules.json": orderApprovalGuardRules
14
18
  };
15
19
  function readExampleJson(fileName) {
16
20
  const embedded = embeddedSeeds[fileName];
@@ -41,12 +45,23 @@ async function seedWorkflowDefinition(em, scope, fileName) {
41
45
  organizationId: scope.organizationId
42
46
  });
43
47
  if (existing) {
48
+ const seedStepCount = seed.definition.steps.length;
49
+ const existingStepCount = existing.definition.steps.length;
50
+ const seedTransitionCount = seed.definition.transitions.length;
51
+ const existingTransitionCount = existing.definition.transitions.length;
52
+ const seedHasTransitionPreConditions = seed.definition.transitions.some(
53
+ (t) => t.preConditions && t.preConditions.length > 0
54
+ );
55
+ const existingHasTransitionPreConditions = existing.definition.transitions.some(
56
+ (t) => t.preConditions && t.preConditions.length > 0
57
+ );
44
58
  const seedStartStep = seed.definition.steps.find((s) => s.stepType === "START");
45
59
  const existingStartStep = existing.definition.steps.find((s) => s.stepType === "START");
46
- const seedHasPreConditions = seedStartStep?.preConditions && seedStartStep.preConditions.length > 0;
47
- const existingHasPreConditions = existingStartStep?.preConditions && existingStartStep.preConditions.length > 0;
48
- if (seedHasPreConditions && !existingHasPreConditions) {
49
- console.log(`[seed] Updating workflow ${workflowId} with preConditions`);
60
+ const seedHasStartPreConditions = seedStartStep?.preConditions && seedStartStep.preConditions.length > 0;
61
+ const existingHasStartPreConditions = existingStartStep?.preConditions && existingStartStep.preConditions.length > 0;
62
+ const needsUpdate = seedStepCount !== existingStepCount || seedTransitionCount !== existingTransitionCount || seedHasStartPreConditions && !existingHasStartPreConditions || seedHasTransitionPreConditions && !existingHasTransitionPreConditions;
63
+ if (needsUpdate) {
64
+ console.log(`[seed] Updating workflow ${workflowId} (steps: ${existingStepCount}\u2192${seedStepCount}, transitions: ${existingTransitionCount}\u2192${seedTransitionCount})`);
50
65
  existing.definition = seed.definition;
51
66
  await em.flush();
52
67
  return true;
@@ -109,6 +124,8 @@ async function seedExampleWorkflows(em, scope) {
109
124
  await seedGuardRules(em, scope, "guard-rules-example.json");
110
125
  await seedWorkflowDefinition(em, scope, "sales-pipeline-definition.json");
111
126
  await seedWorkflowDefinition(em, scope, "simple-approval-definition.json");
127
+ await seedGuardRules(em, scope, "order-approval-guard-rules.json");
128
+ await seedWorkflowDefinition(em, scope, "order-approval-definition.json");
112
129
  }
113
130
  export {
114
131
  seedExampleWorkflows
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/workflows/lib/seeds.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { WorkflowDefinition, type WorkflowDefinitionData } from '../data/entities'\nimport { BusinessRule, type RuleType } from '@open-mercato/core/modules/business_rules/data/entities'\nimport checkoutDemoDefinition from '../examples/checkout-demo-definition.json'\nimport guardRulesExample from '../examples/guard-rules-example.json'\nimport salesPipelineDefinition from '../examples/sales-pipeline-definition.json'\nimport simpleApprovalDefinition from '../examples/simple-approval-definition.json'\n\nexport type WorkflowSeedScope = { tenantId: string; organizationId: string }\n\ntype WorkflowSeedDefinition = {\n workflowId: string\n workflowName: string\n description?: string | null\n version?: number\n definition: WorkflowDefinitionData\n metadata?: Record<string, unknown> | null\n enabled?: boolean\n effectiveFrom?: string | null\n effectiveTo?: string | null\n createdBy?: string | null\n updatedBy?: string | null\n}\n\ntype GuardRuleSeed = {\n ruleId: string\n ruleName: string\n ruleType: RuleType\n entityType: string\n conditionExpression: unknown\n eventType?: string | null\n ruleCategory?: string | null\n description?: string | null\n successActions?: unknown\n failureActions?: unknown\n enabled?: boolean\n priority?: number\n version?: number\n effectiveFrom?: string | null\n effectiveTo?: string | null\n createdBy?: string | null\n updatedBy?: string | null\n tagsJson?: string[]\n labelsJson?: Record<string, string>\n}\n\nconst embeddedSeeds: Record<string, unknown> = {\n 'checkout-demo-definition.json': checkoutDemoDefinition,\n 'guard-rules-example.json': guardRulesExample,\n 'sales-pipeline-definition.json': salesPipelineDefinition,\n 'simple-approval-definition.json': simpleApprovalDefinition,\n}\n\nfunction readExampleJson<T>(fileName: string): T {\n const embedded = embeddedSeeds[fileName]\n if (embedded) {\n return embedded as T\n }\n const candidates = [\n path.join(__dirname, '..', 'examples', fileName),\n path.join(process.cwd(), 'packages', 'core', 'src', 'modules', 'workflows', 'examples', fileName),\n path.join(process.cwd(), 'src', 'modules', 'workflows', 'examples', fileName),\n ]\n const filePath = candidates.find((candidate) => fs.existsSync(candidate))\n if (!filePath) {\n throw new Error(`Missing workflow seed file: ${fileName}`)\n }\n return JSON.parse(fs.readFileSync(filePath, 'utf8')) as T\n}\n\nfunction requireString(value: unknown, label: string): string {\n if (typeof value === 'string' && value.trim().length > 0) return value\n throw new Error(`Invalid ${label} in workflow seed data.`)\n}\n\nasync function seedWorkflowDefinition(\n em: EntityManager,\n scope: WorkflowSeedScope,\n fileName: string,\n): Promise<boolean> {\n const seed = readExampleJson<WorkflowSeedDefinition>(fileName)\n const workflowId = requireString(seed.workflowId, 'workflowId')\n\n const existing = await em.findOne(WorkflowDefinition, {\n workflowId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n\n if (existing) {\n // Check if the definition needs to be updated (e.g., missing preConditions on START step)\n const seedStartStep = seed.definition.steps.find((s: any) => s.stepType === 'START')\n const existingStartStep = existing.definition.steps.find((s: any) => s.stepType === 'START')\n\n const seedHasPreConditions = seedStartStep?.preConditions && seedStartStep.preConditions.length > 0\n const existingHasPreConditions = existingStartStep?.preConditions && existingStartStep.preConditions.length > 0\n\n // Update if seed has preConditions but existing doesn't\n if (seedHasPreConditions && !existingHasPreConditions) {\n console.log(`[seed] Updating workflow ${workflowId} with preConditions`)\n existing.definition = seed.definition\n await em.flush()\n return true\n }\n\n return false\n }\n\n const workflow = em.create(WorkflowDefinition, {\n ...seed,\n workflowId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n em.persist(workflow)\n await em.flush()\n return true\n}\n\nasync function seedGuardRules(\n em: EntityManager,\n scope: WorkflowSeedScope,\n fileName: string,\n): Promise<{ seeded: number; skipped: number; updated: number }> {\n const seeds = readExampleJson<GuardRuleSeed[]>(fileName)\n if (!Array.isArray(seeds)) {\n throw new Error('Invalid guard rules seed data.')\n }\n\n let seeded = 0\n let skipped = 0\n let updated = 0\n for (const rule of seeds) {\n const ruleId = requireString(rule.ruleId, 'ruleId')\n const existing = await em.findOne(BusinessRule, {\n ruleId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n if (existing) {\n // Check if entityType or eventType needs updating\n const needsUpdate = existing.entityType !== rule.entityType || existing.eventType !== rule.eventType\n if (needsUpdate) {\n console.log(`[seed] Updating business rule ${ruleId}: entityType=${rule.entityType}, eventType=${rule.eventType}`)\n existing.entityType = rule.entityType\n existing.eventType = rule.eventType ?? null\n updated += 1\n } else {\n skipped += 1\n }\n continue\n }\n const entry = em.create(BusinessRule, {\n ...rule,\n ruleId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n em.persist(entry)\n seeded += 1\n }\n if (seeded > 0 || updated > 0) {\n await em.flush()\n }\n return { seeded, skipped, updated }\n}\n\nexport async function seedExampleWorkflows(em: EntityManager, scope: WorkflowSeedScope): Promise<void> {\n await seedWorkflowDefinition(em, scope, 'checkout-demo-definition.json')\n await seedGuardRules(em, scope, 'guard-rules-example.json')\n await seedWorkflowDefinition(em, scope, 'sales-pipeline-definition.json')\n await seedWorkflowDefinition(em, scope, 'simple-approval-definition.json')\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { WorkflowDefinition, type WorkflowDefinitionData } from '../data/entities'\nimport { BusinessRule, type RuleType } from '@open-mercato/core/modules/business_rules/data/entities'\nimport checkoutDemoDefinition from '../examples/checkout-demo-definition.json'\nimport guardRulesExample from '../examples/guard-rules-example.json'\nimport salesPipelineDefinition from '../examples/sales-pipeline-definition.json'\nimport simpleApprovalDefinition from '../examples/simple-approval-definition.json'\nimport orderApprovalDefinition from '../examples/order-approval-definition.json'\nimport orderApprovalGuardRules from '../examples/order-approval-guard-rules.json'\n\nexport type WorkflowSeedScope = { tenantId: string; organizationId: string }\n\ntype WorkflowSeedDefinition = {\n workflowId: string\n workflowName: string\n description?: string | null\n version?: number\n definition: WorkflowDefinitionData\n metadata?: Record<string, unknown> | null\n enabled?: boolean\n effectiveFrom?: string | null\n effectiveTo?: string | null\n createdBy?: string | null\n updatedBy?: string | null\n}\n\ntype GuardRuleSeed = {\n ruleId: string\n ruleName: string\n ruleType: RuleType\n entityType: string\n conditionExpression: unknown\n eventType?: string | null\n ruleCategory?: string | null\n description?: string | null\n successActions?: unknown\n failureActions?: unknown\n enabled?: boolean\n priority?: number\n version?: number\n effectiveFrom?: string | null\n effectiveTo?: string | null\n createdBy?: string | null\n updatedBy?: string | null\n tagsJson?: string[]\n labelsJson?: Record<string, string>\n}\n\nconst embeddedSeeds: Record<string, unknown> = {\n 'checkout-demo-definition.json': checkoutDemoDefinition,\n 'guard-rules-example.json': guardRulesExample,\n 'sales-pipeline-definition.json': salesPipelineDefinition,\n 'simple-approval-definition.json': simpleApprovalDefinition,\n 'order-approval-definition.json': orderApprovalDefinition,\n 'order-approval-guard-rules.json': orderApprovalGuardRules,\n}\n\nfunction readExampleJson<T>(fileName: string): T {\n const embedded = embeddedSeeds[fileName]\n if (embedded) {\n return embedded as T\n }\n const candidates = [\n path.join(__dirname, '..', 'examples', fileName),\n path.join(process.cwd(), 'packages', 'core', 'src', 'modules', 'workflows', 'examples', fileName),\n path.join(process.cwd(), 'src', 'modules', 'workflows', 'examples', fileName),\n ]\n const filePath = candidates.find((candidate) => fs.existsSync(candidate))\n if (!filePath) {\n throw new Error(`Missing workflow seed file: ${fileName}`)\n }\n return JSON.parse(fs.readFileSync(filePath, 'utf8')) as T\n}\n\nfunction requireString(value: unknown, label: string): string {\n if (typeof value === 'string' && value.trim().length > 0) return value\n throw new Error(`Invalid ${label} in workflow seed data.`)\n}\n\nasync function seedWorkflowDefinition(\n em: EntityManager,\n scope: WorkflowSeedScope,\n fileName: string,\n): Promise<boolean> {\n const seed = readExampleJson<WorkflowSeedDefinition>(fileName)\n const workflowId = requireString(seed.workflowId, 'workflowId')\n\n const existing = await em.findOne(WorkflowDefinition, {\n workflowId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n\n if (existing) {\n // Check if the definition needs to be updated by comparing steps and transitions\n const seedStepCount = seed.definition.steps.length\n const existingStepCount = existing.definition.steps.length\n const seedTransitionCount = seed.definition.transitions.length\n const existingTransitionCount = existing.definition.transitions.length\n\n // Check for preConditions on transitions\n const seedHasTransitionPreConditions = seed.definition.transitions.some(\n (t: any) => t.preConditions && t.preConditions.length > 0\n )\n const existingHasTransitionPreConditions = existing.definition.transitions.some(\n (t: any) => t.preConditions && t.preConditions.length > 0\n )\n\n // Check for preConditions on START step\n const seedStartStep = seed.definition.steps.find((s: any) => s.stepType === 'START')\n const existingStartStep = existing.definition.steps.find((s: any) => s.stepType === 'START')\n const seedHasStartPreConditions = seedStartStep?.preConditions && seedStartStep.preConditions.length > 0\n const existingHasStartPreConditions = existingStartStep?.preConditions && existingStartStep.preConditions.length > 0\n\n // Update if structure has changed\n const needsUpdate =\n seedStepCount !== existingStepCount ||\n seedTransitionCount !== existingTransitionCount ||\n (seedHasStartPreConditions && !existingHasStartPreConditions) ||\n (seedHasTransitionPreConditions && !existingHasTransitionPreConditions)\n\n if (needsUpdate) {\n console.log(`[seed] Updating workflow ${workflowId} (steps: ${existingStepCount}\u2192${seedStepCount}, transitions: ${existingTransitionCount}\u2192${seedTransitionCount})`)\n existing.definition = seed.definition\n await em.flush()\n return true\n }\n\n return false\n }\n\n const workflow = em.create(WorkflowDefinition, {\n ...seed,\n workflowId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n em.persist(workflow)\n await em.flush()\n return true\n}\n\nasync function seedGuardRules(\n em: EntityManager,\n scope: WorkflowSeedScope,\n fileName: string,\n): Promise<{ seeded: number; skipped: number; updated: number }> {\n const seeds = readExampleJson<GuardRuleSeed[]>(fileName)\n if (!Array.isArray(seeds)) {\n throw new Error('Invalid guard rules seed data.')\n }\n\n let seeded = 0\n let skipped = 0\n let updated = 0\n for (const rule of seeds) {\n const ruleId = requireString(rule.ruleId, 'ruleId')\n const existing = await em.findOne(BusinessRule, {\n ruleId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n if (existing) {\n // Check if entityType or eventType needs updating\n const needsUpdate = existing.entityType !== rule.entityType || existing.eventType !== rule.eventType\n if (needsUpdate) {\n console.log(`[seed] Updating business rule ${ruleId}: entityType=${rule.entityType}, eventType=${rule.eventType}`)\n existing.entityType = rule.entityType\n existing.eventType = rule.eventType ?? null\n updated += 1\n } else {\n skipped += 1\n }\n continue\n }\n const entry = em.create(BusinessRule, {\n ...rule,\n ruleId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n em.persist(entry)\n seeded += 1\n }\n if (seeded > 0 || updated > 0) {\n await em.flush()\n }\n return { seeded, skipped, updated }\n}\n\nexport async function seedExampleWorkflows(em: EntityManager, scope: WorkflowSeedScope): Promise<void> {\n await seedWorkflowDefinition(em, scope, 'checkout-demo-definition.json')\n await seedGuardRules(em, scope, 'guard-rules-example.json')\n await seedWorkflowDefinition(em, scope, 'sales-pipeline-definition.json')\n await seedWorkflowDefinition(em, scope, 'simple-approval-definition.json')\n // Seed order approval guard rules before the workflow definition\n await seedGuardRules(em, scope, 'order-approval-guard-rules.json')\n await seedWorkflowDefinition(em, scope, 'order-approval-definition.json')\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -58,50 +58,60 @@ async function validateWorkflowStart(em, options) {
58
58
  const errors = [];
59
59
  const validatedRules = [];
60
60
  for (const condition of preConditions) {
61
- const ruleContext = {
62
- entityType: `workflow:${workflowId}:start`,
63
- entityId: "pre_start_validation",
64
- eventType: "validate_start",
61
+ const result = await ruleEngine.executeRuleByRuleId(em, {
62
+ ruleId: condition.ruleId,
63
+ // String identifier like "workflow_checkout_inventory_available"
65
64
  data: {
66
65
  workflowId,
67
66
  workflowContext: context
68
67
  },
69
68
  tenantId,
70
69
  organizationId,
70
+ entityType: `workflow:${workflowId}:start`,
71
+ entityId: "pre_start_validation",
72
+ eventType: "validate_start",
71
73
  dryRun: true
72
74
  // Don't log execution during validation
73
- };
74
- const rules = await ruleEngine.findApplicableRules(em, {
75
- entityType: ruleContext.entityType,
76
- eventType: ruleContext.eventType,
77
- tenantId,
78
- organizationId,
79
- ruleType: "GUARD"
80
75
  });
81
- const rule = rules.find((r) => r.ruleId === condition.ruleId);
82
- if (!rule) {
76
+ validatedRules.push({
77
+ ruleId: condition.ruleId,
78
+ passed: result.conditionResult,
79
+ executionTime: result.executionTime
80
+ });
81
+ if (result.error === "Rule not found") {
83
82
  if (condition.required) {
84
83
  errors.push({
85
84
  ruleId: condition.ruleId,
86
- message: getLocalizedMessage(condition, null, locale, `Business rule '${condition.ruleId}' not found`),
85
+ message: getLocalizedMessage(condition, null, locale, `Business rule not found: ${condition.ruleId}`),
87
86
  code: "RULE_NOT_FOUND"
88
87
  });
89
- validatedRules.push({ ruleId: condition.ruleId, passed: false });
90
88
  }
91
89
  continue;
92
90
  }
93
- const result = await ruleEngine.executeSingleRule(em, rule, ruleContext);
94
- validatedRules.push({
95
- ruleId: condition.ruleId,
96
- passed: result.conditionResult,
97
- executionTime: result.executionTime
98
- });
91
+ if (result.error === "Rule is disabled") {
92
+ if (condition.required) {
93
+ errors.push({
94
+ ruleId: condition.ruleId,
95
+ message: getLocalizedMessage(condition, null, locale, `Business rule is disabled: ${result.ruleName}`),
96
+ code: "RULE_DISABLED"
97
+ });
98
+ }
99
+ continue;
100
+ }
101
+ if (result.error && condition.required) {
102
+ errors.push({
103
+ ruleId: condition.ruleId,
104
+ message: getLocalizedMessage(condition, null, locale, `Rule error: ${result.error}`),
105
+ code: "RULE_ERROR"
106
+ });
107
+ continue;
108
+ }
99
109
  if (!result.conditionResult && condition.required) {
100
110
  const message = getLocalizedMessage(
101
111
  condition,
102
- rule,
112
+ null,
103
113
  locale,
104
- `Pre-condition '${rule.ruleName || condition.ruleId}' failed`
114
+ `Pre-condition '${result.ruleName || condition.ruleId}' failed`
105
115
  );
106
116
  errors.push({
107
117
  ruleId: condition.ruleId,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/workflows/lib/start-validator.ts"],
4
- "sourcesContent": ["/**\n * Workflows Module - Start Validator Service\n *\n * Validates pre-conditions on START step before workflow instance creation.\n * This enables guard rules that determine whether a workflow can be started\n * based on the initial context provided.\n */\n\nimport { EntityManager } from '@mikro-orm/core'\nimport { WorkflowDefinition } from '../data/entities'\nimport * as ruleEngine from '../../business_rules/lib/rule-engine'\nimport type { StartPreCondition } from '../data/validators'\n\n// ============================================================================\n// Types and Interfaces\n// ============================================================================\n\nexport interface ValidateStartOptions {\n workflowId: string\n version?: number\n context: Record<string, any>\n locale?: string\n tenantId: string\n organizationId: string\n}\n\nexport interface ValidationError {\n ruleId: string\n message: string\n code: string\n}\n\nexport interface ValidatedRule {\n ruleId: string\n passed: boolean\n executionTime?: number\n}\n\nexport interface ValidateStartResult {\n canStart: boolean\n errors: ValidationError[]\n validatedRules: ValidatedRule[]\n}\n\n// ============================================================================\n// Main Validation Function\n// ============================================================================\n\n/**\n * Validate if a workflow can be started with the given context\n *\n * @param em - Entity manager for database operations\n * @param options - Validation options including workflowId, context, and scope\n * @returns Validation result with canStart flag, errors, and validated rules\n */\nexport async function validateWorkflowStart(\n em: EntityManager,\n options: ValidateStartOptions\n): Promise<ValidateStartResult> {\n const { workflowId, version, context, locale = 'en', tenantId, organizationId } = options\n\n // Find workflow definition\n const definition = await findWorkflowDefinition(em, {\n workflowId,\n version,\n tenantId,\n organizationId,\n })\n\n if (!definition) {\n return {\n canStart: false,\n errors: [{\n ruleId: '_DEFINITION_NOT_FOUND',\n message: `Workflow definition not found: ${workflowId}`,\n code: 'DEFINITION_NOT_FOUND',\n }],\n validatedRules: [],\n }\n }\n\n if (!definition.enabled) {\n return {\n canStart: false,\n errors: [{\n ruleId: '_DEFINITION_DISABLED',\n message: `Workflow is disabled: ${workflowId}`,\n code: 'DEFINITION_DISABLED',\n }],\n validatedRules: [],\n }\n }\n\n // Find START step and get pre-conditions\n const startStep = definition.definition.steps.find(\n (s: any) => s.stepType === 'START'\n )\n\n if (!startStep) {\n return {\n canStart: false,\n errors: [{\n ruleId: '_NO_START_STEP',\n message: 'Workflow has no START step',\n code: 'INVALID_DEFINITION',\n }],\n validatedRules: [],\n }\n }\n\n const preConditions: StartPreCondition[] = startStep.preConditions || []\n\n console.log('[start-validator] START step:', JSON.stringify(startStep, null, 2))\n console.log('[start-validator] preConditions:', preConditions.length, JSON.stringify(preConditions))\n\n // If no pre-conditions, workflow can start\n if (preConditions.length === 0) {\n console.log('[start-validator] No pre-conditions defined, allowing start')\n return {\n canStart: true,\n errors: [],\n validatedRules: [],\n }\n }\n\n // Evaluate each pre-condition using rule engine\n const errors: ValidationError[] = []\n const validatedRules: ValidatedRule[] = []\n\n for (const condition of preConditions) {\n const ruleContext: ruleEngine.RuleEngineContext = {\n entityType: `workflow:${workflowId}:start`,\n entityId: 'pre_start_validation',\n eventType: 'validate_start',\n data: {\n workflowId,\n workflowContext: context,\n },\n tenantId,\n organizationId,\n dryRun: true, // Don't log execution during validation\n }\n\n // Find applicable rules for this context\n const rules = await ruleEngine.findApplicableRules(em, {\n entityType: ruleContext.entityType,\n eventType: ruleContext.eventType,\n tenantId,\n organizationId,\n ruleType: 'GUARD',\n })\n\n const rule = rules.find(r => r.ruleId === condition.ruleId)\n\n if (!rule) {\n // Rule not found - if required, this is an error\n if (condition.required) {\n errors.push({\n ruleId: condition.ruleId,\n message: getLocalizedMessage(condition, null, locale, `Business rule '${condition.ruleId}' not found`),\n code: 'RULE_NOT_FOUND',\n })\n validatedRules.push({ ruleId: condition.ruleId, passed: false })\n }\n continue\n }\n\n // Execute the single rule\n const result = await ruleEngine.executeSingleRule(em, rule, ruleContext)\n\n validatedRules.push({\n ruleId: condition.ruleId,\n passed: result.conditionResult,\n executionTime: result.executionTime,\n })\n\n if (!result.conditionResult && condition.required) {\n // Get localized message from condition, rule failure actions, or default\n const message = getLocalizedMessage(\n condition,\n rule,\n locale,\n `Pre-condition '${rule.ruleName || condition.ruleId}' failed`\n )\n errors.push({\n ruleId: condition.ruleId,\n message,\n code: 'PRE_CONDITION_FAILED',\n })\n }\n }\n\n return {\n canStart: errors.length === 0,\n errors,\n validatedRules,\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Get localized message with fallback chain\n *\n * Priority:\n * 1. condition.validationMessage[locale]\n * 2. condition.validationMessage['en']\n * 3. rule.failureActions[BLOCK_TRANSITION].config.message\n * 4. rule.description\n * 5. defaultMessage\n */\nfunction getLocalizedMessage(\n condition: StartPreCondition,\n rule: any | null,\n locale: string,\n defaultMessage: string\n): string {\n // Priority 1: Localized message in condition definition (requested locale)\n if (condition.validationMessage?.[locale]) {\n return condition.validationMessage[locale]\n }\n\n // Priority 2: English fallback in condition\n if (condition.validationMessage?.['en']) {\n return condition.validationMessage['en']\n }\n\n if (rule) {\n // Priority 3: Message from rule's failureActions\n if (rule.failureActions && Array.isArray(rule.failureActions)) {\n const blockAction = rule.failureActions.find(\n (a: any) => a.type === 'BLOCK_TRANSITION'\n )\n if (blockAction?.config?.message) {\n return blockAction.config.message\n }\n }\n\n // Priority 4: Rule description\n if (rule.description) {\n return rule.description\n }\n }\n\n // Priority 5: Default message\n return defaultMessage\n}\n\n/**\n * Find workflow definition by ID and optional version\n */\nasync function findWorkflowDefinition(\n em: EntityManager,\n options: {\n workflowId: string\n version?: number\n tenantId: string\n organizationId: string\n }\n): Promise<WorkflowDefinition | null> {\n const { workflowId, version, tenantId, organizationId } = options\n\n const where: any = {\n workflowId,\n tenantId,\n organizationId,\n deletedAt: null,\n }\n\n if (version !== undefined) {\n where.version = version\n }\n\n // If no version specified, get latest enabled version\n if (version === undefined) {\n where.enabled = true\n return em.findOne(WorkflowDefinition, where, {\n orderBy: { version: 'DESC' },\n })\n }\n\n return em.findOne(WorkflowDefinition, where)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Workflows Module - Start Validator Service\n *\n * Validates pre-conditions on START step before workflow instance creation.\n * This enables guard rules that determine whether a workflow can be started\n * based on the initial context provided.\n */\n\nimport { EntityManager } from '@mikro-orm/core'\nimport { WorkflowDefinition } from '../data/entities'\nimport * as ruleEngine from '../../business_rules/lib/rule-engine'\nimport type { StartPreCondition } from '../data/validators'\n\n// ============================================================================\n// Types and Interfaces\n// ============================================================================\n\nexport interface ValidateStartOptions {\n workflowId: string\n version?: number\n context: Record<string, any>\n locale?: string\n tenantId: string\n organizationId: string\n}\n\nexport interface ValidationError {\n ruleId: string\n message: string\n code: string\n}\n\nexport interface ValidatedRule {\n ruleId: string\n passed: boolean\n executionTime?: number\n}\n\nexport interface ValidateStartResult {\n canStart: boolean\n errors: ValidationError[]\n validatedRules: ValidatedRule[]\n}\n\n// ============================================================================\n// Main Validation Function\n// ============================================================================\n\n/**\n * Validate if a workflow can be started with the given context\n *\n * @param em - Entity manager for database operations\n * @param options - Validation options including workflowId, context, and scope\n * @returns Validation result with canStart flag, errors, and validated rules\n */\nexport async function validateWorkflowStart(\n em: EntityManager,\n options: ValidateStartOptions\n): Promise<ValidateStartResult> {\n const { workflowId, version, context, locale = 'en', tenantId, organizationId } = options\n\n // Find workflow definition\n const definition = await findWorkflowDefinition(em, {\n workflowId,\n version,\n tenantId,\n organizationId,\n })\n\n if (!definition) {\n return {\n canStart: false,\n errors: [{\n ruleId: '_DEFINITION_NOT_FOUND',\n message: `Workflow definition not found: ${workflowId}`,\n code: 'DEFINITION_NOT_FOUND',\n }],\n validatedRules: [],\n }\n }\n\n if (!definition.enabled) {\n return {\n canStart: false,\n errors: [{\n ruleId: '_DEFINITION_DISABLED',\n message: `Workflow is disabled: ${workflowId}`,\n code: 'DEFINITION_DISABLED',\n }],\n validatedRules: [],\n }\n }\n\n // Find START step and get pre-conditions\n const startStep = definition.definition.steps.find(\n (s: any) => s.stepType === 'START'\n )\n\n if (!startStep) {\n return {\n canStart: false,\n errors: [{\n ruleId: '_NO_START_STEP',\n message: 'Workflow has no START step',\n code: 'INVALID_DEFINITION',\n }],\n validatedRules: [],\n }\n }\n\n const preConditions: StartPreCondition[] = startStep.preConditions || []\n\n console.log('[start-validator] START step:', JSON.stringify(startStep, null, 2))\n console.log('[start-validator] preConditions:', preConditions.length, JSON.stringify(preConditions))\n\n // If no pre-conditions, workflow can start\n if (preConditions.length === 0) {\n console.log('[start-validator] No pre-conditions defined, allowing start')\n return {\n canStart: true,\n errors: [],\n validatedRules: [],\n }\n }\n\n // Evaluate each pre-condition using rule engine\n const errors: ValidationError[] = []\n const validatedRules: ValidatedRule[] = []\n\n for (const condition of preConditions) {\n // Execute rule directly by string rule_id\n const result = await ruleEngine.executeRuleByRuleId(em, {\n ruleId: condition.ruleId, // String identifier like \"workflow_checkout_inventory_available\"\n data: {\n workflowId,\n workflowContext: context,\n },\n tenantId,\n organizationId,\n entityType: `workflow:${workflowId}:start`,\n entityId: 'pre_start_validation',\n eventType: 'validate_start',\n dryRun: true, // Don't log execution during validation\n })\n\n validatedRules.push({\n ruleId: condition.ruleId,\n passed: result.conditionResult,\n executionTime: result.executionTime,\n })\n\n // Handle rule not found\n if (result.error === 'Rule not found') {\n if (condition.required) {\n errors.push({\n ruleId: condition.ruleId,\n message: getLocalizedMessage(condition, null, locale, `Business rule not found: ${condition.ruleId}`),\n code: 'RULE_NOT_FOUND',\n })\n }\n continue\n }\n\n // Handle disabled rule\n if (result.error === 'Rule is disabled') {\n if (condition.required) {\n errors.push({\n ruleId: condition.ruleId,\n message: getLocalizedMessage(condition, null, locale, `Business rule is disabled: ${result.ruleName}`),\n code: 'RULE_DISABLED',\n })\n }\n continue\n }\n\n // Handle other errors (not yet effective, expired, etc.)\n if (result.error && condition.required) {\n errors.push({\n ruleId: condition.ruleId,\n message: getLocalizedMessage(condition, null, locale, `Rule error: ${result.error}`),\n code: 'RULE_ERROR',\n })\n continue\n }\n\n // Handle condition failure\n if (!result.conditionResult && condition.required) {\n // Get localized message from condition or use default with rule name\n const message = getLocalizedMessage(\n condition,\n null,\n locale,\n `Pre-condition '${result.ruleName || condition.ruleId}' failed`\n )\n errors.push({\n ruleId: condition.ruleId,\n message,\n code: 'PRE_CONDITION_FAILED',\n })\n }\n }\n\n return {\n canStart: errors.length === 0,\n errors,\n validatedRules,\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Get localized message with fallback chain\n *\n * Priority:\n * 1. condition.validationMessage[locale]\n * 2. condition.validationMessage['en']\n * 3. rule.failureActions[BLOCK_TRANSITION].config.message\n * 4. rule.description\n * 5. defaultMessage\n */\nfunction getLocalizedMessage(\n condition: StartPreCondition,\n rule: any | null,\n locale: string,\n defaultMessage: string\n): string {\n // Priority 1: Localized message in condition definition (requested locale)\n if (condition.validationMessage?.[locale]) {\n return condition.validationMessage[locale]\n }\n\n // Priority 2: English fallback in condition\n if (condition.validationMessage?.['en']) {\n return condition.validationMessage['en']\n }\n\n if (rule) {\n // Priority 3: Message from rule's failureActions\n if (rule.failureActions && Array.isArray(rule.failureActions)) {\n const blockAction = rule.failureActions.find(\n (a: any) => a.type === 'BLOCK_TRANSITION'\n )\n if (blockAction?.config?.message) {\n return blockAction.config.message\n }\n }\n\n // Priority 4: Rule description\n if (rule.description) {\n return rule.description\n }\n }\n\n // Priority 5: Default message\n return defaultMessage\n}\n\n/**\n * Find workflow definition by ID and optional version\n */\nasync function findWorkflowDefinition(\n em: EntityManager,\n options: {\n workflowId: string\n version?: number\n tenantId: string\n organizationId: string\n }\n): Promise<WorkflowDefinition | null> {\n const { workflowId, version, tenantId, organizationId } = options\n\n const where: any = {\n workflowId,\n tenantId,\n organizationId,\n deletedAt: null,\n }\n\n if (version !== undefined) {\n where.version = version\n }\n\n // If no version specified, get latest enabled version\n if (version === undefined) {\n where.enabled = true\n return em.findOne(WorkflowDefinition, where, {\n orderBy: { version: 'DESC' },\n })\n }\n\n return em.findOne(WorkflowDefinition, where)\n}\n"],
5
+ "mappings": "AASA,SAAS,0BAA0B;AACnC,YAAY,gBAAgB;AA6C5B,eAAsB,sBACpB,IACA,SAC8B;AAC9B,QAAM,EAAE,YAAY,SAAS,SAAS,SAAS,MAAM,UAAU,eAAe,IAAI;AAGlF,QAAM,aAAa,MAAM,uBAAuB,IAAI;AAAA,IAClD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,MACL,UAAU;AAAA,MACV,QAAQ,CAAC;AAAA,QACP,QAAQ;AAAA,QACR,SAAS,kCAAkC,UAAU;AAAA,QACrD,MAAM;AAAA,MACR,CAAC;AAAA,MACD,gBAAgB,CAAC;AAAA,IACnB;AAAA,EACF;AAEA,MAAI,CAAC,WAAW,SAAS;AACvB,WAAO;AAAA,MACL,UAAU;AAAA,MACV,QAAQ,CAAC;AAAA,QACP,QAAQ;AAAA,QACR,SAAS,yBAAyB,UAAU;AAAA,QAC5C,MAAM;AAAA,MACR,CAAC;AAAA,MACD,gBAAgB,CAAC;AAAA,IACnB;AAAA,EACF;AAGA,QAAM,YAAY,WAAW,WAAW,MAAM;AAAA,IAC5C,CAAC,MAAW,EAAE,aAAa;AAAA,EAC7B;AAEA,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,MACL,UAAU;AAAA,MACV,QAAQ,CAAC;AAAA,QACP,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,MAAM;AAAA,MACR,CAAC;AAAA,MACD,gBAAgB,CAAC;AAAA,IACnB;AAAA,EACF;AAEA,QAAM,gBAAqC,UAAU,iBAAiB,CAAC;AAEvE,UAAQ,IAAI,iCAAiC,KAAK,UAAU,WAAW,MAAM,CAAC,CAAC;AAC/E,UAAQ,IAAI,oCAAoC,cAAc,QAAQ,KAAK,UAAU,aAAa,CAAC;AAGnG,MAAI,cAAc,WAAW,GAAG;AAC9B,YAAQ,IAAI,6DAA6D;AACzE,WAAO;AAAA,MACL,UAAU;AAAA,MACV,QAAQ,CAAC;AAAA,MACT,gBAAgB,CAAC;AAAA,IACnB;AAAA,EACF;AAGA,QAAM,SAA4B,CAAC;AACnC,QAAM,iBAAkC,CAAC;AAEzC,aAAW,aAAa,eAAe;AAErC,UAAM,SAAS,MAAM,WAAW,oBAAoB,IAAI;AAAA,MACtD,QAAQ,UAAU;AAAA;AAAA,MAClB,MAAM;AAAA,QACJ;AAAA,QACA,iBAAiB;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY,YAAY,UAAU;AAAA,MAClC,UAAU;AAAA,MACV,WAAW;AAAA,MACX,QAAQ;AAAA;AAAA,IACV,CAAC;AAED,mBAAe,KAAK;AAAA,MAClB,QAAQ,UAAU;AAAA,MAClB,QAAQ,OAAO;AAAA,MACf,eAAe,OAAO;AAAA,IACxB,CAAC;AAGD,QAAI,OAAO,UAAU,kBAAkB;AACrC,UAAI,UAAU,UAAU;AACtB,eAAO,KAAK;AAAA,UACV,QAAQ,UAAU;AAAA,UAClB,SAAS,oBAAoB,WAAW,MAAM,QAAQ,4BAA4B,UAAU,MAAM,EAAE;AAAA,UACpG,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AACA;AAAA,IACF;AAGA,QAAI,OAAO,UAAU,oBAAoB;AACvC,UAAI,UAAU,UAAU;AACtB,eAAO,KAAK;AAAA,UACV,QAAQ,UAAU;AAAA,UAClB,SAAS,oBAAoB,WAAW,MAAM,QAAQ,8BAA8B,OAAO,QAAQ,EAAE;AAAA,UACrG,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AACA;AAAA,IACF;AAGA,QAAI,OAAO,SAAS,UAAU,UAAU;AACtC,aAAO,KAAK;AAAA,QACV,QAAQ,UAAU;AAAA,QAClB,SAAS,oBAAoB,WAAW,MAAM,QAAQ,eAAe,OAAO,KAAK,EAAE;AAAA,QACnF,MAAM;AAAA,MACR,CAAC;AACD;AAAA,IACF;AAGA,QAAI,CAAC,OAAO,mBAAmB,UAAU,UAAU;AAEjD,YAAM,UAAU;AAAA,QACd;AAAA,QACA;AAAA,QACA;AAAA,QACA,kBAAkB,OAAO,YAAY,UAAU,MAAM;AAAA,MACvD;AACA,aAAO,KAAK;AAAA,QACV,QAAQ,UAAU;AAAA,QAClB;AAAA,QACA,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,OAAO,WAAW;AAAA,IAC5B;AAAA,IACA;AAAA,EACF;AACF;AAgBA,SAAS,oBACP,WACA,MACA,QACA,gBACQ;AAER,MAAI,UAAU,oBAAoB,MAAM,GAAG;AACzC,WAAO,UAAU,kBAAkB,MAAM;AAAA,EAC3C;AAGA,MAAI,UAAU,oBAAoB,IAAI,GAAG;AACvC,WAAO,UAAU,kBAAkB,IAAI;AAAA,EACzC;AAEA,MAAI,MAAM;AAER,QAAI,KAAK,kBAAkB,MAAM,QAAQ,KAAK,cAAc,GAAG;AAC7D,YAAM,cAAc,KAAK,eAAe;AAAA,QACtC,CAAC,MAAW,EAAE,SAAS;AAAA,MACzB;AACA,UAAI,aAAa,QAAQ,SAAS;AAChC,eAAO,YAAY,OAAO;AAAA,MAC5B;AAAA,IACF;AAGA,QAAI,KAAK,aAAa;AACpB,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AAGA,SAAO;AACT;AAKA,eAAe,uBACb,IACA,SAMoC;AACpC,QAAM,EAAE,YAAY,SAAS,UAAU,eAAe,IAAI;AAE1D,QAAM,QAAa;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,EACb;AAEA,MAAI,YAAY,QAAW;AACzB,UAAM,UAAU;AAAA,EAClB;AAGA,MAAI,YAAY,QAAW;AACzB,UAAM,UAAU;AAChB,WAAO,GAAG,QAAQ,oBAAoB,OAAO;AAAA,MAC3C,SAAS,EAAE,SAAS,OAAO;AAAA,IAC7B,CAAC;AAAA,EACH;AAEA,SAAO,GAAG,QAAQ,oBAAoB,KAAK;AAC7C;",
6
6
  "names": []
7
7
  }
@@ -99,19 +99,43 @@ async function findValidTransitions(em, instance, fromStepId, context) {
99
99
  if (!definition) {
100
100
  return [];
101
101
  }
102
- const transitions = (definition.definition.transitions || []).filter(
103
- (t) => t.fromStepId === fromStepId
104
- );
102
+ const transitions = (definition.definition.transitions || []).filter((t) => t.fromStepId === fromStepId).sort((a, b) => (b.priority || 0) - (a.priority || 0));
105
103
  const results = [];
106
104
  for (const transition of transitions) {
107
- const result = await evaluateTransition(
105
+ const conditionResult = await evaluateTransition(
108
106
  em,
109
107
  instance,
110
108
  fromStepId,
111
109
  transition.toStepId,
112
110
  context
113
111
  );
114
- results.push(result);
112
+ if (!conditionResult.isValid) {
113
+ results.push(conditionResult);
114
+ continue;
115
+ }
116
+ const preConditions = transition.preConditions || [];
117
+ if (preConditions.length > 0) {
118
+ const preConditionsResult = await evaluatePreConditions(
119
+ em,
120
+ instance,
121
+ transition,
122
+ context
123
+ );
124
+ if (!preConditionsResult.allowed) {
125
+ const failedRules = preConditionsResult.executedRules.filter((r) => !r.conditionResult).map((r) => r.rule.ruleId || r.rule.ruleName);
126
+ results.push({
127
+ isValid: false,
128
+ transition,
129
+ reason: `Pre-conditions failed: ${failedRules.join(", ")}`,
130
+ failedConditions: failedRules
131
+ });
132
+ continue;
133
+ }
134
+ }
135
+ results.push({
136
+ ...conditionResult,
137
+ transition
138
+ });
115
139
  }
116
140
  return results;
117
141
  } catch (error) {
@@ -415,28 +439,74 @@ async function evaluatePreConditions(em, instance, transition, context) {
415
439
  totalExecutionTime: 0
416
440
  };
417
441
  }
418
- const ruleContext = {
419
- entityType: `workflow:${definition.workflowId}:transition`,
420
- entityId: transition.transitionId || `${transition.fromStepId}->${transition.toStepId}`,
421
- eventType: "pre_transition",
422
- data: {
423
- workflowInstanceId: instance.id,
424
- workflowId: definition.workflowId,
425
- fromStepId: transition.fromStepId,
426
- toStepId: transition.toStepId,
427
- workflowContext: {
428
- ...instance.context,
429
- ...context.workflowContext
442
+ const preConditions = transition.preConditions || [];
443
+ if (preConditions.length === 0) {
444
+ return {
445
+ allowed: true,
446
+ executedRules: [],
447
+ totalExecutionTime: 0
448
+ };
449
+ }
450
+ const startTime = Date.now();
451
+ const executedRules = [];
452
+ const errors = [];
453
+ let allowed = true;
454
+ for (const condition of preConditions) {
455
+ const result = await ruleEngine.executeRuleByRuleId(em, {
456
+ ruleId: condition.ruleId,
457
+ // String identifier
458
+ data: {
459
+ workflowInstanceId: instance.id,
460
+ workflowId: definition.workflowId,
461
+ fromStepId: transition.fromStepId,
462
+ toStepId: transition.toStepId,
463
+ workflowContext: {
464
+ ...instance.context,
465
+ ...context.workflowContext
466
+ },
467
+ triggerData: context.triggerData
430
468
  },
431
- triggerData: context.triggerData
432
- },
433
- user: context.userId ? { id: context.userId } : void 0,
434
- tenantId: instance.tenantId,
435
- organizationId: instance.organizationId,
436
- executedBy: context.userId
469
+ user: context.userId ? { id: context.userId } : void 0,
470
+ tenantId: instance.tenantId,
471
+ organizationId: instance.organizationId,
472
+ executedBy: context.userId,
473
+ entityType: `workflow:${definition.workflowId}:transition`,
474
+ entityId: transition.transitionId || `${transition.fromStepId}->${transition.toStepId}`,
475
+ eventType: "pre_transition"
476
+ });
477
+ const ruleResult = {
478
+ rule: {
479
+ ruleId: result.ruleId,
480
+ ruleName: result.ruleName,
481
+ ruleType: "GUARD"
482
+ },
483
+ conditionResult: result.conditionResult,
484
+ actionsExecuted: result.actionsExecuted,
485
+ executionTime: result.executionTime,
486
+ error: result.error,
487
+ logId: result.logId
488
+ };
489
+ executedRules.push(ruleResult);
490
+ if (result.error) {
491
+ const isRequired2 = condition.required !== false;
492
+ if (isRequired2) {
493
+ allowed = false;
494
+ errors.push(`Rule '${result.ruleId}': ${result.error}`);
495
+ }
496
+ continue;
497
+ }
498
+ const isRequired = condition.required !== false;
499
+ if (isRequired && !result.conditionResult) {
500
+ allowed = false;
501
+ errors.push(`Pre-condition '${result.ruleName || result.ruleId}' failed`);
502
+ }
503
+ }
504
+ return {
505
+ allowed,
506
+ executedRules,
507
+ totalExecutionTime: Date.now() - startTime,
508
+ errors: errors.length > 0 ? errors : void 0
437
509
  };
438
- const result = await ruleEngine.executeRules(em, ruleContext);
439
- return result;
440
510
  } catch (error) {
441
511
  console.error("Error evaluating pre-conditions:", error);
442
512
  return {
@@ -459,28 +529,70 @@ async function evaluatePostConditions(em, instance, transition, context) {
459
529
  totalExecutionTime: 0
460
530
  };
461
531
  }
462
- const ruleContext = {
463
- entityType: `workflow:${definition.workflowId}:transition`,
464
- entityId: transition.transitionId || `${transition.fromStepId}->${transition.toStepId}`,
465
- eventType: "post_transition",
466
- data: {
467
- workflowInstanceId: instance.id,
468
- workflowId: definition.workflowId,
469
- fromStepId: transition.fromStepId,
470
- toStepId: transition.toStepId,
471
- workflowContext: {
472
- ...instance.context,
473
- ...context.workflowContext
532
+ const postConditions = transition.postConditions || [];
533
+ if (postConditions.length === 0) {
534
+ return {
535
+ allowed: true,
536
+ executedRules: [],
537
+ totalExecutionTime: 0
538
+ };
539
+ }
540
+ const startTime = Date.now();
541
+ const executedRules = [];
542
+ const errors = [];
543
+ let allowed = true;
544
+ for (const condition of postConditions) {
545
+ const result = await ruleEngine.executeRuleByRuleId(em, {
546
+ ruleId: condition.ruleId,
547
+ // String identifier
548
+ data: {
549
+ workflowInstanceId: instance.id,
550
+ workflowId: definition.workflowId,
551
+ fromStepId: transition.fromStepId,
552
+ toStepId: transition.toStepId,
553
+ workflowContext: {
554
+ ...instance.context,
555
+ ...context.workflowContext
556
+ },
557
+ triggerData: context.triggerData
474
558
  },
475
- triggerData: context.triggerData
476
- },
477
- user: context.userId ? { id: context.userId } : void 0,
478
- tenantId: instance.tenantId,
479
- organizationId: instance.organizationId,
480
- executedBy: context.userId
559
+ user: context.userId ? { id: context.userId } : void 0,
560
+ tenantId: instance.tenantId,
561
+ organizationId: instance.organizationId,
562
+ executedBy: context.userId,
563
+ entityType: `workflow:${definition.workflowId}:transition`,
564
+ entityId: transition.transitionId || `${transition.fromStepId}->${transition.toStepId}`,
565
+ eventType: "post_transition"
566
+ });
567
+ const ruleResult = {
568
+ rule: {
569
+ ruleId: result.ruleId,
570
+ ruleName: result.ruleName,
571
+ ruleType: "GUARD"
572
+ },
573
+ conditionResult: result.conditionResult,
574
+ actionsExecuted: result.actionsExecuted,
575
+ executionTime: result.executionTime,
576
+ error: result.error,
577
+ logId: result.logId
578
+ };
579
+ executedRules.push(ruleResult);
580
+ if (result.error) {
581
+ errors.push(`Rule '${result.ruleId}': ${result.error}`);
582
+ allowed = false;
583
+ continue;
584
+ }
585
+ if (!result.conditionResult) {
586
+ allowed = false;
587
+ errors.push(`Post-condition '${result.ruleName || result.ruleId}' failed`);
588
+ }
589
+ }
590
+ return {
591
+ allowed,
592
+ executedRules,
593
+ totalExecutionTime: Date.now() - startTime,
594
+ errors: errors.length > 0 ? errors : void 0
481
595
  };
482
- const result = await ruleEngine.executeRules(em, ruleContext);
483
- return result;
484
596
  } catch (error) {
485
597
  console.error("Error evaluating post-conditions:", error);
486
598
  return {