docs: add DocumentSW documentation for Drift database layer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
# Drift Database Layer
|
||||
|
||||
**Branch:** `NDM-App-190-drift-database-tables-daos-repositories`
|
||||
**Commits:** `bb0dcdf`, `8256e57`
|
||||
|
||||
## Overview
|
||||
|
||||
Implements the local SQLite persistence layer using Drift 2.x. The database is the offline-first source of truth. All data access goes through typed repository classes that map between Drift-generated data classes and pure domain entities.
|
||||
|
||||
---
|
||||
|
||||
## Files Created
|
||||
|
||||
### `lib/data/models/tables.dart`
|
||||
Four Drift table definitions:
|
||||
|
||||
| Table | Description |
|
||||
|---|---|
|
||||
| `Users` | Device identity, pet selection, pet name |
|
||||
| `Tasks` | Core task items with sync tracking columns |
|
||||
| `PetStates` | Energy, streak, total completions, unlocked items |
|
||||
| `TaskCompletions` | Append-only completion log for rewards/analytics |
|
||||
|
||||
Key design details:
|
||||
- `Tasks` has `@TableIndex` on `userId` (also on `TaskCompletions`).
|
||||
- `localId` on `Tasks` is a nullable unique column for external sync identity.
|
||||
- `PetStates.userId` is unique — one pet state row per user.
|
||||
- Enums (`petType`, `status`) are stored as `text` strings; conversion is done in the repository layer.
|
||||
|
||||
### `lib/data/db/app_database.dart`
|
||||
`@DriftDatabase` class wiring all four tables. Uses `driftDatabase(name: 'kittie_mobile_db')` from `drift_flutter` (the correct API for `drift_flutter ^0.2.0`). `schemaVersion = 1`.
|
||||
|
||||
Generated part file: `lib/data/db/app_database.g.dart`.
|
||||
|
||||
### `lib/data/repositories/task_repository.dart`
|
||||
```dart
|
||||
TaskRepository(AppDatabase db)
|
||||
```
|
||||
|
||||
| Method | Description |
|
||||
|---|---|
|
||||
| `getAllTasks(userId)` | All tasks ordered by `sortOrder` |
|
||||
| `getTodayTasks(userId)` | Pending tasks only, ordered by `sortOrder` |
|
||||
| `getTaskById(id)` | Single task or null |
|
||||
| `watchTasks(userId)` | Reactive stream of all tasks |
|
||||
| `insertTask(entity)` | Insert from domain entity |
|
||||
| `updateTask(entity)` | Full-row update by id |
|
||||
| `deleteTask(id)` | Hard delete |
|
||||
| `completeTask(taskId)` | Transaction: sets `status=done`, inserts `TaskCompletion`; throws `ArgumentError` if task not found |
|
||||
|
||||
### `lib/data/repositories/pet_repository.dart`
|
||||
```dart
|
||||
PetRepository(AppDatabase db)
|
||||
```
|
||||
|
||||
| Method | Description |
|
||||
|---|---|
|
||||
| `getPetState(userId)` | Single pet state or null |
|
||||
| `watchPetState(userId)` | Reactive stream (nullable) |
|
||||
| `insertPetState(entity)` | First-time insert |
|
||||
| `updatePetState(entity)` | Full-row update |
|
||||
| `setEnergyToday(userId, energy)` | Partial update: energyToday + energyDate + updatedAt |
|
||||
| `incrementStreak(userId)` | Transaction: read current streak, write +1 |
|
||||
|
||||
### `lib/data/repositories/user_repository.dart`
|
||||
```dart
|
||||
UserRepository(AppDatabase db)
|
||||
```
|
||||
|
||||
| Method | Description |
|
||||
|---|---|
|
||||
| `getCurrentUser()` | First row (limit 1) or null; single-user device model |
|
||||
| `createUser(id, deviceId, name, petType, petName)` | Named params for clarity |
|
||||
| `updateUser(entity)` | Full-row update |
|
||||
|
||||
---
|
||||
|
||||
## Domain Entities (`lib/domain/entities/`)
|
||||
|
||||
Pure Dart classes — no Drift imports. Used across layers.
|
||||
|
||||
| File | Contents |
|
||||
|---|---|
|
||||
| `task_entity.dart` | `TaskEntity` with `copyWith` (sentinel `_unset` pattern for nullable fields) |
|
||||
| `task_status.dart` | `TaskStatus` enum: `pending`, `done`, `snoozed`, `someday`, `archived` |
|
||||
| `pet_state_entity.dart` | `PetStateEntity` |
|
||||
| `pet_type.dart` | `PetType` enum: `dog`, `cat`, `capybara` |
|
||||
| `user_entity.dart` | `UserEntity` |
|
||||
| `task_completion_entity.dart` | `TaskCompletionEntity` |
|
||||
|
||||
Each enum has a `fromString(String)` factory that parses DB text values.
|
||||
|
||||
---
|
||||
|
||||
## Patterns
|
||||
|
||||
### Entity ↔ Drift mapping
|
||||
Every repository has private `_toEntity(Row)` and `_toCompanion(Entity)` methods. No Drift types leak outside the repository.
|
||||
|
||||
### Transactions
|
||||
`completeTask` and `incrementStreak` wrap multi-step writes in `_db.transaction(...)`.
|
||||
|
||||
### `copyWith` for nullable fields
|
||||
`TaskEntity.copyWith` uses a private `const _unset = Object()` sentinel so callers can explicitly pass `null` to clear a nullable field without ambiguity.
|
||||
|
||||
---
|
||||
|
||||
## Tests (`test/data/repositories/`)
|
||||
|
||||
30 unit tests using `NativeDatabase.memory()` (real in-memory SQLite, no mocking).
|
||||
|
||||
| File | Tests |
|
||||
|---|---|
|
||||
| `user_repository_test.dart` | 6 |
|
||||
| `task_repository_test.dart` | 12 |
|
||||
| `pet_repository_test.dart` | 12 |
|
||||
|
||||
Each test group creates a fresh `AppDatabase` in `setUp` and closes it in `tearDown`.
|
||||
|
||||
---
|
||||
|
||||
## Code Generation
|
||||
|
||||
After any change to `tables.dart` or `app_database.dart`, regenerate with:
|
||||
|
||||
```bash
|
||||
dart run build_runner build --delete-conflicting-outputs
|
||||
```
|
||||
Reference in New Issue
Block a user