- 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).
623 lines
22 KiB
Markdown
623 lines
22 KiB
Markdown
# 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 |