74 lines
4.4 KiB
Markdown
74 lines
4.4 KiB
Markdown
# core:data
|
|
|
|
Room database, DataStore preferences, EncryptedSharedPreferences for tokens, and all repository implementations. This is the coordination layer between network and local storage.
|
|
|
|
## What This Module Provides
|
|
|
|
- **Room database** (`db/LibreChatDatabase.kt`): 6 entities, 6 DAOs, version 1, `exportSchema = true`.
|
|
- **Entities**: `ConversationEntity`, `MessageEntity`, `FileEntity`, `AgentEntity`, `PresetEntity`, `ConversationTagEntity`. Complex fields (lists, nested objects) stored as JSON strings via `Converters`.
|
|
- **DAOs**: `ConversationDao`, `MessageDao`, `FileDao`, `AgentDao`, `PresetDao`, `ConversationTagDao`. Read methods return `Flow<T>` for reactive observation.
|
|
- **DataStore**: `ServerDataStore` (server URL prefs), `SettingsDataStore` (user preferences).
|
|
- **Token storage** (`datastore/TokenDataStore.kt`): Implements `TokenManager` from `:core:network` using `EncryptedSharedPreferences` with AES-256. Uses `Mutex` to ensure only one refresh runs at a time when multiple 401s arrive concurrently.
|
|
- **Repositories** (`repository/`): Interface + Impl for each domain: `AuthRepository`, `ConversationRepository`, `MessageRepository`, `ChatRepository`, `FileRepository`, `AgentRepository`, `PresetRepository`, `PromptRepository`, `TagRepository`, `ShareRepository`, `ConfigRepository`, `UserRepository`, `SettingsRepository`.
|
|
- **Entity mappers** (`db/mapper/EntityMapper.kt`): Convert between Room entities and domain models.
|
|
- **Sync manager** (`sync/ConversationSyncManager.kt`): Orchestrates cache invalidation.
|
|
|
|
## Key Patterns
|
|
|
|
### Repository: Interface + Impl
|
|
|
|
```kotlin
|
|
interface ConversationRepository {
|
|
fun observeConversations(...): Flow<Result<List<Conversation>>>
|
|
suspend fun updateTitle(id: String, title: String): Result<Conversation>
|
|
}
|
|
```
|
|
|
|
All impls take constructor parameters (api, dao, mapper, dispatcher) wired via Koin named qualifiers.
|
|
|
|
### Read-Through Cache
|
|
|
|
1. Emit cached data from Room immediately (first page).
|
|
2. Fetch fresh data from network via API service.
|
|
3. Upsert into Room. The Room `Flow` auto-emits the updated list.
|
|
4. On network error, the cached data remains visible; error is surfaced separately.
|
|
|
|
### Token Refresh with Mutex
|
|
|
|
```kotlin
|
|
private val refreshMutex = Mutex()
|
|
override suspend fun refreshAccessToken(): Boolean = refreshMutex.withLock {
|
|
// Single refresh attempt, all concurrent callers wait on this lock
|
|
}
|
|
```
|
|
|
|
Wrap `EncryptedSharedPreferences` in try/catch -- some OEM devices have broken Keystore implementations. On `KeyStoreException`, clear tokens and force re-login.
|
|
|
|
### DataModule Bindings
|
|
|
|
`DataModule.kt` defines a Koin `module { }` that binds repository interfaces to their implementations via `singleOf(::Impl) bind Interface::class` and provides the Room database, DAOs, and DataStore instances.
|
|
|
|
## Room TypeConverters
|
|
|
|
`Converters.kt` handles JSON serialization for complex Room fields: `List<String>`, `MessageContentPart` lists, `Feedback`, `FileReference` lists, etc. Uses the same `Json` instance from the Koin graph.
|
|
|
|
## Rules
|
|
|
|
- Dependencies: `:core:network`, `:core:model`, `:core:common`, Room, DataStore, security-crypto, Ktor, kotlinx-serialization, Timber, Koin.
|
|
- Convention plugins: `librechat.mobile.library` + `librechat.mobile.koin` + `librechat.mobile.room` + `librechat.kotlin.serialization`.
|
|
- Repositories must use `safeApiCall` from `:core:common` for all network calls.
|
|
- Entities are internal to this module -- feature modules work with domain models from `:core:model`.
|
|
- All DAO read methods that the UI observes should return `Flow`, not suspend functions.
|
|
|
|
### New Repositories (Round 2)
|
|
- `MemoryRepository` / `MemoryRepositoryImpl` — wraps `MemoriesApi` for memory CRUD + preferences
|
|
- `McpRepository` / `McpRepositoryImpl` — wraps `McpApi` for server CRUD, tools, connection status, reinitialize
|
|
- Both bound in `DataModule.kt` via `singleOf`
|
|
- Both use `safeApiCall` — no local caching (server is sole source of truth for memories and MCP)
|
|
- **Gotcha**: Memory delete/update use `key` as identifier (not a separate ID field)
|
|
- **Gotcha**: MCP server operations use `serverName` as identifier
|
|
|
|
### New Repositories (Round 3)
|
|
- `BannerRepository` / `BannerRepositoryImpl` — wraps `BannerApi` for fetching server banners
|
|
- `AgentRepository.getAgentsPaginated()` — server-side paginated agent fetch, maps response to `PaginatedAgents` domain model
|
|
- Both bound in `DataModule.kt` via `singleOf`, both use `safeApiCall`, no local caching
|