Basic Step Definition Example
This example demonstrates how to create, register, and execute basic steps in ZoopFlow. Steps are the fundamental building blocks of workflows in ZoopFlow.
The source code for this example is available on GitHub.
What You'll Learn
- How to define a step with input and output schemas
- How to implement a step's execution logic
- How to register steps with a registry
- How to execute a step and validate its inputs/outputs
- How to add custom validation to steps
Step Definition
A step in ZoopFlow is defined using the defineStep function, which takes a configuration object:
import { defineStep } from '@zoopflow/core';
const calculateTotal = defineStep({
id: 'order.total.calculate',
version: '1.0.0',
description: 'Calculates the total price for an order',
// Input validation schema
inputSchema: {
type: 'object',
properties: {
items: {
type: 'array',
items: {
type: 'object',
properties: {
productId: { type: 'string' },
quantity: { type: 'number' },
price: { type: 'number' }
},
required: ['productId', 'quantity', 'price']
}
},
taxRate: { type: 'number', default: 0 }
},
required: ['items']
},
// Output validation schema
outputSchema: {
type: 'object',
properties: {
subtotal: { type: 'number' },
tax: { type: 'number' },
total: { type: 'number' }
},
required: ['subtotal', 'tax', 'total']
},
// Step implementation
execute: async (input, context) => {
// Calculate the subtotal
const subtotal = input.items.reduce(
(sum, item) => sum + (item.price * item.quantity),
0
);
// Calculate tax
const tax = subtotal * (input.taxRate || 0);
// Calculate total
const total = subtotal + tax;
// Return the result
return {
subtotal,
tax,
total
};
}
});Step Flow Visualization
The example above performs a calculation. Here's a visualization of the step's execution flow:
Step Execution
Once a step is defined, it can be executed directly or as part of a flow:
import { executeStep } from '@zoopflow/core';
// Sample input data
const orderData = {
items: [
{ productId: 'product1', quantity: 2, price: 10.99 },
{ productId: 'product2', quantity: 1, price: 24.99 }
],
taxRate: 0.08 // 8% tax
};
// Execute the step
const result = await executeStep(calculateTotal, orderData);
console.log(result);
// Output:
// {
// subtotal: 46.97,
// tax: 3.76,
// total: 50.73
// }Files in this Example
basic-step-definition.ts- Demonstrates how to define a basic stepstep-registry-example.ts- Shows how to register and use steps with a registryrun-example.ts- Entry point to run the example
Interactive Playground
Try the example in our interactive playground below:
Basic Step Example
import React, { useState } from 'react'; import { defineStep } from './zoopflow'; // Define the calculateTotal step const calculateTotal = defineStep({ id: 'order.total.calculate', version: '1.0.0', description: 'Calculates the total price for an order', execute: async (input, context) => { // Calculate the subtotal const subtotal = input.items.reduce( (sum, item) => sum + (item.price * item.quantity), 0 ); // Calculate tax const tax = subtotal * (input.taxRate || 0); // Calculate total const total = subtotal + tax; // Return the result return { subtotal, tax, total }; } }); // Product data const products = [ { id: 'product1', name: 'Widget A', price: 10.99 }, { id: 'product2', name: 'Widget B', price: 24.99 }, { id: 'product3', name: 'Widget C', price: 15.49 } ]; export function App() { const [cart, setCart] = useState([]); const [taxRate, setTaxRate] = useState(0.08); const [result, setResult] = useState(null); // Add product to cart const addToCart = (product) => { const existing = cart.find(item => item.productId === product.id); if (existing) { setCart(cart.map(item => item.productId === product.id ? { ...item, quantity: item.quantity + 1 } : item )); } else { setCart([...cart, { productId: product.id, name: product.name, price: product.price, quantity: 1 }]); } }; // Remove item from cart const removeFromCart = (productId) => { setCart(cart.filter(item => item.productId !== productId)); }; // Calculate order total const calculateOrder = async () => { try { const orderData = { items: cart.map(item => ({ productId: item.productId, price: item.price, quantity: item.quantity })), taxRate }; // Execute the step const result = await calculateTotal.execute(orderData, { logger: { info: console.log, error: console.error } }); setResult(result); } catch (err) { console.error('Error calculating total:', err); } }; return ( <div className="container"> <h2>Order Calculator Example</h2> <div className="panel"> <h3>Products</h3> <div className="product-list"> {products.map(product => ( <div key={product.id} className="product-item"> <div> <strong>{`${product.name}`}</strong> <div>{`$${product.price.toFixed(2)}`}</div> </div> <button onClick={() => addToCart(product)}>Add to Cart</button> </div> ))} </div> </div> <div className="panel"> <h3>Shopping Cart</h3> {cart.length === 0 ? ( <p>Your cart is empty</p> ) : ( <> <div className="cart-items"> {cart.map(item => ( <div key={item.productId} className="cart-item"> <div> <strong>{`${item.name}`}</strong> × {`${item.quantity}`} <div>{`$${(item.price * item.quantity).toFixed(2)}`}</div> </div> <button className="remove-button" onClick={() => removeFromCart(item.productId)} > ✕ </button> </div> ))} </div> <div className="tax-rate"> <label> Tax Rate: <input type="number" min="0" max="1" step="0.01" value={taxRate} onChange={(e) => setTaxRate(Number(e.target.value))} /> </label> </div> <button className="primary-button" onClick={calculateOrder} disabled={cart.length === 0} > Calculate Total </button> </> )} </div> {result && ( <div className="panel result"> <h3>Order Summary</h3> <div className="summary-item"> <span>Subtotal:</span> <span>{`$${result.subtotal.toFixed(2)}`}</span> </div> <div className="summary-item"> <span>Tax {`(${(taxRate * 100).toFixed(0)}%)`}:</span> <span>{`$${result.tax.toFixed(2)}`}</span> </div> <div className="summary-item total"> <span>Total:</span> <span>{`$${result.total.toFixed(2)}`}</span> </div> </div> )} <style>{ ` .container { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; } .panel { background-color: #f8f8f8; border-radius: 8px; padding: 16px; margin-bottom: 20px; border: 1px solid #e0e0e0; } h2, h3 { margin-top: 0; } .product-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; } .product-item { background-color: white; padding: 12px; border-radius: 6px; border: 1px solid #e0e0e0; display: flex; flex-direction: column; justify-content: space-between; } .cart-items { margin-bottom: 16px; } .cart-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid #e0e0e0; } .tax-rate { margin: 16px 0; } .tax-rate input { width: 60px; margin-left: 8px; padding: 4px; } .summary-item { display: flex; justify-content: space-between; margin: 8px 0; } .summary-item.total { font-weight: bold; font-size: 1.2em; margin-top: 16px; padding-top: 8px; border-top: 1px solid #e0e0e0; } button { background-color: #f0f0f0; border: 1px solid #ccc; border-radius: 4px; padding: 6px 12px; cursor: pointer; font-weight: 500; transition: all 0.2s; } button:hover { background-color: #e0e0e0; } .primary-button { background-color: #00f173; color: black; padding: 8px 16px; font-weight: 600; width: 100%; margin-top: 12px; } .primary-button:hover { background-color: #00d363; } .remove-button { background: transparent; border: none; color: #ff5252; font-weight: bold; } ` }</style> </div> ); }
Key Concepts
- Step Definition: Steps are defined with
defineStepand include metadata and implementation. - Input Validation: The
inputSchemavalidates inputs before execution. - Output Validation: The
outputSchemavalidates the step's return value. - Execution Context: The context parameter provides utilities and services during execution.
- Error Handling: Steps can include error handling logic in their implementation.
Next Steps
- Try modifying the steps to add new operations
- Implement additional validation logic
- Explore how to compose steps into a flow in the Flow Definition Example
- Learn more about Error Handling patterns