UNPKG

@skynetxbt/flow-loop

Version:

Loop Flow for iterative operations with done/continue logic

374 lines (307 loc) 9.82 kB
# 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!