@skynetxbt/flow-loop
Version:
Loop Flow for iterative operations with done/continue logic
374 lines (307 loc) • 9.82 kB
Markdown
# Simple Loop Flow
A simple loop flow for SkynetXBT that executes another flow internally for a specified number of iterations.
## Features
- **Internal Execution** - Executes another flow internally, no complex flow graph looping needed
- **Simple Interface** - Pass a flow and iteration count, get results when done
- **Placeholder Support** - Replace `${{LOOPINPUT}}` with current iteration number
- **Error Handling** - Continues execution even if individual iterations fail
- **Result Collection** - Collects all iteration results and errors
- **Progress Tracking** - Detailed progress and completion status
## Usage
### Basic Usage
```typescript
import LoopFlow from '@skynetxbt/flow-loop';
import SomeOtherFlow from '@skynetxbt/some-other-flow';
// Create the flow you want to loop
const flowToLoop = new SomeOtherFlow();
// Create a loop that runs the flow 5 times
const loopFlow = new LoopFlow({
totalIterations: 5,
flowToLoop: flowToLoop
});
// Execute the loop - it will run the flow 5 times internally
const result = await loopFlow.execute({
agentId: { generation: 0, familyCode: "", serialNumber: "" },
sessionId: 'session123',
userPublicKey: 'user123',
message: {
data: { message: "Process this 5 times" }
},
variables: {}
});
console.log(result);
// Output: {
// result: 'done',
// totalIterations: 5,
// completedIterations: 5,
// results: [
// { iteration: 1, result: {...}, success: true },
// { iteration: 2, result: {...}, success: true },
// // ... etc
// ],
// progress: 'Successfully completed all 5 iterations',
// success: true
// }
```
### In Flow Graph Configuration
```typescript
import ProcessingFlow from '@skynetxbt/some-processing-flow';
// Create the processing flow
const processingFlow = new ProcessingFlow();
// Create loop flow
const loopFlow = new LoopFlow({
totalIterations: 10,
flowToLoop: processingFlow
});
// Simple flow graph - no complex looping logic needed
const graphConfig = {
name: "SimpleLoopExample",
flows: {
entry: {
name: "entry",
outNode: { flows: ["loop-step"] }
},
"loop-step": {
name: "LoopFlow", // This will execute processingFlow 10 times internally
outNode: { flows: ["end-step"] }
},
"end-step": {
name: "YourEndFlow"
}
}
};
```
## Input Format
The loop flow accepts input in the following format:
```typescript
{
totalIterations?: number, // Override constructor value (optional)
data?: any // Data to pass to each iteration
}
```
Each iteration of the internal flow receives:
```typescript
{
currentIteration: number, // Current iteration number (1-based)
totalIterations: number, // Total number of iterations
data: any, // Original data passed in with ${{LOOPINPUT}} replaced
...originalInput // All other original input properties with ${{LOOPINPUT}} replaced
}
```
## Placeholder Replacement
The loop flow supports a special placeholder `${{LOOPINPUT}}` that gets replaced with the current iteration number (1-based) in all string values within the input data.
### Placeholder Examples
```typescript
// Input data with placeholders
const inputData = {
data: {
filename: "file_${{LOOPINPUT}}.txt",
message: "Processing iteration ${{LOOPINPUT}} of 5",
config: {
batchId: "batch_${{LOOPINPUT}}",
settings: ["setting_${{LOOPINPUT}}_a", "setting_${{LOOPINPUT}}_b"]
}
}
};
// Iteration 1 receives:
{
data: {
filename: "file_1.txt",
message: "Processing iteration 1 of 5",
config: {
batchId: "batch_1",
settings: ["setting_1_a", "setting_1_b"]
}
}
}
// Iteration 3 receives:
{
data: {
filename: "file_3.txt",
message: "Processing iteration 3 of 5",
config: {
batchId: "batch_3",
settings: ["setting_3_a", "setting_3_b"]
}
}
}
```
The placeholder replacement works:
- **Recursively** - In nested objects and arrays
- **Multiple times** - Multiple `${{LOOPINPUT}}` in the same string
- **Case sensitive** - Only exact `${{LOOPINPUT}}` format is replaced
- **String values only** - Numbers, booleans, and null values are preserved as-is
## Output Format
The loop flow returns when ALL iterations are complete:
```typescript
{
result: 'done', // Always 'done' when returned
totalIterations: number, // Total iterations configured
completedIterations: number, // How many actually completed
results: Array<{ // Results from each iteration
iteration: number,
result: any, // Result from the flow execution
success: boolean
}>,
progress: string, // Human-readable progress
success: boolean, // True if all iterations succeeded
errors?: Array<{ // Only present if there were errors
iteration: number,
error: string,
success: false
}>
}
```
## Constructor
```typescript
new LoopFlow(config: {
totalIterations: number, // Required: Number of times to execute the flow
flowToLoop: IFlow // Required: The flow to execute repeatedly
})
```
## Methods
- `setTotalIterations(total: number)`: Update the total iteration count
- `getTotalIterations()`: Get current total iteration count
- `getFlowToLoop()`: Get the flow that will be looped
## Examples
### Loop a Google Sheets Flow
```typescript
import GoogleSheetsFlow from '@skynetxbt/flow-google';
const sheetsFlow = new GoogleSheetsFlow({
service: 'sheets',
operation: 'append',
spreadsheetId: 'your-sheet-id',
range: 'A:C'
});
const loopFlow = new LoopFlow({
totalIterations: 10,
flowToLoop: sheetsFlow
});
const result = await loopFlow.execute({
agentId: { generation: 0, familyCode: "", serialNumber: "" },
sessionId: 'session123',
userPublicKey: 'user123',
message: {
data: {
values: [["Data", "To", "Append"]]
}
},
variables: {}
});
// This will append the same row to the sheet 10 times
```
### Loop with Placeholders
```typescript
import ProcessingFlow from '@skynetxbt/some-processing-flow';
const processingFlow = new ProcessingFlow();
const loopFlow = new LoopFlow({
totalIterations: 5,
flowToLoop: processingFlow
});
const result = await loopFlow.execute({
agentId: { generation: 0, familyCode: "", serialNumber: "" },
sessionId: 'session123',
userPublicKey: 'user123',
message: {
data: {
filename: "report_${{LOOPINPUT}}.pdf",
title: "Report #${{LOOPINPUT}}",
config: {
outputPath: "/reports/batch_${{LOOPINPUT}}/",
settings: {
name: "Batch ${{LOOPINPUT}} Settings",
priority: "high"
}
}
}
},
variables: {}
});
// Each iteration will receive:
// Iteration 1: filename: "report_1.pdf", title: "Report #1", outputPath: "/reports/batch_1/"
// Iteration 2: filename: "report_2.pdf", title: "Report #2", outputPath: "/reports/batch_2/"
// ... and so on
```
### Loop with Dynamic Data
```typescript
import ProcessingFlow from '@skynetxbt/some-processing-flow';
const processingFlow = new ProcessingFlow();
const loopFlow = new LoopFlow({
totalIterations: 3,
flowToLoop: processingFlow
});
const result = await loopFlow.execute({
agentId: { generation: 0, familyCode: "", serialNumber: "" },
sessionId: 'session123',
userPublicKey: 'user123',
message: {
totalIterations: 5, // Override constructor value
data: {
baseMessage: "Process this"
}
},
variables: {}
});
// Each iteration will receive:
// Iteration 1: { currentIteration: 1, totalIterations: 5, data: { baseMessage: "Process this" } }
// Iteration 2: { currentIteration: 2, totalIterations: 5, data: { baseMessage: "Process this" } }
// etc.
```
### Error Handling Example
```typescript
import UnreliableFlow from '@skynetxbt/unreliable-flow';
const unreliableFlow = new UnreliableFlow(); // This flow might fail sometimes
const loopFlow = new LoopFlow({
totalIterations: 5,
flowToLoop: unreliableFlow
});
const result = await loopFlow.execute({
agentId: { generation: 0, familyCode: "", serialNumber: "" },
sessionId: 'session123',
userPublicKey: 'user123',
message: { data: "test" },
variables: {}
});
if (result.success) {
console.log("All iterations completed successfully!");
} else {
console.log(`${result.errors?.length} iterations failed:`);
result.errors?.forEach(error => {
console.log(`Iteration ${error.iteration}: ${error.error}`);
});
}
// The loop continues even if some iterations fail
// You get detailed results for each iteration
```
## Advantages of This Approach
1. **No Complex Flow Graphs**: No need to set up complex looping logic in flow graphs
2. **Clean Results**: Get all results at once when the loop completes
3. **Error Resilience**: Individual iteration failures don't stop the entire loop
4. **Simple Integration**: Works like any other flow in your flow graphs
5. **Stateless**: No session state management needed
6. **Predictable**: Always returns when done, never needs external loop control
## Integration with Flow Graphs
Since the loop flow handles all iteration internally, it integrates cleanly into flow graphs:
```typescript
const graphConfig = {
name: "DataProcessingPipeline",
flows: {
"fetch-data": {
name: "FetchDataFlow",
outNode: { flows: ["process-loop"] }
},
"process-loop": {
name: "LoopFlow", // Executes processing flow 10 times
outNode: { flows: ["save-results"] }
},
"save-results": {
name: "SaveResultsFlow"
}
}
};
```
The loop flow will:
1. Receive data from "fetch-data"
2. Execute its internal flow 10 times
3. Return complete results to "save-results"
No complex conditional branching or state management required!