Examples
Signal Handling

Signal Handling Example

This example demonstrates how to use signals to interact with running flows in ZoopFlow. Signals provide a way to communicate with long-running flows, allowing for modifications, cancellations, and updates during execution.

What You'll Learn

  • How to define signals with type safety
  • How to register signals with a flow registry and signal manager
  • How to send signals to running workflows
  • How to handle signals within a workflow context
  • How to update flow state and execution based on signals
  • Common signal handling patterns with middleware support

Files in this Example

  • signal-definitions.ts - Demonstrates how to define signals with proper typing
  • order-processing-flow.ts - A sample flow that handles cancellation and update signals
  • flow-registration.ts - Shows how to register flows and signals with the Temporal integration
  • client.ts - Example of sending signals to a running workflow
  • run-example.ts - Entry point to run the example

Signal Handling Concepts

In ZoopFlow, signals:

  • Can be registered globally or with specific flow registries
  • Allow external systems to communicate with running workflows
  • Have defined schemas with JSON Schema validation for type-safe payloads
  • Can be processed through middleware for cross-cutting concerns
  • Can be used to update workflow state through the SignalContext
  • Enable workflows to respond to external events
  • Include standard handlers for common operations (pause, resume, cancel, update)
  • Provide history tracking and checkpoint creation
  • Are crucial for long-running business processes

Flow-Specific Signal Registration

ZoopFlow uses a flow-specific approach to signal registration, where each signal handler is associated with a specific flow. This design provides several benefits:

  • Isolation: Signals for one flow don't affect others
  • Context Relevance: Handlers receive context specific to their flow
  • Clearer Boundaries: Explicit association between flows and signals
  • Reduced Naming Conflicts: Same signal name can be used in different flows

How to Run

# From the project root
npm install
npm run build
node dist/examples/signal-handling/run-example.js

Expected Output

The example will:

  1. Start an order processing workflow
  2. Send a signal to update the shipping address
  3. Optionally send a signal to cancel the order
  4. Show how the workflow responds to these signals
  5. Demonstrate state changes based on signals

Output will show signal delivery and how the flow execution changes in response.

Code Example: Signal Registration

// flow-registration.ts
import { registerTemporalFlowControlSignals } from '@zoop/flow/core/signals/temporal';
import { SignalRegistryImpl } from '@zoop/flow/core/signals/core';
import { SignalManager, createLoggingMiddleware } from '@zoop/flow/core/signals/manager';
import { orderProcessingFlow } from './order-processing-flow';
import { 
  cancelOrderSignal,
  updateAddressSignal,
  paymentReceivedSignal
} from './signal-definitions';
 
// Create a registry specific to this flow
const orderFlowRegistry = new SignalRegistryImpl();
 
// Register signals with the registry
orderFlowRegistry.registerSignal(cancelOrderSignal);
orderFlowRegistry.registerSignal(updateAddressSignal);
orderFlowRegistry.registerSignal(paymentReceivedSignal);
 
// Create a signal manager with middleware
const signalManager = new SignalManager(orderFlowRegistry, {
  queueSignalsUntilInitialized: true,
  middleware: [
    createLoggingMiddleware(),
    // Add more middleware as needed
  ]
});
 
// When setting up the Temporal workflow
function setupOrderWorkflow(workflowId: string, executionId: string, temporalContext: any) {
  // Register standard flow control signals (pause, resume, cancel, update)
  registerTemporalFlowControlSignals(
    orderProcessingFlow.id,
    executionId,
    temporalContext
  );
  
  // Initialize the signal manager
  signalManager.markInitialized();
}

Sending Signals to a Flow

// client.ts
import { Connection, WorkflowClient } from '@temporalio/client';
 
async function sendSignalToWorkflow() {
  // Connect to Temporal server
  const connection = await Connection.connect();
  const client = new WorkflowClient({ connection });
  
  // Get a handle to the running workflow
  const workflowId = 'order-123456';
  const handle = client.getHandle(workflowId);
  
  // Send a signal to update the shipping address
  // The signal ID format is namespace.name
  await handle.signal('order.updateAddress', {
    orderId: workflowId,
    address: {
      street: '123 Main St',
      city: 'Anytown',
      state: 'CA',
      zip: '12345'
    }
  });
  
  console.log('Address update signal sent successfully');
}

Next Steps

  • Create your own custom signals for specific flow scenarios
  • Implement middleware for custom validation, logging, or transformation
  • Use the history tracking and checkpoint features for auditability
  • Learn about integration with Temporal in the Temporal Integration Example
  • Implement a real-world use case with flow-specific signals
  • Try the built-in flow control signals (pause, resume, cancel, update)
  • Explore signal state management for complex workflow control