Data Sources topic
Data Sources
Datasources are the only classes that touch the network, the filesystem, or device-only APIs. They inject directly into Riverpod providers — there is no repository layer.
Supabase
| File | Responsibility |
|---|---|
supabase_datasource.dart |
Content + user progress (domains, topics, lessons, sessions, annotations, goals). The largest surface in the codebase. |
org_datasource.dart |
Organizations, teams, members, invitations, certifications, learning paths. |
entitlement_datasource.dart |
Feature-flag lookups keyed by plan + org + user. |
grants_datasource.dart |
OAuth grants (Google, Notion, Slack, Lucca). |
billing_datasource.dart |
Subscription state; RevenueCat synced back into Supabase. |
user_prefs_datasource.dart |
Per-user preferences (last org, onboarding step, etc.). |
AI providers
The client talks to three AI vendors directly (bring-your-own-key) or via the ai-proxy Supabase Edge Function (managed plan):
| File | Vendor |
|---|---|
claude_datasource.dart |
Anthropic Claude Messages API |
openai_datasource.dart |
OpenAI Chat Completions |
gemini_datasource.dart |
Google Gemini |
proxy_ai_datasource.dart |
Dutato's ai-proxy Edge Function (Claude on the managed plan) |
ai_datasource.dart |
Sealed union — the provider factory picks an implementation based on selectedAiProvider state. |
Local (native-only, stubbed on web)
| File | Backed by |
|---|---|
local_embedding_datasource.dart |
ONNX Runtime (sentence-transformers MiniLM) |
local_vector_store.dart |
SQLite + sqlite_vector |
revenuecat_datasource.dart |
RevenueCat SDK |
Each has a *_barrel.dart that switches between the real implementation and a stub at import time. Always import the barrel, never the underlying file directly — the barrel is what lets the web build ship without dart:io.
Pattern
A typical datasource looks like:
class SupabaseDatasource {
SupabaseDatasource(this._client);
final SupabaseClient _client;
Future<List<Topic>> fetchTopics(String bookId) async {
final response = await _client
.from('topics')
.select()
.eq('book_id', bookId)
.order('position');
return (response as List)
.map((row) => Topic.fromJson(row))
.toList();
}
}
- Constructor-injected client.
- Raw Supabase/Dio calls; no wrapping the result in a
Result<T, E>. Exceptions bubble up and are handled by the caller viauserFriendlyMessage. - Models are freezed + json_serializable so
fromJsonis generated.
Riverpod providers thread the datasource in once (providers.dart builds the singleton supabaseDatasource provider) and every screen/provider that needs it reads that provider.
Classes
- AiDatasource Data Sources
- Abstract interface for AI provider datasources.
- OrgDatasource Data Sources
- SupabaseDatasource Data Sources