# 01. Analyze and document current Expo architecture meta: id: native-business-logic-migration-01 feature: native-business-logic-migration priority: P0 depends_on: [] tags: [analysis, documentation] objective: - Document the current Expo/TypeScript architecture to understand what needs to be migrated deliverables: - Architecture documentation file - Component dependency diagram - Data flow diagrams - List of all services, stores, and utilities to migrate steps: - Review all TypeScript source files in src/ - Identify all data models and types - Map out service dependencies - Document state management flow (Zustand stores) - Identify database schema and queries - Document API endpoints and network calls - Create migration checklist acceptance_criteria: - Architecture document exists with all components listed - Data flow diagrams created for key features - Migration checklist complete with all items identified - All services and their dependencies documented notes: - Focus on business logic, not UI components - Pay attention to async operations and error handling - Document any platform-specific code that needs special handling --- # Current Architecture Analysis ## Overview RSSuper is a multi-platform RSS reader built with **Expo (React Native)** using **TypeScript**. The current architecture follows a service-oriented pattern with Zustand for state management and expo-sqlite for local persistence. ## Technology Stack | Layer | Technology | |-------|------------| | Framework | Expo SDK 55 (canary) | | Runtime | React Native 0.83.4 | | Language | TypeScript 5.9 | | State Management | Zustand 5.x | | Data Fetching | TanStack Query 5.x | | HTTP Client | Axios | | Local Database | expo-sqlite | | Navigation | React Navigation 7.x | | XML Parsing | fast-xml-parser | | Audio | expo-av | --- ## Data Models ### Core Types (`src/types/feed.ts`) ``` FeedItem ├── id: string ├── title: string ├── link?: string ├── description?: string ├── content?: string ├── author?: string ├── published?: Date ├── updated?: Date ├── categories?: string[] ├── enclosure?: { url, type, length? } └── guid?: string Feed ├── id: string ├── title: string ├── link?: string ├── description?: string ├── subtitle?: string ├── language?: string ├── lastBuildDate?: Date ├── updated?: Date ├── generator?: string ├── ttl?: number ├── items: FeedItem[] ├── rawUrl: string ├── lastFetchedAt?: Date └── nextFetchAt?: Date FeedSubscription ├── id: string ├── url: string ├── title: string ├── category?: string ├── enabled: boolean ├── fetchInterval: number (minutes) ├── createdAt: Date ├── updatedAt: Date ├── lastFetchedAt?: Date ├── nextFetchAt?: Date ├── error?: string └── httpAuth?: { username, password } SearchQuery / SearchFilters / SearchResult ``` ### Global Types (`src/types/global.d.ts`) ``` NotificationType (enum) ├── NEW_ARTICLE ├── EPISODE_RELEASE ├── CUSTOM_ALERT └── UPGRADE_PROMO NotificationConfig AccountSettings PushNotificationConfig ``` --- ## Services Architecture ### 1. Feed Service (`src/services/feed-service.ts`) **Responsibilities**: RSS/Atom feed parsing and fetching ``` Functions: ├── parseFeed(url, data) → ParseResult ├── fetchFeed(url, auth?) → ParseResult └── fetchFeeds(subscriptions) → Map Dependencies: ├── axios (HTTP) └── xml2js (XML parsing) Platform: Pure TypeScript - NO native dependencies ``` ### 2. Database Service (`src/services/database.ts`) **Responsibilities**: SQLite persistence layer ``` Tables: ├── subscriptions │ ├── id (PRIMARY KEY) │ ├── url (UNIQUE) │ ├── title, category │ ├── enabled, fetch_interval │ ├── http_auth_username, http_auth_password │ ├── timestamps (created_at, updated_at, last_fetched_at, next_fetch_at) │ └── error │ ├── feeds │ ├── id (PRIMARY KEY) │ ├── subscription_id (FOREIGN KEY) │ ├── title, link, description, author │ ├── published, updated, content │ ├── guid (UNIQUE) │ └── created_at │ ├── feeds_fts (FTS5 virtual table) ├── subscriptions_fts (FTS5 virtual table) └── search_history Functions: ├── initDatabase() → void ├── saveSubscription(sub) → void ├── getSubscription(id) → FeedSubscription ├── getAllSubscriptions() → FeedSubscription[] ├── deleteSubscription(id) → void ├── saveFeedItems(subscriptionId, items) → void ├── getFeedItems(subscriptionId?, limit, offset) → FeedItem[] ├── getAllFeedItems(limit, offset) → FeedItem[] └── search history functions Dependencies: expo-sqlite ``` ### 3. Search Service (`src/services/search-service.ts`) **Responsibilities**: Full-text search using SQLite FTS5 ``` Functions: ├── searchArticles(query, options) → SearchResult[] ├── searchFeeds(query, limit) → SearchResult[] └── combinedSearch(query, options) → { articles, feeds } Search Features: ├── FTS5 full-text search (primary) ├── LIKE fallback search ├── Date filters ├── Feed filters ├── Author filters ├── Content type filters └── Multiple sort options Dependencies: database service ``` ### 4. Sync Service (`src/services/sync-service.ts`) **Responsibilities**: Feed synchronization orchestration ``` Functions: ├── syncFeed(subscription) → { success, itemsSynced } ├── syncAllFeeds() → { success, totalItemsSynced } ├── getFeedsDueForSync() → FeedSubscription[] ├── getLocalFeedItems(...) → FeedItem[] ├── getAllLocalFeedItems(limit) → FeedItem[] ├── hasLocalData() → boolean └── resolveConflict(options) → FeedItem Conflict Resolution Strategies: ├── newer ├── older ├── local └── remote Dependencies: feed-service, database, feed-store ``` ### 5. Background Sync Service (`src/services/background-sync.ts`) **Responsibilities**: Background task management ``` Task: BACKGROUND_SYNC_TASK ├── Uses expo-task-manager ├── Registers background fetch task └── Syncs all enabled feeds Functions: ├── registerBackgroundSync() → boolean ├── unregisterBackgroundSync() → void └── isBackgroundSyncRegistered() → boolean Dependencies: expo-task-manager, sync-service, feed-store ``` ### 6. Notification Service (`src/services/notification-service.ts`) **Responsibilities**: Push and local notifications ``` Functions: ├── registerNotificationChannels() → void (Android) ├── requestNotificationPermissions() → boolean ├── getPermissionStatus() → status ├── scheduleNotification(config) → string | null ├── scheduleNotificationAtTime(config, date) → string | null ├── cancelNotification(id) → void ├── cancelAllNotifications() → void ├── getScheduledNotifications() → NotificationRequest[] ├── showNotification(config) → void ├── setBadgeCount(count) → void ├── getBadgeCount() → number ├── addNotificationListener(callback) → unsubscribe └── addNotificationResponseListener(callback) → unsubscribe Android Channels: ├── default ├── new-articles ├── episode-releases └── custom-alerts Dependencies: expo-notifications, settings-store ``` ### 7. Audio Player Service (`src/services/audio-player.ts`) **Responsibilities**: Podcast/audio playback ``` Class: AudioPlayerService ├── initialize() → void ├── subscribe(callback) → unsubscribe ├── play(item) → void ├── pause() → void ├── resume() → void ├── stop() → void ├── seekTo(positionMs) → void ├── setPlaybackSpeed(speed) → void ├── skipForward(seconds) → void ├── skipBackward(seconds) → void └── getState() → PlayerState Playback States: idle | loading | playing | paused | error Playback Speeds: 0.5 | 0.75 | 1 | 1.25 | 1.5 | 1.75 | 2 Dependencies: expo-av ``` ### 8. API Service (`src/services/api.ts`) **Responsibilities**: HTTP client setup (currently minimal) ``` Setup: ├── Axios instance with baseURL ├── Request interceptor (auth token placeholder) └── Response interceptor (error handling) Note: Not actively used - TODO for future cloud sync ``` --- ## State Management (Zustand Stores) ### 1. Feed Store (`src/stores/feed-store.ts`) ``` State: ├── subscriptions: FeedSubscription[] ├── feedItems: FeedItem[] ├── loading: boolean └── error: string | null Actions: ├── addSubscription(subscription) → Promise ├── updateSubscription(id, updates) → void ├── removeSubscription(id) → Promise ├── loadSubscriptions() → Promise ├── loadFeedItems(subscriptionId?, limit?, offset?) → Promise ├── setLoading(loading) → void ├── setError(error) → void └── clearError() → void ``` ### 2. Settings Store (`src/stores/settings-store.ts`) ``` State: ├── syncInterval: SyncInterval ├── theme: 'system' | 'light' | 'dark' ├── notificationPreferences: NotificationPreferences ├── readingPreferences: ReadingPreferences └── accountSettings: AccountSettings Actions: ├── setSyncInterval(interval) → void ├── setTheme(theme) → void ├── setNotificationPreferences(prefs) → void ├── setReadingPreferences(prefs) → void ├── setAccountSettings(settings) → void └── resetSettings() → void Persistence: AsyncStorage (via zustand persist) Storage Key: rssuper-settings ``` ### 3. Bookmark Store (`src/stores/bookmark-store.ts`) ``` State: └── bookmarkedIds: Set Actions: ├── addBookmark(articleId) → void ├── removeBookmark(articleId) → void ├── toggleBookmark(articleId) → void ├── isBookmarked(articleId) → boolean └── getBookmarks() → string[] Persistence: AsyncStorage (via zustand persist) Storage Key: rssuper-bookmarks ``` ### 4. Search Store (`src/stores/search-store.ts`) ``` State: ├── query: string ├── filters: SearchFilters ├── sort: SearchSortOption ├── page: number ├── pageSize: number ├── loading: boolean ├── error: string | null └── searchHistory: SearchHistoryItem[] Actions: ├── setQuery(query) → void ├── setFilters(filters) → void ├── setSort(sort) → void ├── setPage(page) → void ├── setLoading(loading) → void ├── setError(error) → void ├── clearSearch() → void ├── addToHistory(query) → Promise ├── loadHistory() → Promise ├── clearHistory() → Promise ├── removeFromHistory(query) → Promise └── resetSearch() → void ``` --- ## Component Dependencies ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ UI Components │ │ (feed-item-card, article-card, search-filters, etc.) │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ React Hooks │ │ use-feed-list, use-theme, use-color-scheme, use-offline │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ Zustand Stores │ │ feed-store, settings-store, bookmark-store, search-store │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ │ ▼ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ Services Layer │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │ │ │feed-service │ │search-service│ │ sync-service │ │ api.ts │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ └───────────┘ │ │ ┌──────────────────────┐ ┌──────────────────────┐ │ │ │background-sync.ts │ │notification-service │ │ │ └──────────────────────┘ └──────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ ▼ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ Data Layer │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ database.ts (expo-sqlite) │ │ │ │ - subscriptions table │ │ │ │ - feeds table │ │ │ │ - FTS5 virtual tables │ │ │ │ - search_history table │ │ │ └──────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ Expo Modules (Native) │ │ expo-sqlite, expo-task-manager, expo-notifications, expo-av │ └─────────────────────────────────────────────────────────────────────────┘ ``` --- ## Data Flow Diagrams ### Feed Sync Flow ``` User adds subscription │ ▼ ┌───────────────────┐ │ feed-store │ │ addSubscription() │ └─────────┬─────────┘ │ ▼ ┌───────────────────┐ ┌───────────────────┐ │ database.ts │ │ settings-store │ │ saveSubscription()│ │ (check intervals) │ └─────────┬─────────┘ └───────────────────┘ │ ▼ (manual or background) ┌───────────────────┐ │ sync-service │ │ syncFeed() │ └─────────┬─────────┘ │ ▼ ┌───────────────────┐ ┌───────────────────┐ │ feed-service │ │ feed-store │ │ fetchFeed() │ │ updateSubscription│ │ parseFeed() │ └─────────┬─────────┘ └─────────┬─────────┘ │ │ ▼ │ ┌───────────────────┐ │ │ database.ts │ │ │ saveFeedItems() │ │ └─────────┬─────────┘ │ │ ▼ ▼ ┌───────────────────┐ ┌───────────────────┐ │ axios.get() │ │ UI updates │ │ (HTTP request) │ │ via Zustand │ └───────────────────┘ └───────────────────┘ ``` ### Search Flow ``` User enters search query │ ▼ ┌───────────────────┐ │ search-store │ │ setQuery() │ └─────────┬─────────┘ │ ▼ ┌───────────────────┐ │ search-service │ │ searchArticles() │ └─────────┬─────────┘ │ ▼ ┌───────────────────┐ ┌───────────────────┐ │ database.ts │ │ FTS5 or LIKE │ │ getDb() │────▶│ fallback search │ └─────────┬─────────┘ └───────────────────┘ │ ▼ ┌───────────────────┐ │ SearchResult[] │ └─────────┬─────────┘ │ ▼ ┌───────────────────┐ │ UI renders │ │ search-results │ └───────────────────┘ ``` --- ## Migration Checklist ### Services to Migrate (Pure TypeScript) | Service | File | Native Deps | Priority | |---------|------|-------------|----------| | Feed Service | `feed-service.ts` | None | P0 | | Search Service | `search-service.ts` | None | P0 | | Sync Service | `sync-service.ts` | None | P0 | | API Service | `api.ts` | None | P1 | ### Services with Native Dependencies | Service | File | Native Deps | Priority | |---------|------|-------------|----------| | Database | `database.ts` | expo-sqlite | P0 | | Background Sync | `background-sync.ts` | expo-task-manager | P0 | | Notifications | `notification-service.ts` | expo-notifications | P0 | | Audio Player | `audio-player.ts` | expo-av | P1 | ### Stores to Migrate | Store | File | Persistence | Priority | |-------|------|-------------|----------| | Feed Store | `feed-store.ts` | None | P0 | | Settings Store | `settings-store.ts` | AsyncStorage | P0 | | Bookmark Store | `bookmark-store.ts` | AsyncStorage | P0 | | Search Store | `search-store.ts` | None | P0 | ### Types to Migrate | Types | File | Priority | |-------|------|----------| | Feed Types | `types/feed.ts` | P0 | | Global Types | `types/global.d.ts` | P0 | --- ## Key Findings 1. **Pure TypeScript Services** (can be shared across all platforms): - Feed parsing (RSS/Atom) - Search logic - Sync orchestration - Conflict resolution 2. **Native Module Dependencies**: - `expo-sqlite` → SQLite database - `expo-task-manager` → Background tasks - `expo-notifications` → Push/local notifications - `expo-av` → Audio playback 3. **State Management**: - All stores use Zustand - Settings and bookmarks persist to AsyncStorage - Database serves as source of truth for feeds/subscriptions 4. **Error Handling**: - Most services return `{ success: boolean; error?: string }` - Try-catch blocks with meaningful error messages - Fallback strategies (e.g., FTS → LIKE search) 5. **Platform-Specific Code**: - `hooks/use-color-scheme.web.ts` - Web-specific color scheme - `components/animated-icon.web.tsx` - Web-specific icon animation - `components/app-tabs.web.tsx` - Web-specific tabs --- ## Migration Priorities 1. **Phase 1**: Data models and types (shared) 2. **Phase 2**: Pure TypeScript services (feed, search, sync) 3. **Phase 3**: Database layer with expo-sqlite 4. **Phase 4**: Background sync with expo-task-manager 5. **Phase 5**: Notifications with expo-notifications 6. **Phase 6**: Audio player with expo-av 7. **Phase 7**: State management integration