# Migration Guide: Sync to Async API

This guide helps you migrate from Milton's synchronous API to the new asynchronous API using the FCM Client SDK. The migration provides immediate responses, better user experience, and improved scalability.

## Overview

### Before: Synchronous API
- Requests block for 5-15 seconds
- Poor user experience during processing
- Limited scalability
- No background processing support

### After: Asynchronous API
- Immediate responses (< 200ms)
- Background processing with notifications
- Better user experience
- 10x improved scalability
- Offline support

## Migration Strategy

### Phase 1: Parallel Implementation
Run both sync and async APIs in parallel to ensure smooth transition.

### Phase 2: Gradual Migration
Migrate endpoints one by one, starting with less critical features.

### Phase 3: Full Migration
Complete migration to async API and deprecate sync endpoints.

## Code Migration Examples

### 1. User Message Submission

#### Before (Synchronous)

```javascript
import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, Alert } from 'react-native';

const SyncUserMessage = () => {
  const [question, setQuestion] = useState('');
  const [loading, setLoading] = useState(false);
  const [result, setResult] = useState(null);

  const handleSubmit = async () => {
    if (!question.trim()) return;

    setLoading(true);
    
    try {
      // Synchronous API call - blocks for 5-15 seconds
      const response = await fetch('https://api.milton.com/user', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${API_KEY}`,
        },
        body: JSON.stringify({
          orgId: 123,
          userId: 456,
          question: question.trim(),
        }),
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }

      const result = await response.json();
      setResult(result);
      
    } catch (error) {
      Alert.alert('Error', error.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <View>
      <TextInput
        value={question}
        onChangeText={setQuestion}
        placeholder="Ask Milton a question..."
        editable={!loading}
      />
      
      <TouchableOpacity onPress={handleSubmit} disabled={loading}>
        <Text>{loading ? 'Processing...' : 'Submit'}</Text>
      </TouchableOpacity>

      {loading && (
        <Text>Please wait 5-15 seconds...</Text>
      )}

      {result && (
        <Text>Result: {JSON.stringify(result)}</Text>
      )}
    </View>
  );
};
```

#### After (Asynchronous)

```javascript
import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, Alert } from 'react-native';
import { MiltonAsyncClient } from '@milton/fcm-client-sdk';

const client = new MiltonAsyncClient({
  baseUrl: 'https://api.milton.com',
  apiKey: API_KEY,
  enablePushNotifications: true,
});

const AsyncUserMessage = () => {
  const [question, setQuestion] = useState('');
  const [status, setStatus] = useState('');
  const [result, setResult] = useState(null);
  const [requestId, setRequestId] = useState(null);

  const handleSubmit = async () => {
    if (!question.trim()) return;

    setStatus('Submitting...');
    setResult(null);

    try {
      // Asynchronous API call - returns immediately
      const response = await client.submitUserMessage({
        orgId: 123,
        userId: 456,
        question: question.trim(),
      }, {
        onProgress: (status) => {
          setStatus(`Processing: ${status.status}`);
        },
        onComplete: (result) => {
          setStatus('Completed!');
          setResult(result);
        },
        onError: (error) => {
          setStatus(`Error: ${error.message}`);
          Alert.alert('Error', error.message);
        }
      });

      setRequestId(response.request_id);
      setStatus(`Submitted (ID: ${response.request_id})`);
      
    } catch (error) {
      setStatus('Submission failed');
      Alert.alert('Error', error.message);
    }
  };

  return (
    <View>
      <TextInput
        value={question}
        onChangeText={setQuestion}
        placeholder="Ask Milton a question..."
      />
      
      <TouchableOpacity onPress={handleSubmit}>
        <Text>Submit</Text>
      </TouchableOpacity>

      {status && (
        <Text>Status: {status}</Text>
      )}

      {requestId && (
        <Text>Request ID: {requestId}</Text>
      )}

      {result && (
        <Text>Result: {JSON.stringify(result)}</Text>
      )}
    </View>
  );
};
```

### 2. Survey Submission

#### Before (Synchronous)

```javascript
const submitSurvey = async (surveyData) => {
  setLoading(true);
  
  try {
    const response = await fetch('https://api.milton.com/survey', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${API_KEY}`,
      },
      body: JSON.stringify({
        orgId: 123,
        userId: 456,
        surveyData,
      }),
    });

    const result = await response.json();
    
    // Handle result immediately
    handleSurveyResult(result);
    
  } catch (error) {
    handleError(error);
  } finally {
    setLoading(false);
  }
};
```

#### After (Asynchronous)

```javascript
const submitSurvey = async (surveyData) => {
  try {
    const response = await client.submitSurvey({
      orgId: 123,
      userId: 456,
      surveyData,
    }, {
      onProgress: (status) => {
        updateProgressIndicator(status.status);
      },
      onComplete: (result) => {
        // Handle result when ready
        handleSurveyResult(result);
      },
      onError: (error) => {
        handleError(error);
      }
    });

    // Immediate response with request ID
    showSubmissionConfirmation(response.request_id);
    
  } catch (error) {
    handleError(error);
  }
};
```

### 3. Fitness Calculation

#### Before (Synchronous)

```javascript
const calculateFitness = async (fitnessData) => {
  showLoadingSpinner(true);
  
  try {
    const response = await fetch('https://api.milton.com/calculate_fitness', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${API_KEY}`,
      },
      body: JSON.stringify({
        orgId: 123,
        userId: 456,
        fitnessData,
      }),
    });

    const result = await response.json();
    
    // Update UI with fitness results
    updateFitnessDisplay(result);
    
  } catch (error) {
    showError(error.message);
  } finally {
    showLoadingSpinner(false);
  }
};
```

#### After (Asynchronous)

```javascript
const calculateFitness = async (fitnessData) => {
  try {
    const response = await client.calculateFitness({
      orgId: 123,
      userId: 456,
      fitnessData,
    }, {
      onProgress: (status) => {
        showProgressStatus(`Calculating: ${status.status}`);
      },
      onComplete: (result) => {
        // Update UI when calculation is complete
        updateFitnessDisplay(result);
        showNotification('Fitness calculation complete!');
      },
      onError: (error) => {
        showError(error.message);
      }
    });

    // Show immediate confirmation
    showSubmissionSuccess(response.request_id);
    
  } catch (error) {
    showError(error.message);
  }
};
```

## Advanced Migration Patterns

### 1. Request Management Hook

Create a custom hook to manage async requests:

```javascript
import { useState, useCallback } from 'react';
import { MiltonAsyncClient } from '@milton/fcm-client-sdk';

export const useMiltonRequest = (client) => {
  const [activeRequests, setActiveRequests] = useState(new Map());

  const submitRequest = useCallback(async (type, data, options = {}) => {
    const requestOptions = {
      ...options,
      onProgress: (status) => {
        setActiveRequests(prev => new Map(prev.set(status.request_id, {
          type,
          status: status.status,
          data
        })));
        options.onProgress?.(status);
      },
      onComplete: (result) => {
        setActiveRequests(prev => {
          const newMap = new Map(prev);
          newMap.delete(result.request_id);
          return newMap;
        });
        options.onComplete?.(result);
      },
      onError: (error) => {
        setActiveRequests(prev => {
          const newMap = new Map(prev);
          // Keep failed requests for retry
          return newMap;
        });
        options.onError?.(error);
      }
    };

    let response;
    switch (type) {
      case 'user_message':
        response = await client.submitUserMessage(data, requestOptions);
        break;
      case 'survey':
        response = await client.submitSurvey(data, requestOptions);
        break;
      case 'fitness':
        response = await client.calculateFitness(data, requestOptions);
        break;
      default:
        throw new Error(`Unknown request type: ${type}`);
    }

    setActiveRequests(prev => new Map(prev.set(response.request_id, {
      type,
      status: 'submitted',
      data
    })));

    return response;
  }, [client]);

  const cancelRequest = useCallback(async (requestId) => {
    try {
      await client.cancelRequest(requestId);
      setActiveRequests(prev => {
        const newMap = new Map(prev);
        newMap.delete(requestId);
        return newMap;
      });
    } catch (error) {
      console.error('Failed to cancel request:', error);
    }
  }, [client]);

  return {
    submitRequest,
    cancelRequest,
    activeRequests: Array.from(activeRequests.entries())
  };
};
```

### 2. Batch Request Migration

#### Before (Multiple Sync Calls)

```javascript
const processMultipleRequests = async (requests) => {
  setLoading(true);
  const results = [];

  try {
    // Process sequentially to avoid overwhelming server
    for (const request of requests) {
      const response = await fetch(`https://api.milton.com/${request.endpoint}`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${API_KEY}`,
        },
        body: JSON.stringify(request.data),
      });
      
      const result = await response.json();
      results.push(result);
    }

    handleAllResults(results);
    
  } catch (error) {
    handleError(error);
  } finally {
    setLoading(false);
  }
};
```

#### After (Concurrent Async Calls)

```javascript
const processMultipleRequests = async (requests) => {
  const requestPromises = requests.map(async (request) => {
    return new Promise((resolve, reject) => {
      const submitMethod = getSubmitMethod(request.type);
      
      submitMethod(request.data, {
        onComplete: (result) => {
          resolve({ requestId: request.id, result });
        },
        onError: (error) => {
          reject({ requestId: request.id, error });
        }
      });
    });
  });

  try {
    // Process all requests concurrently
    const results = await Promise.allSettled(requestPromises);
    
    const successful = results
      .filter(r => r.status === 'fulfilled')
      .map(r => r.value);
      
    const failed = results
      .filter(r => r.status === 'rejected')
      .map(r => r.reason);

    handleBatchResults(successful, failed);
    
  } catch (error) {
    handleError(error);
  }
};

const getSubmitMethod = (type) => {
  switch (type) {
    case 'user_message': return client.submitUserMessage.bind(client);
    case 'survey': return client.submitSurvey.bind(client);
    case 'fitness': return client.calculateFitness.bind(client);
    default: throw new Error(`Unknown type: ${type}`);
  }
};
```

### 3. Error Handling Migration

#### Before (Simple Error Handling)

```javascript
const handleRequest = async () => {
  try {
    const response = await fetch(url, options);
    const result = await response.json();
    return result;
  } catch (error) {
    Alert.alert('Error', 'Something went wrong');
    throw error;
  }
};
```

#### After (Comprehensive Error Handling)

```javascript
const handleRequest = async () => {
  try {
    const response = await client.submitUserMessage(data, {
      onError: (error) => {
        if (error.message.includes('HTTP 503')) {
          // Server overloaded - show retry option
          showRetryDialog('Server is busy. Try again?', () => {
            setTimeout(() => handleRequest(), 5000);
          });
        } else if (error.message.includes('offline')) {
          // Offline - request will be queued
          showOfflineMessage('Request queued for when you\'re back online');
        } else {
          // Other errors
          Alert.alert('Error', error.message);
        }
      }
    });
    
    return response;
  } catch (error) {
    // Handle submission errors
    if (error.message.includes('validation')) {
      showValidationErrors(error.details);
    } else {
      Alert.alert('Error', 'Failed to submit request');
    }
    throw error;
  }
};
```

## UI/UX Migration Patterns

### 1. Loading States

#### Before (Blocking Loading)

```javascript
const LoadingComponent = ({ loading }) => {
  if (loading) {
    return (
      <View style={styles.overlay}>
        <ActivityIndicator size="large" />
        <Text>Processing... Please wait 5-15 seconds</Text>
      </View>
    );
  }
  return null;
};
```

#### After (Progressive Loading)

```javascript
const ProgressComponent = ({ status, requestId }) => {
  const getStatusMessage = (status) => {
    switch (status) {
      case 'submitted': return 'Request submitted successfully';
      case 'queued': return 'Request queued for processing';
      case 'processing': return 'Processing your request...';
      case 'waiting_for_push': return 'Waiting for notification...';
      default: return 'Processing...';
    }
  };

  return (
    <View style={styles.progressContainer}>
      <ProgressBar progress={getProgress(status)} />
      <Text>{getStatusMessage(status)}</Text>
      {requestId && (
        <Text style={styles.requestId}>ID: {requestId}</Text>
      )}
    </View>
  );
};
```

### 2. Background Processing Indicator

```javascript
const BackgroundProcessingIndicator = ({ activeRequests }) => {
  if (activeRequests.length === 0) return null;

  return (
    <View style={styles.backgroundIndicator}>
      <Icon name="clock" size={16} />
      <Text>{activeRequests.length} request(s) processing</Text>
      <TouchableOpacity onPress={() => showRequestDetails(activeRequests)}>
        <Text style={styles.viewDetails}>View</Text>
      </TouchableOpacity>
    </View>
  );
};
```

## Testing Migration

### 1. A/B Testing Setup

```javascript
const useAsyncAPI = () => {
  // Feature flag or user percentage
  return Math.random() < 0.5; // 50% of users
};

const SubmitComponent = () => {
  const shouldUseAsync = useAsyncAPI();

  if (shouldUseAsync) {
    return <AsyncSubmitComponent />;
  } else {
    return <SyncSubmitComponent />;
  }
};
```

### 2. Performance Comparison

```javascript
const performanceTracker = {
  startTime: null,
  
  startTracking() {
    this.startTime = Date.now();
  },
  
  endTracking(type, success) {
    const duration = Date.now() - this.startTime;
    
    // Log metrics
    analytics.track('api_performance', {
      type, // 'sync' or 'async'
      duration,
      success,
      timestamp: new Date().toISOString()
    });
  }
};

// Usage in sync version
performanceTracker.startTracking();
try {
  const result = await syncAPICall();
  performanceTracker.endTracking('sync', true);
} catch (error) {
  performanceTracker.endTracking('sync', false);
}

// Usage in async version
performanceTracker.startTracking();
try {
  const response = await asyncAPICall();
  performanceTracker.endTracking('async', true);
} catch (error) {
  performanceTracker.endTracking('async', false);
}
```

## Migration Checklist

### Pre-Migration
- [ ] Install and configure FCM Client SDK
- [ ] Set up Firebase Cloud Messaging
- [ ] Test push notifications on target devices
- [ ] Create async API endpoints on backend
- [ ] Set up monitoring and analytics

### During Migration
- [ ] Implement async versions alongside sync versions
- [ ] Add feature flags for gradual rollout
- [ ] Monitor performance metrics
- [ ] Collect user feedback
- [ ] Handle edge cases and errors

### Post-Migration
- [ ] Verify all functionality works correctly
- [ ] Monitor error rates and performance
- [ ] Deprecate sync endpoints (with proper notice)
- [ ] Update documentation
- [ ] Train support team on new flow

## Common Migration Pitfalls

### 1. Not Handling Push Notification Permissions

```javascript
// Wrong - assuming permissions are granted
const client = new MiltonAsyncClient({
  enablePushNotifications: true
});

// Right - check and request permissions
const setupClient = async () => {
  const hasPermission = await checkNotificationPermission();
  
  const client = new MiltonAsyncClient({
    enablePushNotifications: hasPermission
  });
  
  if (!hasPermission) {
    // Fallback to polling only
    showPermissionPrompt();
  }
  
  return client;
};
```

### 2. Not Handling Offline Scenarios

```javascript
// Wrong - not considering offline state
await client.submitUserMessage(data);

// Right - handle offline gracefully
try {
  const response = await client.submitUserMessage(data, {
    onProgress: (status) => {
      if (status.status === 'queued_offline') {
        showOfflineMessage('Request will be processed when online');
      }
    }
  });
} catch (error) {
  if (error.message.includes('offline')) {
    showOfflineError();
  }
}
```

### 3. Not Cleaning Up Resources

```javascript
// Wrong - memory leaks
const client = new MiltonAsyncClient(config);

// Right - proper cleanup
useEffect(() => {
  const client = new MiltonAsyncClient(config);
  
  return () => {
    client.dispose(); // Clean up resources
  };
}, []);
```

## Support and Resources

### Documentation
- [SDK API Reference](README.md)
- [Firebase Setup Guide](FIREBASE_SETUP.md)
- [Troubleshooting Guide](TROUBLESHOOTING.md)
- [TypeScript Definitions](TYPESCRIPT_DEFINITIONS.md)

### Migration Support
- Email: sanjay@joinmmnt.com
- Office Hours: Tuesdays 2-3 PM PST

### Example Projects
- [Complete Migration Example](https://github.com/milton-health/migration-example)
- [React Native Demo App](https://github.com/milton-health/rn-demo)

The migration to async API provides significant benefits in user experience, performance, and scalability. Take time to plan the migration carefully and test thoroughly before full deployment.