- Document technology stack (Expo, TypeScript, Zustand, SQLite) - Map out all data models and types - Document service dependencies and architecture - Create component dependency diagrams - Document data flow for feed sync and search - Provide migration checklist with priorities - Identify pure TypeScript vs native-dependent services This analysis provides the foundation for migrating business logic from Expo/TypeScript to native platforms (iOS, Android, Linux).
22 KiB
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<string, ParseResult>
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<void>
├── updateSubscription(id, updates) → void
├── removeSubscription(id) → Promise<void>
├── loadSubscriptions() → Promise<void>
├── loadFeedItems(subscriptionId?, limit?, offset?) → Promise<void>
├── 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<string>
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<void>
├── loadHistory() → Promise<void>
├── clearHistory() → Promise<void>
├── removeFromHistory(query) → Promise<void>
└── 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
-
Pure TypeScript Services (can be shared across all platforms):
- Feed parsing (RSS/Atom)
- Search logic
- Sync orchestration
- Conflict resolution
-
Native Module Dependencies:
expo-sqlite→ SQLite databaseexpo-task-manager→ Background tasksexpo-notifications→ Push/local notificationsexpo-av→ Audio playback
-
State Management:
- All stores use Zustand
- Settings and bookmarks persist to AsyncStorage
- Database serves as source of truth for feeds/subscriptions
-
Error Handling:
- Most services return
{ success: boolean; error?: string } - Try-catch blocks with meaningful error messages
- Fallback strategies (e.g., FTS → LIKE search)
- Most services return
-
Platform-Specific Code:
hooks/use-color-scheme.web.ts- Web-specific color schemecomponents/animated-icon.web.tsx- Web-specific icon animationcomponents/app-tabs.web.tsx- Web-specific tabs
Migration Priorities
- Phase 1: Data models and types (shared)
- Phase 2: Pure TypeScript services (feed, search, sync)
- Phase 3: Database layer with expo-sqlite
- Phase 4: Background sync with expo-task-manager
- Phase 5: Notifications with expo-notifications
- Phase 6: Audio player with expo-av
- Phase 7: State management integration