( tx: any, workflowId: string, operation: string, payload: any )
| 1625 | } |
| 1626 | |
| 1627 | async function handleSubflowOperationTx( |
| 1628 | tx: any, |
| 1629 | workflowId: string, |
| 1630 | operation: string, |
| 1631 | payload: any |
| 1632 | ) { |
| 1633 | switch (operation) { |
| 1634 | case SUBFLOW_OPERATIONS.UPDATE: { |
| 1635 | if (!payload.id || !payload.config) { |
| 1636 | throw new Error('Missing required fields for update subflow operation') |
| 1637 | } |
| 1638 | |
| 1639 | logger.debug(`Updating subflow ${payload.id} with config:`, payload.config) |
| 1640 | |
| 1641 | // Read-modify-write merge so partial config payloads never wipe other fields |
| 1642 | // (e.g. an iteration-only update from one client should not drop batchSize set by another) |
| 1643 | const existingSubflow = await tx |
| 1644 | .select({ config: workflowSubflows.config }) |
| 1645 | .from(workflowSubflows) |
| 1646 | .where( |
| 1647 | and(eq(workflowSubflows.id, payload.id), eq(workflowSubflows.workflowId, workflowId)) |
| 1648 | ) |
| 1649 | .limit(1) |
| 1650 | |
| 1651 | const existingConfig = (existingSubflow[0]?.config as Record<string, unknown>) || {} |
| 1652 | const mergedConfig = { ...existingConfig, ...payload.config } |
| 1653 | |
| 1654 | const updateResult = await tx |
| 1655 | .update(workflowSubflows) |
| 1656 | .set({ |
| 1657 | config: mergedConfig, |
| 1658 | updatedAt: new Date(), |
| 1659 | }) |
| 1660 | .where( |
| 1661 | and(eq(workflowSubflows.id, payload.id), eq(workflowSubflows.workflowId, workflowId)) |
| 1662 | ) |
| 1663 | .returning({ id: workflowSubflows.id }) |
| 1664 | |
| 1665 | if (updateResult.length === 0) { |
| 1666 | throw new Error(`Subflow ${payload.id} not found in workflow ${workflowId}`) |
| 1667 | } |
| 1668 | |
| 1669 | logger.debug(`Successfully updated subflow ${payload.id} in database`) |
| 1670 | |
| 1671 | // Also update the corresponding block's data to keep UI in sync |
| 1672 | if (payload.type === 'loop') { |
| 1673 | const existingBlock = await tx |
| 1674 | .select({ data: workflowBlocks.data }) |
| 1675 | .from(workflowBlocks) |
| 1676 | .where(and(eq(workflowBlocks.id, payload.id), eq(workflowBlocks.workflowId, workflowId))) |
| 1677 | .limit(1) |
| 1678 | |
| 1679 | const existingData = (existingBlock[0]?.data as any) || {} |
| 1680 | |
| 1681 | const blockData: any = { |
| 1682 | ...existingData, |
| 1683 | count: payload.config.iterations ?? existingData.count ?? DEFAULT_LOOP_ITERATIONS, |
| 1684 | loopType: payload.config.loopType ?? existingData.loopType ?? 'for', |
no test coverage detected