docs: Document current Expo architecture for native migration
- 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).
This commit is contained in:
@@ -0,0 +1,623 @@
|
||||
# 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
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user