@open-mercato/core 0.9.1-develop.7519.1.38f51f8dbb → 0.9.1-develop.7532.1.8eac7e198f

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 (82) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/api/logout.js +6 -1
  3. package/dist/modules/auth/api/logout.js.map +2 -2
  4. package/dist/modules/auth/api/session/refresh.js +6 -1
  5. package/dist/modules/auth/api/session/refresh.js.map +2 -2
  6. package/dist/modules/catalog/ai-tools/mutation-pack.js +10 -5
  7. package/dist/modules/catalog/ai-tools/mutation-pack.js.map +2 -2
  8. package/dist/modules/catalog/ai-tools/types.js.map +2 -2
  9. package/dist/modules/customer_accounts/api/login.js +14 -7
  10. package/dist/modules/customer_accounts/api/login.js.map +2 -2
  11. package/dist/modules/customer_accounts/services/customerUserService.js +10 -10
  12. package/dist/modules/customer_accounts/services/customerUserService.js.map +2 -2
  13. package/dist/modules/forms/api/distributions/[distributionId]/invitations/[invitationId]/route.js +1 -1
  14. package/dist/modules/forms/api/distributions/[distributionId]/invitations/[invitationId]/route.js.map +1 -1
  15. package/dist/modules/forms/api/form-submissions/[id]/submit/route.js +12 -2
  16. package/dist/modules/forms/api/form-submissions/[id]/submit/route.js.map +2 -2
  17. package/dist/modules/forms/api/form-submissions/route.js +16 -1
  18. package/dist/modules/forms/api/form-submissions/route.js.map +2 -2
  19. package/dist/modules/forms/api/public/submissions/[id]/submit/route.js +6 -1
  20. package/dist/modules/forms/api/public/submissions/[id]/submit/route.js.map +2 -2
  21. package/dist/modules/forms/api/runtime-helpers.js +16 -0
  22. package/dist/modules/forms/api/runtime-helpers.js.map +2 -2
  23. package/dist/modules/forms/data/validators.js.map +2 -2
  24. package/dist/modules/forms/lib/portal-subject-policy.js +17 -0
  25. package/dist/modules/forms/lib/portal-subject-policy.js.map +7 -0
  26. package/dist/modules/forms/lib/runtime-principal.js +27 -10
  27. package/dist/modules/forms/lib/runtime-principal.js.map +2 -2
  28. package/dist/modules/forms/services/distribution-service.js +35 -13
  29. package/dist/modules/forms/services/distribution-service.js.map +2 -2
  30. package/dist/modules/forms/services/submission-service.js +4 -0
  31. package/dist/modules/forms/services/submission-service.js.map +2 -2
  32. package/dist/modules/staff/commands/leave-requests.js +54 -24
  33. package/dist/modules/staff/commands/leave-requests.js.map +2 -2
  34. package/dist/modules/warranty_claims/api/portal/attachments/route.js +19 -7
  35. package/dist/modules/warranty_claims/api/portal/attachments/route.js.map +2 -2
  36. package/dist/modules/warranty_claims/commands/claims.js +1 -1
  37. package/dist/modules/warranty_claims/commands/claims.js.map +2 -2
  38. package/dist/modules/warranty_claims/subscribers/return-shipment-tracking.js +6 -6
  39. package/dist/modules/warranty_claims/subscribers/return-shipment-tracking.js.map +2 -2
  40. package/dist/modules/wms/commands/inventory-actions.js +19 -3
  41. package/dist/modules/wms/commands/inventory-actions.js.map +2 -2
  42. package/dist/modules/workflows/lib/async-activity-wait.js +43 -0
  43. package/dist/modules/workflows/lib/async-activity-wait.js.map +7 -0
  44. package/dist/modules/workflows/lib/parallel-handler.js +20 -14
  45. package/dist/modules/workflows/lib/parallel-handler.js.map +2 -2
  46. package/dist/modules/workflows/lib/workflow-executor.js +21 -23
  47. package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
  48. package/package.json +7 -7
  49. package/src/modules/auth/api/logout.ts +6 -1
  50. package/src/modules/auth/api/session/refresh.ts +6 -1
  51. package/src/modules/catalog/ai-tools/mutation-pack.ts +14 -5
  52. package/src/modules/catalog/ai-tools/types.ts +1 -0
  53. package/src/modules/customer_accounts/api/login.ts +15 -7
  54. package/src/modules/customer_accounts/services/customerUserService.ts +34 -10
  55. package/src/modules/forms/api/distributions/[distributionId]/invitations/[invitationId]/route.ts +1 -1
  56. package/src/modules/forms/api/form-submissions/[id]/submit/route.ts +16 -3
  57. package/src/modules/forms/api/form-submissions/route.ts +21 -2
  58. package/src/modules/forms/api/public/submissions/[id]/submit/route.ts +7 -0
  59. package/src/modules/forms/api/runtime-helpers.ts +16 -0
  60. package/src/modules/forms/data/validators.ts +2 -1
  61. package/src/modules/forms/i18n/de.json +1 -0
  62. package/src/modules/forms/i18n/en.json +1 -0
  63. package/src/modules/forms/i18n/es.json +1 -0
  64. package/src/modules/forms/i18n/ko.json +1 -0
  65. package/src/modules/forms/i18n/pl.json +1 -0
  66. package/src/modules/forms/lib/portal-subject-policy.ts +42 -0
  67. package/src/modules/forms/lib/runtime-principal.ts +35 -11
  68. package/src/modules/forms/services/distribution-service.ts +35 -13
  69. package/src/modules/forms/services/submission-service.ts +12 -0
  70. package/src/modules/staff/commands/leave-requests.ts +54 -23
  71. package/src/modules/staff/i18n/de.json +2 -0
  72. package/src/modules/staff/i18n/en.json +2 -0
  73. package/src/modules/staff/i18n/es.json +2 -0
  74. package/src/modules/staff/i18n/ko.json +2 -0
  75. package/src/modules/staff/i18n/pl.json +2 -0
  76. package/src/modules/warranty_claims/api/portal/attachments/route.ts +20 -8
  77. package/src/modules/warranty_claims/commands/claims.ts +1 -1
  78. package/src/modules/warranty_claims/subscribers/return-shipment-tracking.ts +5 -5
  79. package/src/modules/wms/commands/inventory-actions.ts +30 -10
  80. package/src/modules/workflows/lib/async-activity-wait.ts +71 -0
  81. package/src/modules/workflows/lib/parallel-handler.ts +24 -15
  82. package/src/modules/workflows/lib/workflow-executor.ts +24 -27
@@ -7,6 +7,11 @@ import {
7
7
  WorkflowEvent
8
8
  } from "../data/entities.js";
9
9
  import { logWorkflowEvent } from "./event-logger.js";
10
+ import {
11
+ ASYNC_ACTIVITY_OUTCOME_EVENT_TYPES,
12
+ readPendingAsyncJobIds,
13
+ resolveAsyncActivityWait
14
+ } from "./async-activity-wait.js";
10
15
  import * as stepHandler from "./step-handler.js";
11
16
  import { branchToken, mergeTokenContext } from "./execution-token.js";
12
17
  import { WORKFLOW_ERROR_CONTEXT_KEY, buildErrorContextEntry } from "./error-routing.js";
@@ -106,22 +111,23 @@ async function resumeBranchAfterActivities(em, container, instanceId, branchInst
106
111
  return { continueExecution: false };
107
112
  }
108
113
  const namespace = branch.contextNamespace || {};
109
- const pendingJobIds = namespace._pendingAsyncActivities || [];
110
- const completedEvents = await em.find(WorkflowEvent, {
111
- workflowInstanceId: instanceId,
112
- branchInstanceId,
113
- eventType: "ACTIVITY_COMPLETED",
114
- eventData: { async: true }
115
- });
116
- const failedCount = await em.count(WorkflowEvent, {
117
- workflowInstanceId: instanceId,
118
- branchInstanceId,
119
- eventType: "ACTIVITY_FAILED",
120
- eventData: { async: true }
121
- });
122
- if (completedEvents.length + failedCount < pendingJobIds.length) {
114
+ const pendingJobIds = readPendingAsyncJobIds(namespace._pendingAsyncActivities);
115
+ const outcomeEvents = pendingJobIds.length === 0 ? [] : await em.find(
116
+ WorkflowEvent,
117
+ {
118
+ workflowInstanceId: instanceId,
119
+ branchInstanceId,
120
+ eventType: { $in: [...ASYNC_ACTIVITY_OUTCOME_EVENT_TYPES] },
121
+ eventData: { async: true, jobId: { $in: pendingJobIds } }
122
+ },
123
+ { orderBy: { occurredAt: "asc", id: "asc" } }
124
+ );
125
+ const wait = resolveAsyncActivityWait(pendingJobIds, outcomeEvents);
126
+ if (!wait.settled) {
123
127
  return { continueExecution: false };
124
128
  }
129
+ const completedEvents = wait.completed;
130
+ const failedCount = wait.failed.length;
125
131
  const now = /* @__PURE__ */ new Date();
126
132
  if (failedCount > 0) {
127
133
  branch.status = "FAILED";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/workflows/lib/parallel-handler.ts"],
4
- "sourcesContent": ["/**\n * Workflows Module - Parallel Fork / Join Handler\n *\n * Implements PARALLEL_FORK / PARALLEL_JOIN execution with a multi-token model:\n *\n * - `openFork` turns a PARALLEL_FORK step into N persistent branch tokens\n * (`WorkflowBranchInstance`), one per outgoing `auto` transition, and puts the\n * instance into the dormant `FORKED` state.\n * - `advanceBranches` runs the interleaved loop: while the instance is FORKED it\n * advances each ACTIVE branch one step at a time (BPMN semantics \u2014 single lock,\n * no thread-level concurrency). A branch pauses independently (USER_TASK /\n * signal / timer / async activity) without blocking siblings.\n * - When every branch has reached its JOIN (wait-all), `fireJoin` merges the\n * branch namespaces back into `instance.context`, applies optional\n * `outputMapping`, and resumes the root token at the step after the JOIN.\n * - A failed branch cancels its siblings and fails the whole instance.\n *\n * All work happens under the executor's pessimistic instance lock + transaction,\n * so wait-all counting and JOIN firing are race-free.\n */\n\nimport { EntityManager, LockMode } from '@mikro-orm/core'\nimport type { AwilixContainer } from 'awilix'\nimport {\n WorkflowInstance,\n WorkflowBranchInstance,\n WorkflowDefinition,\n StepInstance,\n UserTask,\n WorkflowEvent,\n} from '../data/entities'\nimport { logWorkflowEvent } from './event-logger'\nimport * as stepHandler from './step-handler'\nimport { branchToken, mergeTokenContext, type ExecutionToken } from './execution-token'\nimport { WORKFLOW_ERROR_CONTEXT_KEY, buildErrorContextEntry } from './error-routing'\n\nexport interface AdvanceBranchesResult {\n outcome: 'joined' | 'waiting' | 'failed'\n error?: string\n}\n\nexport interface ResumeBranchOptions {\n instanceId: string\n branchInstanceId: string\n tenantId: string\n organizationId: string\n /** Values merged into the branch's private namespace before resuming (task form data, signal payload, \u2026). */\n contextMerge?: Record<string, any>\n /** Step instance to exit (the paused step's instance) before the branch advances past it. */\n exitStepInstanceId?: string | null\n exitOutput?: any\n}\n\n/**\n * Resume a single paused/waiting branch: optionally merge data into its\n * namespace, exit its paused step instance, and mark it ACTIVE. The caller then\n * re-enters `executeWorkflow` (FORKED mode) so the interleaved loop advances the\n * branch and, if it is the last to arrive, fires the JOIN.\n *\n * Idempotent: a re-delivered signal/timer/task on an already-ACTIVE or terminal\n * branch is a no-op. Returns false when there is nothing to resume.\n */\nexport async function resumeBranch(\n em: EntityManager,\n options: ResumeBranchOptions,\n): Promise<boolean> {\n const branch = await em.findOne(WorkflowBranchInstance, {\n id: options.branchInstanceId,\n workflowInstanceId: options.instanceId,\n tenantId: options.tenantId,\n organizationId: options.organizationId,\n })\n\n if (!branch) return false\n if (branch.status !== 'PAUSED' && branch.status !== 'WAITING_FOR_ACTIVITIES') {\n // Already active or terminal \u2014 re-delivery; nothing to do.\n return false\n }\n\n const now = new Date()\n if (options.contextMerge) {\n branch.contextNamespace = { ...(branch.contextNamespace || {}), ...options.contextMerge }\n }\n\n if (options.exitStepInstanceId) {\n const stepInstance = await em.findOne(StepInstance, {\n id: options.exitStepInstanceId,\n workflowInstanceId: options.instanceId,\n status: 'ACTIVE',\n })\n if (stepInstance) {\n await stepHandler.exitStep(em, stepInstance, options.exitOutput)\n }\n }\n\n branch.status = 'ACTIVE'\n branch.pendingTransition = null\n branch.updatedAt = now\n await em.flush()\n\n return true\n}\n\ninterface ParallelContext {\n userId?: string\n}\n\nfunction normalizeWorkflowUserId(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n if (!trimmed || trimmed.startsWith('trigger:')) return null\n return trimmed\n}\n\nfunction getNestedValue(obj: any, path: string): any {\n return path.split('.').reduce((current, key) => (current == null ? undefined : current[key]), obj)\n}\n\n/**\n * Open a PARALLEL_FORK: create one ACTIVE branch per outgoing `auto` transition\n * and mark the instance FORKED. Branch tokens start positioned ON the fork step\n * so the interleaved loop runs each branch's fork transition (and its\n * activities) in that branch's own context.\n */\nexport async function openFork(\n em: EntityManager,\n instance: WorkflowInstance,\n definition: WorkflowDefinition,\n forkStepDef: any,\n): Promise<void> {\n const forkStepId: string = forkStepDef.stepId\n const joinStepId: string | undefined = forkStepDef.config?.joinStepId\n if (!joinStepId) {\n throw new Error(`[internal] PARALLEL_FORK \"${forkStepId}\" missing config.joinStepId`)\n }\n\n const outgoing = (definition.definition.transitions || []).filter(\n (transition: any) => transition.fromStepId === forkStepId && transition.trigger === 'auto',\n )\n\n const now = new Date()\n const branchKeys: string[] = []\n for (const transition of outgoing) {\n branchKeys.push(transition.transitionId)\n const branch = em.create(WorkflowBranchInstance, {\n workflowInstanceId: instance.id,\n forkStepId,\n joinStepId,\n branchKey: transition.transitionId,\n parentBranchId: null,\n // Start ON the fork; advanceBranches runs the branch_key transition first.\n currentStepId: forkStepId,\n status: 'ACTIVE',\n contextNamespace: {},\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n startedAt: now,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(branch)\n }\n\n instance.status = 'FORKED'\n instance.activeForkStepId = forkStepId\n instance.updatedAt = now\n await em.flush()\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n eventType: 'PARALLEL_FORK_OPENED',\n eventData: { forkStepId, joinStepId, branchKeys },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n}\n\n/**\n * Resume a branch that was WAITING_FOR_ACTIVITIES after its async activities\n * finished. Mirrors the instance-level resume but scoped to the branch:\n * merges completed-activity outputs into the branch namespace, advances the\n * branch's pending transition target, and marks it ACTIVE (or FAILED if any\n * async activity failed). The caller re-enters the interleaved loop.\n */\nexport async function resumeBranchAfterActivities(\n em: EntityManager,\n container: AwilixContainer,\n instanceId: string,\n branchInstanceId: string,\n): Promise<{ continueExecution: boolean }> {\n const branch = await em.findOne(\n WorkflowBranchInstance,\n { id: branchInstanceId, workflowInstanceId: instanceId },\n { lockMode: LockMode.PESSIMISTIC_WRITE },\n )\n if (!branch) {\n throw new Error('[internal] Branch not found during async resume')\n }\n if (branch.status !== 'WAITING_FOR_ACTIVITIES') {\n // Re-delivery or already advanced \u2014 nothing to do.\n return { continueExecution: false }\n }\n\n const namespace = branch.contextNamespace || {}\n const pendingJobIds = (namespace._pendingAsyncActivities as any[]) || []\n\n const completedEvents = await em.find(WorkflowEvent, {\n workflowInstanceId: instanceId,\n branchInstanceId,\n eventType: 'ACTIVITY_COMPLETED',\n eventData: { async: true },\n })\n const failedCount = await em.count(WorkflowEvent, {\n workflowInstanceId: instanceId,\n branchInstanceId,\n eventType: 'ACTIVITY_FAILED',\n eventData: { async: true },\n })\n\n if (completedEvents.length + failedCount < pendingJobIds.length) {\n // Still waiting on other branch activities.\n return { continueExecution: false }\n }\n\n const now = new Date()\n if (failedCount > 0) {\n branch.status = 'FAILED'\n branch.errorMessage = `${failedCount} async activities failed in branch \"${branch.branchKey}\"`\n branch.updatedAt = now\n await em.flush()\n // The interleaved loop will observe the FAILED branch and cancel siblings.\n return { continueExecution: true }\n }\n\n const mergedNamespace: Record<string, any> = { ...namespace }\n for (const event of completedEvents) {\n if (event.eventData?.output) {\n mergedNamespace[`${event.eventData.activityId}_result`] = event.eventData.output\n }\n }\n delete mergedNamespace._pendingAsyncActivities\n branch.contextNamespace = mergedNamespace\n\n const pending = branch.pendingTransition\n branch.pendingTransition = null\n branch.status = 'ACTIVE'\n branch.updatedAt = now\n await em.flush()\n\n // Execute the destination step in branch context (mirrors the instance-level\n // resume) so the branch cursor lands on an executed step. The step may pause\n // the branch again (e.g. a following USER_TASK); that is handled by the loop.\n if (pending) {\n const instance = await em.findOne(WorkflowInstance, { id: instanceId })\n if (instance) {\n branch.currentStepId = pending.toStepId\n await em.flush()\n await stepHandler.executeStep(\n em,\n instance,\n pending.toStepId,\n {\n workflowContext: { ...(instance.context || {}), ...(branch.contextNamespace || {}) },\n userId: normalizeWorkflowUserId(instance.metadata?.initiatedBy) ?? undefined,\n },\n container,\n branch,\n )\n }\n }\n\n return { continueExecution: true }\n}\n\n/**\n * Interleaved branch execution loop. Advances ACTIVE branches one step at a\n * time until either all branches reach the JOIN (\u2192 fireJoin \u2192 'joined'), a\n * branch fails (\u2192 cancel siblings + 'failed'), or no branch is ACTIVE because\n * they are all waiting on external resume (\u2192 'waiting').\n */\nexport async function advanceBranches(\n em: EntityManager,\n container: AwilixContainer,\n instance: WorkflowInstance,\n definition: WorkflowDefinition,\n context: ParallelContext,\n): Promise<AdvanceBranchesResult> {\n const forkStepId = instance.activeForkStepId\n if (!forkStepId) {\n throw new Error('[internal] advanceBranches called but instance has no active fork')\n }\n\n // Branch-aware iteration budget: each branch gets the same per-token budget as\n // the single-token loop, so fan-out alone never trips the guard.\n const allBranchesForBudget = await em.find(WorkflowBranchInstance, {\n workflowInstanceId: instance.id,\n forkStepId,\n })\n const maxIterations = Math.max(1, allBranchesForBudget.length) * 100\n let iterations = 0\n\n while (iterations < maxIterations) {\n iterations++\n\n const branches = await em.find(WorkflowBranchInstance, {\n workflowInstanceId: instance.id,\n forkStepId,\n })\n\n const active = branches.filter((b) => b.status === 'ACTIVE')\n\n if (active.length === 0) {\n const failed = branches.find((b) => b.status === 'FAILED')\n if (failed) {\n // A branch failed out-of-band (e.g. async-activity resume marked it\n // FAILED); cancel any non-terminal siblings before failing the instance.\n await cancelSiblings(em, instance, failed)\n return { outcome: 'failed', error: failed.errorMessage || 'Branch failed' }\n }\n if (branches.every((b) => b.status === 'COMPLETED')) {\n await fireJoin(em, instance, definition, branches, forkStepId)\n return { outcome: 'joined' }\n }\n // Some branches paused/waiting for external resume \u2014 instance idles.\n return { outcome: 'waiting' }\n }\n\n for (const branch of active) {\n const result = await advanceOneBranch(em, container, instance, definition, branch, context)\n if (result === 'failed') {\n await cancelSiblings(em, instance, branch)\n return { outcome: 'failed', error: branch.errorMessage || 'Branch failed' }\n }\n }\n }\n\n throw new Error('[internal] Maximum branch execution iterations reached - possible infinite loop')\n}\n\n/**\n * Advance a single ACTIVE branch by one step. Returns 'failed' if the branch\n * failed; otherwise mutates branch state (advanced / completed@join / paused).\n */\nasync function advanceOneBranch(\n em: EntityManager,\n container: AwilixContainer,\n instance: WorkflowInstance,\n definition: WorkflowDefinition,\n branch: WorkflowBranchInstance,\n context: ParallelContext,\n): Promise<'advanced' | 'failed'> {\n // Already at the join \u2192 synchronize (mark completed, do not execute the join).\n if (branch.currentStepId === branch.joinStepId) {\n await completeBranchAtJoin(em, instance, branch)\n return 'advanced'\n }\n\n const transitionHandler = await import('./transition-handler')\n\n const token = branchToken(instance, branch)\n // Token read context = instance snapshot overlaid with branch namespace.\n const readContext = { ...(instance.context || {}), ...(branch.contextNamespace || {}) }\n const evalContext = { workflowContext: readContext, userId: context.userId }\n\n // Select the transition to take. Branches positioned on the fork follow their\n // own branch_key transition; otherwise pick the highest-priority valid auto one.\n let selected: any | null = null\n if (branch.currentStepId === branch.forkStepId) {\n selected = (definition.definition.transitions || []).find(\n (transition: any) => transition.transitionId === branch.branchKey,\n ) || null\n } else {\n const valid = await transitionHandler.findValidTransitions(\n em,\n instance,\n branch.currentStepId,\n evalContext,\n )\n const validAuto = valid.filter((vt) => vt.isValid && vt.transition?.trigger === 'auto')\n selected = validAuto.length > 0 ? validAuto[0].transition : null\n }\n\n if (!selected) {\n // No outgoing transition and not at the join \u2192 the branch is stuck.\n await failBranch(em, instance, branch, `Branch \"${branch.branchKey}\" has no valid transition from \"${branch.currentStepId}\"`)\n return 'failed'\n }\n\n try {\n const transitionResult = await transitionHandler.executeTransitionForToken(\n em,\n container,\n token,\n selected.fromStepId,\n selected.toStepId,\n { ...evalContext, transitionId: selected.transitionId },\n )\n\n if (!transitionResult.success) {\n // Error routing (spec 5.9) inside a branch: a wired error route is taken\n // in the branch itself, so only unhandled failures propagate to the\n // instance. The failure-queue directive is deliberately instance-level and\n // is not honored per branch \u2014 such a branch keeps failing as it does today.\n const routed = await followBranchErrorRoute(\n em,\n container,\n instance,\n branch,\n token,\n transitionResult,\n evalContext,\n )\n if (routed) {\n if (branch.currentStepId === branch.joinStepId) {\n await completeBranchAtJoin(em, instance, branch)\n }\n return 'advanced'\n }\n await failBranch(em, instance, branch, transitionResult.error || 'Branch transition failed')\n return 'failed'\n }\n\n // executeTransitionForToken set branch.currentStepId = toStepId. If that is\n // the join, synchronize now (the join step itself is a no-op).\n if (branch.currentStepId === branch.joinStepId) {\n await completeBranchAtJoin(em, instance, branch)\n }\n return 'advanced'\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n await failBranch(em, instance, branch, message)\n return 'failed'\n }\n}\n\n/**\n * Follow a branch step's wired error route with the branch token, so the branch\n * keeps its own cursor and context namespace. Returns false when the transition\n * result carries no route or the route could not be taken.\n */\nasync function followBranchErrorRoute(\n em: EntityManager,\n container: AwilixContainer,\n instance: WorkflowInstance,\n branch: WorkflowBranchInstance,\n token: ExecutionToken,\n transitionResult: { failedStepId?: string; errorRoute?: { transitionId?: string; toStepId: string }; error?: string },\n evalContext: { workflowContext: Record<string, any>; userId?: string },\n): Promise<boolean> {\n const { failedStepId, errorRoute } = transitionResult\n if (!failedStepId || !errorRoute) return false\n\n const error = transitionResult.error || 'Branch transition failed'\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n branchInstanceId: branch.id,\n eventType: 'ERROR_ROUTED',\n eventData: {\n failedStepId,\n toStepId: errorRoute.toStepId,\n transitionId: errorRoute.transitionId,\n branchKey: branch.branchKey,\n error,\n },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n\n const errorEntry = buildErrorContextEntry(failedStepId, error)\n mergeTokenContext(token, { [WORKFLOW_ERROR_CONTEXT_KEY]: errorEntry })\n await em.flush()\n\n const transitionHandler = await import('./transition-handler')\n const routedResult = await transitionHandler.executeTransitionForToken(\n em,\n container,\n token,\n failedStepId,\n errorRoute.toStepId,\n {\n ...evalContext,\n workflowContext: {\n ...evalContext.workflowContext,\n [WORKFLOW_ERROR_CONTEXT_KEY]: errorEntry,\n },\n transitionId: errorRoute.transitionId,\n },\n )\n\n return routedResult.success === true\n}\n\nasync function completeBranchAtJoin(\n em: EntityManager,\n instance: WorkflowInstance,\n branch: WorkflowBranchInstance,\n): Promise<void> {\n const now = new Date()\n branch.status = 'COMPLETED'\n branch.completedAt = now\n branch.updatedAt = now\n await em.flush()\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n branchInstanceId: branch.id,\n eventType: 'PARALLEL_BRANCH_COMPLETED',\n eventData: { branchKey: branch.branchKey, joinStepId: branch.joinStepId },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n}\n\nasync function failBranch(\n em: EntityManager,\n instance: WorkflowInstance,\n branch: WorkflowBranchInstance,\n message: string,\n): Promise<void> {\n const now = new Date()\n branch.status = 'FAILED'\n branch.errorMessage = message\n branch.updatedAt = now\n await em.flush()\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n branchInstanceId: branch.id,\n eventType: 'PARALLEL_BRANCH_FAILED',\n eventData: { branchKey: branch.branchKey, error: message },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n}\n\n/**\n * Cancel sibling branches of a failed branch (best-effort), cancel their open\n * user tasks, and log a cancellation event per branch.\n */\nasync function cancelSiblings(\n em: EntityManager,\n instance: WorkflowInstance,\n failedBranch: WorkflowBranchInstance,\n): Promise<void> {\n const siblings = await em.find(WorkflowBranchInstance, {\n workflowInstanceId: instance.id,\n forkStepId: failedBranch.forkStepId,\n })\n\n const now = new Date()\n for (const sibling of siblings) {\n if (sibling.id === failedBranch.id) continue\n if (sibling.status === 'ACTIVE' || sibling.status === 'PAUSED' || sibling.status === 'WAITING_FOR_ACTIVITIES') {\n sibling.status = 'CANCELLED'\n sibling.updatedAt = now\n\n // Best-effort: cancel the branch's open user tasks.\n const openTasks = await em.find(UserTask, {\n workflowInstanceId: instance.id,\n branchInstanceId: sibling.id,\n status: 'PENDING',\n })\n for (const task of openTasks) {\n task.status = 'CANCELLED'\n task.updatedAt = now\n }\n\n await em.flush()\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n branchInstanceId: sibling.id,\n eventType: 'PARALLEL_BRANCH_CANCELLED',\n eventData: { branchKey: sibling.branchKey, reason: 'sibling-failed' },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n }\n }\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n eventType: 'PARALLEL_FORK_FAILED',\n eventData: { forkStepId: failedBranch.forkStepId, failedBranchKey: failedBranch.branchKey },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n}\n\n/**\n * Fire the JOIN once all branches are COMPLETED: merge each branch namespace\n * under `instance.context.branches[branchKey]`, apply optional outputMapping to\n * lift selected values to the top level, then resume the root token at the step\n * after the JOIN.\n */\nasync function fireJoin(\n em: EntityManager,\n instance: WorkflowInstance,\n definition: WorkflowDefinition,\n branches: WorkflowBranchInstance[],\n forkStepId: string,\n): Promise<void> {\n const joinStepId = branches[0]?.joinStepId\n if (!joinStepId) {\n throw new Error('[internal] fireJoin called without a join step id')\n }\n\n const joinStep = definition.definition.steps.find((s: any) => s.stepId === joinStepId)\n\n // Deterministic merge \u2014 no silent collisions: each branch keeps its own slot.\n const branchesContext: Record<string, any> = { ...(instance.context?.branches || {}) }\n const mergedBranchKeys: string[] = []\n for (const branch of branches) {\n branchesContext[branch.branchKey] = branch.contextNamespace || {}\n mergedBranchKeys.push(branch.branchKey)\n }\n\n let nextContext: Record<string, any> = {\n ...(instance.context || {}),\n branches: branchesContext,\n }\n\n // Optional outputMapping: topLevelKey -> 'branches.<branchKey>.<path>'. The\n // reserved `branches` slot map is never overwritten \u2014 guard it explicitly so a\n // mapping cannot clobber the per-branch namespaces.\n const outputMapping: Record<string, string> | undefined = joinStep?.config?.outputMapping\n if (outputMapping) {\n for (const [topKey, sourcePath] of Object.entries(outputMapping)) {\n if (topKey === 'branches') continue\n const value = getNestedValue(nextContext, sourcePath)\n if (value !== undefined) nextContext[topKey] = value\n }\n }\n\n // Park the root token ON the JOIN step (not its successor) and let the\n // single-token executor loop run the JOIN's outgoing transition. Jumping\n // straight to the post-join step would bypass stepHandler.executeStep() for\n // that step \u2014 so a following USER_TASK / WAIT_FOR_TIMER / WAIT_FOR_SIGNAL would\n // never have its task created / timer enqueued / signal wait registered (the\n // instance would hang), and any activities on the JOIN's outgoing transition\n // would be skipped. The JOIN step itself is a no-op, so the loop simply runs\n // its outgoing transition through the normal step-entry path.\n const afterJoinStepId = (definition.definition.transitions || []).find(\n (transition: any) => transition.fromStepId === joinStepId,\n )?.toStepId ?? null\n\n const now = new Date()\n instance.context = nextContext\n instance.status = 'RUNNING'\n instance.activeForkStepId = null\n instance.currentStepId = joinStepId\n instance.updatedAt = now\n await em.flush()\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n eventType: 'PARALLEL_JOIN_COMPLETED',\n eventData: { forkStepId, joinStepId, mergedBranchKeys, afterJoinStepId },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Workflows Module - Parallel Fork / Join Handler\n *\n * Implements PARALLEL_FORK / PARALLEL_JOIN execution with a multi-token model:\n *\n * - `openFork` turns a PARALLEL_FORK step into N persistent branch tokens\n * (`WorkflowBranchInstance`), one per outgoing `auto` transition, and puts the\n * instance into the dormant `FORKED` state.\n * - `advanceBranches` runs the interleaved loop: while the instance is FORKED it\n * advances each ACTIVE branch one step at a time (BPMN semantics \u2014 single lock,\n * no thread-level concurrency). A branch pauses independently (USER_TASK /\n * signal / timer / async activity) without blocking siblings.\n * - When every branch has reached its JOIN (wait-all), `fireJoin` merges the\n * branch namespaces back into `instance.context`, applies optional\n * `outputMapping`, and resumes the root token at the step after the JOIN.\n * - A failed branch cancels its siblings and fails the whole instance.\n *\n * All work happens under the executor's pessimistic instance lock + transaction,\n * so wait-all counting and JOIN firing are race-free.\n */\n\nimport { EntityManager, LockMode } from '@mikro-orm/core'\nimport type { AwilixContainer } from 'awilix'\nimport {\n WorkflowInstance,\n WorkflowBranchInstance,\n WorkflowDefinition,\n StepInstance,\n UserTask,\n WorkflowEvent,\n} from '../data/entities'\nimport { logWorkflowEvent } from './event-logger'\nimport {\n ASYNC_ACTIVITY_OUTCOME_EVENT_TYPES,\n readPendingAsyncJobIds,\n resolveAsyncActivityWait,\n} from './async-activity-wait'\nimport * as stepHandler from './step-handler'\nimport { branchToken, mergeTokenContext, type ExecutionToken } from './execution-token'\nimport { WORKFLOW_ERROR_CONTEXT_KEY, buildErrorContextEntry } from './error-routing'\n\nexport interface AdvanceBranchesResult {\n outcome: 'joined' | 'waiting' | 'failed'\n error?: string\n}\n\nexport interface ResumeBranchOptions {\n instanceId: string\n branchInstanceId: string\n tenantId: string\n organizationId: string\n /** Values merged into the branch's private namespace before resuming (task form data, signal payload, \u2026). */\n contextMerge?: Record<string, any>\n /** Step instance to exit (the paused step's instance) before the branch advances past it. */\n exitStepInstanceId?: string | null\n exitOutput?: any\n}\n\n/**\n * Resume a single paused/waiting branch: optionally merge data into its\n * namespace, exit its paused step instance, and mark it ACTIVE. The caller then\n * re-enters `executeWorkflow` (FORKED mode) so the interleaved loop advances the\n * branch and, if it is the last to arrive, fires the JOIN.\n *\n * Idempotent: a re-delivered signal/timer/task on an already-ACTIVE or terminal\n * branch is a no-op. Returns false when there is nothing to resume.\n */\nexport async function resumeBranch(\n em: EntityManager,\n options: ResumeBranchOptions,\n): Promise<boolean> {\n const branch = await em.findOne(WorkflowBranchInstance, {\n id: options.branchInstanceId,\n workflowInstanceId: options.instanceId,\n tenantId: options.tenantId,\n organizationId: options.organizationId,\n })\n\n if (!branch) return false\n if (branch.status !== 'PAUSED' && branch.status !== 'WAITING_FOR_ACTIVITIES') {\n // Already active or terminal \u2014 re-delivery; nothing to do.\n return false\n }\n\n const now = new Date()\n if (options.contextMerge) {\n branch.contextNamespace = { ...(branch.contextNamespace || {}), ...options.contextMerge }\n }\n\n if (options.exitStepInstanceId) {\n const stepInstance = await em.findOne(StepInstance, {\n id: options.exitStepInstanceId,\n workflowInstanceId: options.instanceId,\n status: 'ACTIVE',\n })\n if (stepInstance) {\n await stepHandler.exitStep(em, stepInstance, options.exitOutput)\n }\n }\n\n branch.status = 'ACTIVE'\n branch.pendingTransition = null\n branch.updatedAt = now\n await em.flush()\n\n return true\n}\n\ninterface ParallelContext {\n userId?: string\n}\n\nfunction normalizeWorkflowUserId(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n if (!trimmed || trimmed.startsWith('trigger:')) return null\n return trimmed\n}\n\nfunction getNestedValue(obj: any, path: string): any {\n return path.split('.').reduce((current, key) => (current == null ? undefined : current[key]), obj)\n}\n\n/**\n * Open a PARALLEL_FORK: create one ACTIVE branch per outgoing `auto` transition\n * and mark the instance FORKED. Branch tokens start positioned ON the fork step\n * so the interleaved loop runs each branch's fork transition (and its\n * activities) in that branch's own context.\n */\nexport async function openFork(\n em: EntityManager,\n instance: WorkflowInstance,\n definition: WorkflowDefinition,\n forkStepDef: any,\n): Promise<void> {\n const forkStepId: string = forkStepDef.stepId\n const joinStepId: string | undefined = forkStepDef.config?.joinStepId\n if (!joinStepId) {\n throw new Error(`[internal] PARALLEL_FORK \"${forkStepId}\" missing config.joinStepId`)\n }\n\n const outgoing = (definition.definition.transitions || []).filter(\n (transition: any) => transition.fromStepId === forkStepId && transition.trigger === 'auto',\n )\n\n const now = new Date()\n const branchKeys: string[] = []\n for (const transition of outgoing) {\n branchKeys.push(transition.transitionId)\n const branch = em.create(WorkflowBranchInstance, {\n workflowInstanceId: instance.id,\n forkStepId,\n joinStepId,\n branchKey: transition.transitionId,\n parentBranchId: null,\n // Start ON the fork; advanceBranches runs the branch_key transition first.\n currentStepId: forkStepId,\n status: 'ACTIVE',\n contextNamespace: {},\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n startedAt: now,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(branch)\n }\n\n instance.status = 'FORKED'\n instance.activeForkStepId = forkStepId\n instance.updatedAt = now\n await em.flush()\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n eventType: 'PARALLEL_FORK_OPENED',\n eventData: { forkStepId, joinStepId, branchKeys },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n}\n\n/**\n * Resume a branch that was WAITING_FOR_ACTIVITIES after its async activities\n * finished. Mirrors the instance-level resume but scoped to the branch:\n * merges completed-activity outputs into the branch namespace, advances the\n * branch's pending transition target, and marks it ACTIVE (or FAILED if any\n * async activity failed). The caller re-enters the interleaved loop.\n */\nexport async function resumeBranchAfterActivities(\n em: EntityManager,\n container: AwilixContainer,\n instanceId: string,\n branchInstanceId: string,\n): Promise<{ continueExecution: boolean }> {\n const branch = await em.findOne(\n WorkflowBranchInstance,\n { id: branchInstanceId, workflowInstanceId: instanceId },\n { lockMode: LockMode.PESSIMISTIC_WRITE },\n )\n if (!branch) {\n throw new Error('[internal] Branch not found during async resume')\n }\n if (branch.status !== 'WAITING_FOR_ACTIVITIES') {\n // Re-delivery or already advanced \u2014 nothing to do.\n return { continueExecution: false }\n }\n\n const namespace = branch.contextNamespace || {}\n const pendingJobIds = readPendingAsyncJobIds(namespace._pendingAsyncActivities)\n\n const outcomeEvents = pendingJobIds.length === 0\n ? []\n : await em.find(\n WorkflowEvent,\n {\n workflowInstanceId: instanceId,\n branchInstanceId,\n eventType: { $in: [...ASYNC_ACTIVITY_OUTCOME_EVENT_TYPES] },\n eventData: { async: true, jobId: { $in: pendingJobIds } },\n },\n { orderBy: { occurredAt: 'asc', id: 'asc' } },\n )\n const wait = resolveAsyncActivityWait(pendingJobIds, outcomeEvents)\n\n if (!wait.settled) {\n // Still waiting on other branch activities.\n return { continueExecution: false }\n }\n\n const completedEvents = wait.completed\n const failedCount = wait.failed.length\n\n const now = new Date()\n if (failedCount > 0) {\n branch.status = 'FAILED'\n branch.errorMessage = `${failedCount} async activities failed in branch \"${branch.branchKey}\"`\n branch.updatedAt = now\n await em.flush()\n // The interleaved loop will observe the FAILED branch and cancel siblings.\n return { continueExecution: true }\n }\n\n const mergedNamespace: Record<string, any> = { ...namespace }\n for (const event of completedEvents) {\n if (event.eventData?.output) {\n mergedNamespace[`${event.eventData.activityId}_result`] = event.eventData.output\n }\n }\n delete mergedNamespace._pendingAsyncActivities\n branch.contextNamespace = mergedNamespace\n\n const pending = branch.pendingTransition\n branch.pendingTransition = null\n branch.status = 'ACTIVE'\n branch.updatedAt = now\n await em.flush()\n\n // Execute the destination step in branch context (mirrors the instance-level\n // resume) so the branch cursor lands on an executed step. The step may pause\n // the branch again (e.g. a following USER_TASK); that is handled by the loop.\n if (pending) {\n const instance = await em.findOne(WorkflowInstance, { id: instanceId })\n if (instance) {\n branch.currentStepId = pending.toStepId\n await em.flush()\n await stepHandler.executeStep(\n em,\n instance,\n pending.toStepId,\n {\n workflowContext: { ...(instance.context || {}), ...(branch.contextNamespace || {}) },\n userId: normalizeWorkflowUserId(instance.metadata?.initiatedBy) ?? undefined,\n },\n container,\n branch,\n )\n }\n }\n\n return { continueExecution: true }\n}\n\n/**\n * Interleaved branch execution loop. Advances ACTIVE branches one step at a\n * time until either all branches reach the JOIN (\u2192 fireJoin \u2192 'joined'), a\n * branch fails (\u2192 cancel siblings + 'failed'), or no branch is ACTIVE because\n * they are all waiting on external resume (\u2192 'waiting').\n */\nexport async function advanceBranches(\n em: EntityManager,\n container: AwilixContainer,\n instance: WorkflowInstance,\n definition: WorkflowDefinition,\n context: ParallelContext,\n): Promise<AdvanceBranchesResult> {\n const forkStepId = instance.activeForkStepId\n if (!forkStepId) {\n throw new Error('[internal] advanceBranches called but instance has no active fork')\n }\n\n // Branch-aware iteration budget: each branch gets the same per-token budget as\n // the single-token loop, so fan-out alone never trips the guard.\n const allBranchesForBudget = await em.find(WorkflowBranchInstance, {\n workflowInstanceId: instance.id,\n forkStepId,\n })\n const maxIterations = Math.max(1, allBranchesForBudget.length) * 100\n let iterations = 0\n\n while (iterations < maxIterations) {\n iterations++\n\n const branches = await em.find(WorkflowBranchInstance, {\n workflowInstanceId: instance.id,\n forkStepId,\n })\n\n const active = branches.filter((b) => b.status === 'ACTIVE')\n\n if (active.length === 0) {\n const failed = branches.find((b) => b.status === 'FAILED')\n if (failed) {\n // A branch failed out-of-band (e.g. async-activity resume marked it\n // FAILED); cancel any non-terminal siblings before failing the instance.\n await cancelSiblings(em, instance, failed)\n return { outcome: 'failed', error: failed.errorMessage || 'Branch failed' }\n }\n if (branches.every((b) => b.status === 'COMPLETED')) {\n await fireJoin(em, instance, definition, branches, forkStepId)\n return { outcome: 'joined' }\n }\n // Some branches paused/waiting for external resume \u2014 instance idles.\n return { outcome: 'waiting' }\n }\n\n for (const branch of active) {\n const result = await advanceOneBranch(em, container, instance, definition, branch, context)\n if (result === 'failed') {\n await cancelSiblings(em, instance, branch)\n return { outcome: 'failed', error: branch.errorMessage || 'Branch failed' }\n }\n }\n }\n\n throw new Error('[internal] Maximum branch execution iterations reached - possible infinite loop')\n}\n\n/**\n * Advance a single ACTIVE branch by one step. Returns 'failed' if the branch\n * failed; otherwise mutates branch state (advanced / completed@join / paused).\n */\nasync function advanceOneBranch(\n em: EntityManager,\n container: AwilixContainer,\n instance: WorkflowInstance,\n definition: WorkflowDefinition,\n branch: WorkflowBranchInstance,\n context: ParallelContext,\n): Promise<'advanced' | 'failed'> {\n // Already at the join \u2192 synchronize (mark completed, do not execute the join).\n if (branch.currentStepId === branch.joinStepId) {\n await completeBranchAtJoin(em, instance, branch)\n return 'advanced'\n }\n\n const transitionHandler = await import('./transition-handler')\n\n const token = branchToken(instance, branch)\n // Token read context = instance snapshot overlaid with branch namespace.\n const readContext = { ...(instance.context || {}), ...(branch.contextNamespace || {}) }\n const evalContext = { workflowContext: readContext, userId: context.userId }\n\n // Select the transition to take. Branches positioned on the fork follow their\n // own branch_key transition; otherwise pick the highest-priority valid auto one.\n let selected: any | null = null\n if (branch.currentStepId === branch.forkStepId) {\n selected = (definition.definition.transitions || []).find(\n (transition: any) => transition.transitionId === branch.branchKey,\n ) || null\n } else {\n const valid = await transitionHandler.findValidTransitions(\n em,\n instance,\n branch.currentStepId,\n evalContext,\n )\n const validAuto = valid.filter((vt) => vt.isValid && vt.transition?.trigger === 'auto')\n selected = validAuto.length > 0 ? validAuto[0].transition : null\n }\n\n if (!selected) {\n // No outgoing transition and not at the join \u2192 the branch is stuck.\n await failBranch(em, instance, branch, `Branch \"${branch.branchKey}\" has no valid transition from \"${branch.currentStepId}\"`)\n return 'failed'\n }\n\n try {\n const transitionResult = await transitionHandler.executeTransitionForToken(\n em,\n container,\n token,\n selected.fromStepId,\n selected.toStepId,\n { ...evalContext, transitionId: selected.transitionId },\n )\n\n if (!transitionResult.success) {\n // Error routing (spec 5.9) inside a branch: a wired error route is taken\n // in the branch itself, so only unhandled failures propagate to the\n // instance. The failure-queue directive is deliberately instance-level and\n // is not honored per branch \u2014 such a branch keeps failing as it does today.\n const routed = await followBranchErrorRoute(\n em,\n container,\n instance,\n branch,\n token,\n transitionResult,\n evalContext,\n )\n if (routed) {\n if (branch.currentStepId === branch.joinStepId) {\n await completeBranchAtJoin(em, instance, branch)\n }\n return 'advanced'\n }\n await failBranch(em, instance, branch, transitionResult.error || 'Branch transition failed')\n return 'failed'\n }\n\n // executeTransitionForToken set branch.currentStepId = toStepId. If that is\n // the join, synchronize now (the join step itself is a no-op).\n if (branch.currentStepId === branch.joinStepId) {\n await completeBranchAtJoin(em, instance, branch)\n }\n return 'advanced'\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n await failBranch(em, instance, branch, message)\n return 'failed'\n }\n}\n\n/**\n * Follow a branch step's wired error route with the branch token, so the branch\n * keeps its own cursor and context namespace. Returns false when the transition\n * result carries no route or the route could not be taken.\n */\nasync function followBranchErrorRoute(\n em: EntityManager,\n container: AwilixContainer,\n instance: WorkflowInstance,\n branch: WorkflowBranchInstance,\n token: ExecutionToken,\n transitionResult: { failedStepId?: string; errorRoute?: { transitionId?: string; toStepId: string }; error?: string },\n evalContext: { workflowContext: Record<string, any>; userId?: string },\n): Promise<boolean> {\n const { failedStepId, errorRoute } = transitionResult\n if (!failedStepId || !errorRoute) return false\n\n const error = transitionResult.error || 'Branch transition failed'\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n branchInstanceId: branch.id,\n eventType: 'ERROR_ROUTED',\n eventData: {\n failedStepId,\n toStepId: errorRoute.toStepId,\n transitionId: errorRoute.transitionId,\n branchKey: branch.branchKey,\n error,\n },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n\n const errorEntry = buildErrorContextEntry(failedStepId, error)\n mergeTokenContext(token, { [WORKFLOW_ERROR_CONTEXT_KEY]: errorEntry })\n await em.flush()\n\n const transitionHandler = await import('./transition-handler')\n const routedResult = await transitionHandler.executeTransitionForToken(\n em,\n container,\n token,\n failedStepId,\n errorRoute.toStepId,\n {\n ...evalContext,\n workflowContext: {\n ...evalContext.workflowContext,\n [WORKFLOW_ERROR_CONTEXT_KEY]: errorEntry,\n },\n transitionId: errorRoute.transitionId,\n },\n )\n\n return routedResult.success === true\n}\n\nasync function completeBranchAtJoin(\n em: EntityManager,\n instance: WorkflowInstance,\n branch: WorkflowBranchInstance,\n): Promise<void> {\n const now = new Date()\n branch.status = 'COMPLETED'\n branch.completedAt = now\n branch.updatedAt = now\n await em.flush()\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n branchInstanceId: branch.id,\n eventType: 'PARALLEL_BRANCH_COMPLETED',\n eventData: { branchKey: branch.branchKey, joinStepId: branch.joinStepId },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n}\n\nasync function failBranch(\n em: EntityManager,\n instance: WorkflowInstance,\n branch: WorkflowBranchInstance,\n message: string,\n): Promise<void> {\n const now = new Date()\n branch.status = 'FAILED'\n branch.errorMessage = message\n branch.updatedAt = now\n await em.flush()\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n branchInstanceId: branch.id,\n eventType: 'PARALLEL_BRANCH_FAILED',\n eventData: { branchKey: branch.branchKey, error: message },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n}\n\n/**\n * Cancel sibling branches of a failed branch (best-effort), cancel their open\n * user tasks, and log a cancellation event per branch.\n */\nasync function cancelSiblings(\n em: EntityManager,\n instance: WorkflowInstance,\n failedBranch: WorkflowBranchInstance,\n): Promise<void> {\n const siblings = await em.find(WorkflowBranchInstance, {\n workflowInstanceId: instance.id,\n forkStepId: failedBranch.forkStepId,\n })\n\n const now = new Date()\n for (const sibling of siblings) {\n if (sibling.id === failedBranch.id) continue\n if (sibling.status === 'ACTIVE' || sibling.status === 'PAUSED' || sibling.status === 'WAITING_FOR_ACTIVITIES') {\n sibling.status = 'CANCELLED'\n sibling.updatedAt = now\n\n // Best-effort: cancel the branch's open user tasks.\n const openTasks = await em.find(UserTask, {\n workflowInstanceId: instance.id,\n branchInstanceId: sibling.id,\n status: 'PENDING',\n })\n for (const task of openTasks) {\n task.status = 'CANCELLED'\n task.updatedAt = now\n }\n\n await em.flush()\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n branchInstanceId: sibling.id,\n eventType: 'PARALLEL_BRANCH_CANCELLED',\n eventData: { branchKey: sibling.branchKey, reason: 'sibling-failed' },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n }\n }\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n eventType: 'PARALLEL_FORK_FAILED',\n eventData: { forkStepId: failedBranch.forkStepId, failedBranchKey: failedBranch.branchKey },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n}\n\n/**\n * Fire the JOIN once all branches are COMPLETED: merge each branch namespace\n * under `instance.context.branches[branchKey]`, apply optional outputMapping to\n * lift selected values to the top level, then resume the root token at the step\n * after the JOIN.\n */\nasync function fireJoin(\n em: EntityManager,\n instance: WorkflowInstance,\n definition: WorkflowDefinition,\n branches: WorkflowBranchInstance[],\n forkStepId: string,\n): Promise<void> {\n const joinStepId = branches[0]?.joinStepId\n if (!joinStepId) {\n throw new Error('[internal] fireJoin called without a join step id')\n }\n\n const joinStep = definition.definition.steps.find((s: any) => s.stepId === joinStepId)\n\n // Deterministic merge \u2014 no silent collisions: each branch keeps its own slot.\n const branchesContext: Record<string, any> = { ...(instance.context?.branches || {}) }\n const mergedBranchKeys: string[] = []\n for (const branch of branches) {\n branchesContext[branch.branchKey] = branch.contextNamespace || {}\n mergedBranchKeys.push(branch.branchKey)\n }\n\n let nextContext: Record<string, any> = {\n ...(instance.context || {}),\n branches: branchesContext,\n }\n\n // Optional outputMapping: topLevelKey -> 'branches.<branchKey>.<path>'. The\n // reserved `branches` slot map is never overwritten \u2014 guard it explicitly so a\n // mapping cannot clobber the per-branch namespaces.\n const outputMapping: Record<string, string> | undefined = joinStep?.config?.outputMapping\n if (outputMapping) {\n for (const [topKey, sourcePath] of Object.entries(outputMapping)) {\n if (topKey === 'branches') continue\n const value = getNestedValue(nextContext, sourcePath)\n if (value !== undefined) nextContext[topKey] = value\n }\n }\n\n // Park the root token ON the JOIN step (not its successor) and let the\n // single-token executor loop run the JOIN's outgoing transition. Jumping\n // straight to the post-join step would bypass stepHandler.executeStep() for\n // that step \u2014 so a following USER_TASK / WAIT_FOR_TIMER / WAIT_FOR_SIGNAL would\n // never have its task created / timer enqueued / signal wait registered (the\n // instance would hang), and any activities on the JOIN's outgoing transition\n // would be skipped. The JOIN step itself is a no-op, so the loop simply runs\n // its outgoing transition through the normal step-entry path.\n const afterJoinStepId = (definition.definition.transitions || []).find(\n (transition: any) => transition.fromStepId === joinStepId,\n )?.toStepId ?? null\n\n const now = new Date()\n instance.context = nextContext\n instance.status = 'RUNNING'\n instance.activeForkStepId = null\n instance.currentStepId = joinStepId\n instance.updatedAt = now\n await em.flush()\n\n await logWorkflowEvent(em, {\n workflowInstanceId: instance.id,\n eventType: 'PARALLEL_JOIN_COMPLETED',\n eventData: { forkStepId, joinStepId, mergedBranchKeys, afterJoinStepId },\n tenantId: instance.tenantId,\n organizationId: instance.organizationId,\n })\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -5,6 +5,11 @@ import {
5
5
  WorkflowEvent
6
6
  } from "../data/entities.js";
7
7
  import { compensateWorkflow } from "./compensation-handler.js";
8
+ import {
9
+ ASYNC_ACTIVITY_OUTCOME_EVENT_TYPES,
10
+ readPendingAsyncJobIds,
11
+ resolveAsyncActivityWait
12
+ } from "./async-activity-wait.js";
8
13
  import { WORKFLOW_ENGINE_VERSION, isEngineVersionSupported } from "./engine-version.js";
9
14
  import {
10
15
  WORKFLOW_ERROR_CONTEXT_KEY,
@@ -1084,27 +1089,25 @@ async function resumeWorkflowAfterActivities(em, container, instanceId, branchIn
1084
1089
  if (!instance) {
1085
1090
  throw new Error("Workflow instance not waiting for activities");
1086
1091
  }
1087
- const pendingJobIds = instance.context._pendingAsyncActivities || [];
1088
- const completedActivities = await trx.count(WorkflowEvent, {
1089
- workflowInstanceId: instanceId,
1090
- eventType: "ACTIVITY_COMPLETED",
1091
- eventData: { async: true }
1092
- });
1093
- const failedActivities = await trx.count(WorkflowEvent, {
1094
- workflowInstanceId: instanceId,
1095
- eventType: "ACTIVITY_FAILED",
1096
- eventData: { async: true }
1097
- });
1098
- const totalProcessed = completedActivities + failedActivities;
1099
- if (totalProcessed < pendingJobIds.length) {
1092
+ const pendingJobIds = readPendingAsyncJobIds(instance.context._pendingAsyncActivities);
1093
+ const outcomeEvents = pendingJobIds.length === 0 ? [] : await trx.find(
1094
+ WorkflowEvent,
1095
+ {
1096
+ workflowInstanceId: instanceId,
1097
+ eventType: { $in: [...ASYNC_ACTIVITY_OUTCOME_EVENT_TYPES] },
1098
+ eventData: { async: true, jobId: { $in: pendingJobIds } }
1099
+ },
1100
+ { orderBy: { occurredAt: "asc", id: "asc" } }
1101
+ );
1102
+ const wait = resolveAsyncActivityWait(pendingJobIds, outcomeEvents);
1103
+ if (!wait.settled) {
1100
1104
  throw new Error("Activities still pending");
1101
1105
  }
1106
+ const completedEvents = wait.completed;
1107
+ const failedEvents = wait.failed;
1108
+ const completedActivities = completedEvents.length;
1109
+ const failedActivities = failedEvents.length;
1102
1110
  if (failedActivities > 0) {
1103
- const failedEvents = await trx.find(WorkflowEvent, {
1104
- workflowInstanceId: instanceId,
1105
- eventType: "ACTIVITY_FAILED",
1106
- eventData: { async: true }
1107
- });
1108
1111
  instance.status = "FAILED";
1109
1112
  instance.errorMessage = `${failedActivities} async activities failed`;
1110
1113
  instance.errorDetails = {
@@ -1128,11 +1131,6 @@ async function resumeWorkflowAfterActivities(em, container, instanceId, branchIn
1128
1131
  });
1129
1132
  return { continueExecution: false };
1130
1133
  }
1131
- const completedEvents = await trx.find(WorkflowEvent, {
1132
- workflowInstanceId: instanceId,
1133
- eventType: "ACTIVITY_COMPLETED",
1134
- eventData: { async: true }
1135
- });
1136
1134
  for (const event of completedEvents) {
1137
1135
  if (event.eventData.output) {
1138
1136
  instance.context = {