# 🌐 FX API Plugin - Complete Cheat Sheet ## 🎯 **Plugin Overview** **FX API** provides a convenient abstraction over FX core's built-in `@/api/` support. It maintains FX's zero async contamination principle while offering a clean, familiar API interface. ### **Core Features** - ✅ **All HTTP Verbs** - GET, POST, PUT, PATCH, DELETE support - ✅ **Zero Async Contamination** - Uses FX's FutureProxy internally - ✅ **Immediate Response** - Synchronous surface with background processing - ✅ **Base URL Support** - Configure API base URLs - ✅ **Request Interceptors** - Modify requests before sending - ✅ **Response Interceptors** - Transform responses after receiving - ✅ **Convenience Methods** - Cleaner syntax over raw FX @/api/ ### **Key Principle** This plugin is a **convenience wrapper** - it doesn't replace FX's built-in API support but makes it easier to use while preserving all FX benefits. --- ## 📦 **Installation & Setup** ### **Loading the Plugin** ```html ``` ### **Basic Usage** ```javascript // Simple requests - immediate FX nodes returned const users = $api.get('/users'); const newUser = $api.post('/users', { name: 'John', email: 'john@example.com' }); // Access data reactively users.watch(data => console.log('Users updated:', data)); newUser.watch(result => console.log('User created:', result)); ``` --- ## 🚀 **Core API Reference** ### **HTTP Methods** | Method | Syntax | Description | |--------|--------|-------------| | `get(url, args?)` | `$api.get('/users')` | GET request | | `post(url, body?, args?)` | `$api.post('/users', userData)` | POST request | | `put(url, body?, args?)` | `$api.put('/users/42', userData)` | PUT request | | `patch(url, body?, args?)` | `$api.patch('/users/42', updates)` | PATCH request | | `delete(url, args?)` | `$api.delete('/users/42')` | DELETE request | ### **Endpoint Builder** ```javascript // Create endpoint-specific interface const usersAPI = $api.endpoint('/users'); // Use all HTTP methods on this endpoint const allUsers = usersAPI.get(); const newUser = usersAPI.post({ name: 'John' }); const updatedUser = usersAPI.put({ id: 42, name: 'Updated' }); const patchedUser = usersAPI.patch({ name: 'Patched' }); usersAPI.delete(); ``` ### **Base URL Builder** ```javascript // Create API instance with different base URL const v1API = $api.base('https://api.example.com/v1'); const v2API = $api.base('https://api.example.com/v2'); // Use different versions const v1Users = v1API.get('/users'); const v2Users = v2API.get('/users'); ``` --- ## 🔧 **Advanced Configuration** ### **Request Interceptors** ```javascript // Add authentication headers automatically const authenticatedAPI = $api.addRequestInterceptor((config) => { const token = $$('auth.token').get(); if (token) { config.headers = { ...config.headers, 'Authorization': `Bearer ${token}` }; } return config; }); // Add API versioning const versionedAPI = $api.addRequestInterceptor((config) => { config.headers = { ...config.headers, 'API-Version': '2024-01-01' }; return config; }); ``` ### **Response Interceptors** ```javascript // Transform response data const transformedAPI = $api.addResponseInterceptor((response, config) => { // Add metadata to all responses return { ...response, _metadata: { requestedAt: Date.now(), endpoint: config.url, method: config.method } }; }); // Error handling interceptor const errorHandledAPI = $api.addResponseInterceptor((response, config) => { if (response.error) { // Log errors to FX node for global error handling $$('api.errors').set({ error: response.error, endpoint: config.url, timestamp: Date.now() }); } return response; }); ``` ### **Configuration Chaining** ```javascript // Build complex API configurations const productionAPI = $api .base('https://prod-api.example.com') .defaults({ headers: { 'Environment': 'production', 'Client-Version': '1.0.0' } }) .addRequestInterceptor(authInterceptor) .addResponseInterceptor(errorInterceptor) .addResponseInterceptor(loggingInterceptor); // Use configured API const users = productionAPI.get('/users'); const metrics = productionAPI.get('/analytics/metrics'); ``` --- ## ⚡ **FX Integration Patterns** ### **Direct FX Core Access** ```javascript // Escape hatch to FX core's @/api/ syntax const directNode = $api.fx('/users'); // This is equivalent to: const directNode2 = $$('@/api/users'); // Both return FX nodes with reactive capabilities directNode.watch(data => updateUI(data)); ``` ### **Reactive Data Binding** ```javascript // API responses automatically become reactive FX nodes const users = $api.get('/users'); // Bind to DOM using fx-dom-dollar $('#user-count').text(users.length); $('#user-list').html(users.map(u => `
  • ${u.name}
  • `).join('')); // React to data changes users.watch(userData => { $$('app.stats.userCount').set(userData.length); $$('app.cache.lastUserFetch').set(Date.now()); }); ``` ### **Complex API Workflows** ```javascript // Multi-step API workflows with FX reactivity function createUserWorkflow(userData) { // Step 1: Validate user data const validation = $api.post('/validate/user', userData); validation.watch(result => { if (result.valid) { // Step 2: Create user const newUser = $api.post('/users', userData); newUser.watch(user => { // Step 3: Send welcome email $api.post('/emails/welcome', { userId: user.id }); // Step 4: Update app state $$('app.currentUser').set(user); $$('notifications.success').set('User created successfully!'); }); } else { $$('notifications.error').set(result.errors); } }); } ``` --- ## 🎪 **Real-World Examples** ### **Authentication Flow** ```javascript // Login system with reactive state management class AuthAPI { static login(credentials) { const response = $api.post('/auth/login', credentials); response.watch(result => { if (result.success) { $$('auth.token').set(result.token); $$('auth.user').set(result.user); $$('auth.isLoggedIn').set(true); // Redirect to dashboard $$('router.currentPath').set('/dashboard'); } else { $$('auth.error').set(result.message); } }); return response; } static logout() { const response = $api.post('/auth/logout'); response.watch(() => { // Clear auth state $$('auth.token').set(null); $$('auth.user').set(null); $$('auth.isLoggedIn').set(false); // Redirect to login $$('router.currentPath').set('/login'); }); return response; } static refreshToken() { const currentToken = $$('auth.token').get(); const response = $api.post('/auth/refresh', { token: currentToken }); response.watch(result => { if (result.token) { $$('auth.token').set(result.token); } else { // Token refresh failed, logout user this.logout(); } }); return response; } } // Usage - completely synchronous from user perspective AuthAPI.login({ email: 'user@example.com', password: 'secret' }); // Auth state updates automatically, UI reacts immediately ``` ### **Data Management with Caching** ```javascript // Smart data fetching with automatic caching class DataManager { static getUsers(filters = {}) { // Create cache-friendly endpoint const endpoint = `/users?${new URLSearchParams(filters).toString()}`; // Get data using FX API const response = $api.get(endpoint); // Set up reactive caching response.watch(users => { // Cache in FX for 5 minutes $$('cache.users').set(users); $$('cache.users.timestamp').set(Date.now()); // Update global user list $$('app.users.list').set(users); $$('app.users.count').set(users.length); }); return response; } static getUserProfile(userId) { // Check cache first const cached = $$('cache.userProfiles')[userId]; const cacheAge = Date.now() - (cached?.timestamp || 0); if (cached && cacheAge < 300000) { // 5 minutes return cached.data; } // Fetch fresh data const response = $api.get(`/users/${userId}/profile`); response.watch(profile => { // Cache profile data $$(`cache.userProfiles.${userId}`).set({ data: profile, timestamp: Date.now() }); // Update current profile if it's the active user if ($$('app.currentUser.id').get() === userId) { $$('app.currentUser.profile').set(profile); } }); return response; } static createUser(userData) { const response = $api.post('/users', userData); response.watch(newUser => { // Add to local users list const currentUsers = $$('app.users.list').get() || []; $$('app.users.list').set([...currentUsers, newUser]); // Update count $$('app.users.count').set(currentUsers.length + 1); // Clear cache to force refresh $$('cache.users').set(null); // Show success notification $$('notifications.success').set(`User ${newUser.name} created!`); }); return response; } } ``` ### **Real-Time API Integration** ```javascript // Real-time dashboard with reactive API calls class DashboardAPI { static initialize() { // Set up periodic data fetching this.startMetricsUpdates(); this.startNotificationPolling(); this.setupWebSocketFallback(); } static startMetricsUpdates() { const updateMetrics = () => { const metrics = $api.get('/dashboard/metrics'); metrics.watch(data => { $$('dashboard.metrics').set(data); $$('dashboard.lastUpdate').set(Date.now()); // Schedule next update setTimeout(updateMetrics, 30000); // 30 seconds }); }; updateMetrics(); } static startNotificationPolling() { const pollNotifications = () => { const lastCheck = $$('notifications.lastCheck').get() || 0; const notifications = $api.get(`/notifications?since=${lastCheck}`); notifications.watch(newNotifications => { if (newNotifications.length > 0) { const current = $$('notifications.list').get() || []; $$('notifications.list').set([...current, ...newNotifications]); $$('notifications.unreadCount').set( current.length + newNotifications.length ); } $$('notifications.lastCheck').set(Date.now()); // Schedule next poll setTimeout(pollNotifications, 10000); // 10 seconds }); }; pollNotifications(); } static setupWebSocketFallback() { // If WebSocket connection fails, fall back to API polling $$('websocket.connected').watch(connected => { if (!connected) { console.log('WebSocket down, using API fallback'); this.startMetricsUpdates(); } }); } } ``` --- ## 🚨 **Best Practices & Patterns** ### ✅ **DO: Leverage FX's Synchronous Surface** ```javascript // ✅ Direct usage - feels synchronous const users = $api.get('/users'); const user = $api.get('/users/42'); // ✅ Immediate reactive binding users.watch(data => updateUserList(data)); $('#user-count').text(users.length); // Updates automatically // ✅ Chain API calls reactively const profile = $api.get('/users/42/profile'); profile.watch(data => { if (data.preferences) { const settings = $api.get('/users/42/settings'); settings.watch(settingsData => { $$('user.completeProfile').set({ ...data, ...settingsData }); }); } }); ``` ### ❌ **DON'T: Use Async/Await (Breaks FX Principles)** ```javascript // ❌ DON'T: Manual async handling async function loadUser(id) { try { const response = await fetch(`/api/users/${id}`); const user = await response.json(); updateUI(user); } catch (error) { handleError(error); } } // ✅ DO: Use FX API for immediate reactive results function loadUser(id) { const user = $api.get(`/users/${id}`); user.watch(userData => updateUI(userData)); // No try/catch needed - FX handles errors internally } ``` ### ✅ **DO: Use Interceptors for Cross-Cutting Concerns** ```javascript // ✅ Authentication interceptor const authAPI = $api.addRequestInterceptor((config) => { const token = $$('auth.token').get(); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // ✅ Logging interceptor const loggedAPI = authAPI.addResponseInterceptor((response, config) => { $$('api.logs').set({ endpoint: config.url, method: config.method, timestamp: Date.now(), status: response.status || 'success' }); return response; }); ``` --- ## 🎯 **Comparison: FX API vs Raw FX Core** ### **Raw FX Core (Built-in)** ```javascript // Direct FX core usage const users = $$('@/api/users').get({ headers: { 'Authorization': 'Bearer token' } }); const newUser = $$('@/api/users').post({ body: { name: 'John' }, headers: { 'Content-Type': 'application/json' } }); ``` ### **FX API Plugin (Convenience)** ```javascript // Same functionality, cleaner syntax const users = $api.get('/users'); // Headers from config const newUser = $api.post('/users', { name: 'John' }); // Body simplified ``` ### **Both Are Valid!** - **Use FX core directly** for simple cases or when you need full control - **Use FX API plugin** for complex applications with consistent API patterns - **Mix both approaches** - they're fully compatible! --- ## 🛠 **Integration with Other FX Plugins** ### **With fx-dom-dollar** ```javascript // API data binding to DOM const users = $api.get('/users'); // Bind API response to DOM elements $('#user-count').text(users.length); $('#loading').css('display', users.loading ? 'block' : 'none'); // Form submission using API $('#user-form').on('submit', (e) => { e.preventDefault(); const formData = { name: $('#name').val(), email: $('#email').val() }; const response = $api.post('/users', formData); response.watch(result => { if (result.success) { $('#message').text('User created successfully!'); $('#user-form')[0].reset(); } else { $('#error').text(result.message); } }); }); ``` ### **With fx-orm** ```javascript // Hybrid local/remote data management class UserService { static syncUserData(userId) { // Get user from API const apiUser = $api.get(`/users/${userId}`); apiUser.watch(userData => { // Update local database const localUser = $db.users.find(`#${userId}`); if (localUser) { localUser.name = userData.name; localUser.email = userData.email; localUser.lastSync = Date.now(); } else { $db.users.create(userData); } // Update FX state $$('app.currentUser').set(userData); }); } static saveUserChanges(userId, changes) { // Update API const apiResponse = $api.patch(`/users/${userId}`, changes); // Update local DB immediately (optimistic update) const localUser = $db.users.find(`#${userId}`); Object.assign(localUser, changes); apiResponse.watch(result => { if (!result.success) { // Rollback on API failure const originalData = $api.get(`/users/${userId}`); originalData.watch(original => { Object.assign(localUser, original); }); } }); } } ``` ### **With fx-flow** ```javascript // API calls in FX flows $flow('userRegistration') .node('validateEmail', { runsOn: 'client', effect: (ctx) => { const validation = $api.post('/validate/email', { email: ctx.in.email }); validation.watch(result => { if (result.valid) { ctx.next('createUser', ctx.in); } else { ctx.error('Email validation failed'); } }); } }) .node('createUser', { runsOn: 'server', effect: (ctx) => { const newUser = $api.post('/users', ctx.in); newUser.watch(user => { ctx.next('sendWelcome', { userId: user.id }); }); } }) .node('sendWelcome', { runsOn: 'both', effect: (ctx) => { const welcome = $api.post('/emails/welcome', ctx.in); welcome.watch(() => { $$('notifications.success').set('Registration complete!'); ctx.complete(); }); } }); ``` --- ## 🎊 **Complete Application Example** ```javascript // E-commerce API integration class ShopAPI { static initialize() { // Configure API with authentication this.api = $api .base('https://shop-api.example.com/v1') .addRequestInterceptor(this.addAuth) .addResponseInterceptor(this.handleErrors); } static addAuth(config) { const token = $$('auth.token').get(); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; } static handleErrors(response, config) { if (response.status >= 400) { $$('ui.errors').set({ message: response.message, endpoint: config.url, timestamp: Date.now() }); } return response; } // Product catalog static getProducts(category = 'all', page = 1) { const products = this.api.get(`/products?category=${category}&page=${page}`); products.watch(data => { $$('shop.products.list').set(data.products); $$('shop.products.total').set(data.total); $$('shop.products.currentPage').set(page); }); return products; } // Shopping cart static addToCart(productId, quantity = 1) { const response = this.api.post('/cart/items', { productId, quantity }); response.watch(result => { if (result.success) { // Update cart state const cartItems = $$('shop.cart.items').get() || []; cartItems.push(result.item); $$('shop.cart.items').set(cartItems); $$('shop.cart.count').set(cartItems.length); $$('shop.cart.total').set( cartItems.reduce((sum, item) => sum + (item.price * item.quantity), 0) ); // Show success message $$('ui.notifications').set({ type: 'success', message: 'Added to cart!', timestamp: Date.now() }); } }); return response; } // Checkout process static checkout(orderData) { const response = this.api.post('/orders', orderData); response.watch(result => { if (result.success) { // Clear cart $$('shop.cart.items').set([]); $$('shop.cart.count').set(0); $$('shop.cart.total').set(0); // Update order history const orders = $$('user.orders').get() || []; $$('user.orders').set([result.order, ...orders]); // Redirect to confirmation $$('router.currentPath').set(`/orders/${result.order.id}/confirmation`); } }); return response; } } // Initialize and use ShopAPI.initialize(); // Reactive product loading $$('ui.selectedCategory').watch(category => { ShopAPI.getProducts(category); }); // Reactive cart updates $$('shop.cart.count').watch(count => { $('#cart-badge').text(count); $('#cart-badge').css('display', count > 0 ? 'block' : 'none'); }); ``` --- This cheat sheet demonstrates how FX API provides convenience while maintaining FX's core principles of synchronous operation and reactive data management. The plugin seamlessly integrates with the FX ecosystem while providing familiar HTTP API patterns.