# 🔄 FX Flow Plugin - Complete Cheat Sheet ## 🎯 **Plugin Overview** **FX Flow** provides dynamic, reactive, cross-realm procedure graphs for complex business logic and multi-step workflows that can execute on client, server, or both seamlessly. ### **Core Features** - ✅ **Cross-Realm Execution** - Run nodes on client, server, or both - ✅ **Dynamic Flow Creation** - Build flows programmatically - ✅ **Reactive Integration** - Flows integrate with FX reactive system - ✅ **Zero Latency** - Uses FX's sync surface with background execution - ✅ **Error Handling** - Built-in retry, timeout, and error recovery - ✅ **State Management** - Flow state stored in FX nodes --- ## 📦 **Installation & Setup** ### **Auto-Loading (Recommended)** ```javascript // Just use $flow - auto-loads fx-flow plugin const userFlow = $flow('userRegistration') .node('validate', { runsOn: 'client', effect: validateUser }) .node('save', { runsOn: 'server', effect: saveUser }) .start('validate', userData); ``` ### **Manual Loading** ```html ``` --- ## 🚀 **Core API Reference** ### **Flow Creation** ```typescript $flow(flowName: string) ``` | Method | Description | Example | |--------|-------------|---------| | `.node(name, config)` | Add flow node | `.node('validate', { runsOn: 'client', effect: fn })` | | `.connect(from, to)` | Connect nodes | `.connect('validate', 'save')` | | `.start(nodeName, data)` | Start flow execution | `.start('validate', userData)` | | `.status()` | Get flow status | `.status()` | | `.result()` | Get flow result | `.result()` | ### **Node Configuration** ```typescript { runsOn: 'client' | 'server' | 'both', effect: (ctx) => { /* node logic */ }, timeout?: number, retries?: number, guards?: (ctx) => boolean } ``` --- ## 🎪 **Basic Usage Patterns** ### **Simple Linear Flow** ```typescript // User registration workflow const registrationFlow = $flow('userRegistration') .node('validateInput', { runsOn: 'client', effect: (ctx) => { const { email, password } = ctx.in; if (!email.includes('@')) { ctx.error('Invalid email format'); return; } if (password.length < 8) { ctx.error('Password too short'); return; } ctx.next('checkExisting', { email, password }); } }) .node('checkExisting', { runsOn: 'server', effect: (ctx) => { const existingUser = $db.users.where(`[email="${ctx.in.email}"]`).first(); if (existingUser) { ctx.error('Email already registered'); return; } ctx.next('createUser', ctx.in); } }) .node('createUser', { runsOn: 'server', effect: (ctx) => { const userId = $db.users.create({ email: ctx.in.email, password: hashPassword(ctx.in.password), createdAt: Date.now() }); ctx.set({ userId, success: true }); } }); // Execute the flow const result = registrationFlow.start('validateInput', { email: 'user@example.com', password: 'securepass123' }); // Watch for completion result.watch(data => { if (data.success) { console.log('User registered with ID:', data.userId); } }); ``` ### **Branching Flow with Conditions** ```typescript // Order processing with multiple paths const orderFlow = $flow('orderProcessing') .node('validateOrder', { runsOn: 'both', effect: (ctx) => { const order = ctx.in; if (!order.items?.length) { ctx.error('Empty order'); return; } // Branch based on order type if (order.type === 'subscription') { ctx.next('processSubscription', order); } else { ctx.next('processOneTime', order); } } }) .node('processSubscription', { runsOn: 'server', effect: (ctx) => { // Handle subscription logic const subscription = $db.subscriptions.create(ctx.in); ctx.next('sendConfirmation', { type: 'subscription', id: subscription.id }); } }) .node('processOneTime', { runsOn: 'server', effect: (ctx) => { // Handle one-time purchase const order = $db.orders.create(ctx.in); ctx.next('sendConfirmation', { type: 'order', id: order.id }); } }) .node('sendConfirmation', { runsOn: 'both', effect: (ctx) => { const emailResult = $api.post('/emails/confirmation', ctx.in); emailResult.watch(result => { ctx.set({ emailSent: result.success, ...ctx.in }); }); } }); ``` ### **Parallel Execution Flow** ```typescript // Data processing with parallel steps const dataProcessingFlow = $flow('dataProcessing') .node('splitData', { runsOn: 'server', effect: (ctx) => { const data = ctx.in; const chunks = chunkArray(data, 100); // Start parallel processing chunks.forEach((chunk, index) => { ctx.next(`processChunk${index}`, chunk); }); } }); // Add dynamic processing nodes for (let i = 0; i < maxChunks; i++) { dataProcessingFlow.node(`processChunk${i}`, { runsOn: 'server', effect: (ctx) => { const processedChunk = processData(ctx.in); ctx.next('collectResults', { chunkIndex: i, data: processedChunk }); } }); } dataProcessingFlow.node('collectResults', { runsOn: 'server', effect: (ctx) => { // Collect all processed chunks const results = $$('flows.dataProcessing.results').get() || []; results.push(ctx.in); $$('flows.dataProcessing.results').set(results); // Check if all chunks processed if (results.length >= expectedChunks) { ctx.set({ allChunks: results, completed: true }); } } }); ``` --- ## 🛡️ **Error Handling & Resilience** ### **Retry and Timeout** ```typescript const resilientFlow = $flow('resilientAPI') .node('callExternalAPI', { runsOn: 'server', timeout: 10000, // 10 second timeout retries: 3, // Retry 3 times effect: (ctx) => { const response = $api.get('https://external-api.com/data'); response.watch((data, error) => { if (error) { ctx.retry(error); // Triggers automatic retry } else { ctx.next('processResponse', data); } }); } }) .node('processResponse', { runsOn: 'both', effect: (ctx) => { const processedData = processAPIResponse(ctx.in); ctx.set(processedData); } }); ``` ### **Error Recovery** ```typescript const recoveryFlow = $flow('withRecovery') .node('riskyOperation', { runsOn: 'server', effect: (ctx) => { try { const result = performRiskyOperation(ctx.in); ctx.next('success', result); } catch (error) { ctx.next('handleError', { error: error.message, input: ctx.in }); } } }) .node('handleError', { runsOn: 'server', effect: (ctx) => { // Log error to FX $$('flows.errors').set({ flow: 'withRecovery', error: ctx.in.error, timestamp: Date.now() }); // Attempt recovery ctx.next('recovery', ctx.in.input); } }) .node('recovery', { runsOn: 'server', effect: (ctx) => { const recoveredResult = performRecoveryOperation(ctx.in); ctx.set({ recovered: true, result: recoveredResult }); } }) .node('success', { runsOn: 'both', effect: (ctx) => { ctx.set({ success: true, result: ctx.in }); } }); ``` --- ## 🎯 **Real-World Examples** ### **E-commerce Checkout Flow** ```typescript const checkoutFlow = $flow('ecommerceCheckout') .node('validateCart', { runsOn: 'client', effect: (ctx) => { const cart = $$('cart.items').get(); if (!cart?.length) { ctx.error('Cart is empty'); return; } ctx.next('calculateTotal', cart); } }) .node('calculateTotal', { runsOn: 'both', effect: (ctx) => { const items = ctx.in; const subtotal = items.reduce((sum, item) => sum + (item.price * item.quantity), 0); const tax = subtotal * 0.1; const total = subtotal + tax; ctx.next('processPayment', { items, subtotal, tax, total }); } }) .node('processPayment', { runsOn: 'server', timeout: 30000, retries: 2, effect: (ctx) => { const paymentData = ctx.in; $safe.circuit('payment-processor').execute(() => { const payment = $api.post('/payments/process', paymentData); payment.watch((result, error) => { if (error) { ctx.error('Payment failed: ' + error.message); } else { ctx.next('createOrder', { ...paymentData, paymentId: result.id }); } }); }); } }) .node('createOrder', { runsOn: 'server', effect: (ctx) => { const orderId = $db.orders.create({ items: ctx.in.items, total: ctx.in.total, paymentId: ctx.in.paymentId, status: 'confirmed', createdAt: Date.now() }); ctx.next('sendConfirmation', { orderId }); } }) .node('sendConfirmation', { runsOn: 'both', effect: (ctx) => { // Send email confirmation $api.post('/emails/order-confirmation', ctx.in); // Clear cart $$('cart.items').set([]); // Update UI $$('checkout.status').set('completed'); $$('checkout.orderId').set(ctx.in.orderId); ctx.set({ success: true, orderId: ctx.in.orderId }); } }); // Usage in checkout component function processCheckout(cartData) { const result = checkoutFlow.start('validateCart', cartData); result.watch(data => { if (data.success) { showSuccessMessage(`Order ${data.orderId} confirmed!`); redirectToOrderPage(data.orderId); } }); } ``` ### **User Onboarding Flow** ```typescript const onboardingFlow = $flow('userOnboarding') .node('collectInfo', { runsOn: 'client', effect: (ctx) => { // Show onboarding form jsx.render(`

Welcome! Let's set up your profile

`, '#onboarding-container'); // Wait for form submission $$('onboarding.formData').watch(formData => { if (formData) { ctx.next('validateProfile', formData); } }); } }) .node('validateProfile', { runsOn: 'server', effect: (ctx) => { const { name, role } = ctx.in; // Validate with external service const validation = $api.post('/validation/profile', { name, role }); validation.watch(result => { if (result.valid) { ctx.next('createProfile', ctx.in); } else { ctx.error('Profile validation failed: ' + result.message); } }); } }) .node('createProfile', { runsOn: 'server', effect: (ctx) => { const profileId = $db.profiles.create(ctx.in); ctx.next('setupPreferences', { ...ctx.in, profileId }); } }) .node('setupPreferences', { runsOn: 'client', effect: (ctx) => { // Set default preferences in FX $$('user.preferences').set({ theme: 'light', notifications: true, language: 'en' }); ctx.next('welcome', ctx.in); } }) .node('welcome', { runsOn: 'both', effect: (ctx) => { // Update UI $$('user.currentProfile').set(ctx.in); $$('onboarding.completed').set(true); // Send welcome email $api.post('/emails/welcome', { profileId: ctx.in.profileId }); ctx.set({ onboarded: true, profile: ctx.in }); } }); ``` --- ## 🔧 **Advanced Patterns** ### **Conditional Flow Execution** ```typescript const smartFlow = $flow('smartProcessing') .node('analyze', { runsOn: 'server', effect: (ctx) => { const data = ctx.in; const analysis = analyzeData(data); // Dynamic routing based on analysis if (analysis.type === 'simple') { ctx.next('simpleProcess', data); } else if (analysis.type === 'complex') { ctx.next('complexProcess', data); } else { ctx.next('manualReview', data); } } }) .node('simpleProcess', { runsOn: 'client', effect: (ctx) => { const result = processSimple(ctx.in); ctx.set(result); } }) .node('complexProcess', { runsOn: 'server', effect: (ctx) => { const result = processComplex(ctx.in); ctx.set(result); } }) .node('manualReview', { runsOn: 'both', effect: (ctx) => { // Queue for manual review $$('reviews.queue').set([ ...$$('reviews.queue').get(), { data: ctx.in, timestamp: Date.now() } ]); ctx.set({ queued: true, reviewRequired: true }); } }); ``` ### **Flow Composition** ```typescript // Compose multiple flows const masterFlow = $flow('masterProcess') .node('initiate', { runsOn: 'server', effect: (ctx) => { // Start sub-flows const userFlow = $flow('userSubProcess').start('begin', ctx.in.userData); const dataFlow = $flow('dataSubProcess').start('begin', ctx.in.data); // Wait for both to complete Promise.all([userFlow.promise(), dataFlow.promise()]).then(results => { ctx.next('combine', { userResult: results[0], dataResult: results[1] }); }); } }) .node('combine', { runsOn: 'server', effect: (ctx) => { const combined = combineResults(ctx.in.userResult, ctx.in.dataResult); ctx.set(combined); } }); ``` --- ## 🚨 **Best Practices** ### ✅ **DO: Effective Flow Design** ```typescript // ✅ Clear node responsibilities $flow('userLogin') .node('validateCredentials', { /* validation only */ }) .node('checkPermissions', { /* permissions only */ }) .node('createSession', { /* session only */ }) .node('updateUI', { /* UI updates only */ }); // ✅ Use appropriate execution realms .node('clientValidation', { runsOn: 'client' }) // Form validation .node('serverAuth', { runsOn: 'server' }) // Database auth .node('updateBoth', { runsOn: 'both' }) // UI + state // ✅ Handle errors gracefully .node('riskyOperation', { runsOn: 'server', retries: 3, timeout: 10000, effect: (ctx) => { try { const result = riskyOperation(ctx.in); ctx.next('success', result); } catch (error) { ctx.next('handleError', { error: error.message }); } } }); ``` ### ❌ **DON'T: Common Mistakes** ```typescript // ❌ Don't put everything in one node .node('doEverything', { effect: (ctx) => { validateData(ctx.in); // Should be separate node processData(ctx.in); // Should be separate node saveData(ctx.in); // Should be separate node updateUI(ctx.in); // Should be separate node } }); // ❌ Don't ignore error handling .node('fragileOperation', { effect: (ctx) => { const result = mightFail(ctx.in); // No error handling! ctx.set(result); } }); // ❌ Don't mix execution realms inappropriately .node('clientDatabaseAccess', { runsOn: 'client', effect: (ctx) => { const users = $db.users.all(); // Database on client! ctx.set(users); } }); ``` --- ## 🎊 **Integration with FX Ecosystem** ### **With fx-safe (Resilience)** ```typescript const resilientFlow = $flow('resilientDataFetch') .node('fetchWithCircuitBreaker', { runsOn: 'server', effect: (ctx) => { const result = $safe.circuit('external-api').execute(() => { return $api.get('https://external-api.com/data'); }); if (result.success) { ctx.next('processData', result.data); } else { ctx.next('fallbackData', { error: result.error }); } } }) .node('fallbackData', { runsOn: 'server', effect: (ctx) => { const fallback = $cache.get('lastKnownData'); ctx.set({ data: fallback, fromCache: true }); } }); ``` ### **With fx-time-travel (Debugging)** ```typescript const debuggableFlow = $flow('debuggableProcess') .node('checkpoint', { runsOn: 'server', effect: (ctx) => { // Create snapshot before risky operation const snapshot = $time.snapshot('Before risky flow operation'); ctx.next('riskyOperation', { ...ctx.in, snapshotId: snapshot.id }); } }) .node('riskyOperation', { runsOn: 'server', effect: (ctx) => { try { const result = performRiskyOperation(ctx.in); ctx.set(result); } catch (error) { // Rollback to snapshot $time.restore(ctx.in.snapshotId); ctx.error('Operation failed, state restored'); } } }); ``` This comprehensive cheat sheet covers FX Flow's powerful cross-realm execution, dynamic flow creation, and integration with the FX ecosystem for building robust, reactive workflows.