53 lines
1.4 KiB
Cheetah
53 lines
1.4 KiB
Cheetah
---
|
|
description: "Supabase conventions for {{PROJECT_NAME}}"
|
|
alwaysApply: true
|
|
---
|
|
|
|
# Supabase Standards — {{PROJECT_NAME}}
|
|
|
|
## Row Level Security (RLS) awareness
|
|
- **ALWAYS** assume RLS is enabled — never write queries that assume full table access
|
|
- Test queries in the Supabase dashboard before implementing in Flutter
|
|
- If a query returns empty unexpectedly, check RLS policies first
|
|
|
|
## Queries
|
|
```dart
|
|
// ✅ Type-safe query with error handling
|
|
Future<List<Product>> getProducts() async {
|
|
final response = await _supabase
|
|
.from('products')
|
|
.select('id, name, price, category_id, categories(name)')
|
|
.eq('is_active', true)
|
|
.order('created_at', ascending: false)
|
|
.limit(50);
|
|
return response.map(Product.fromMap).toList();
|
|
}
|
|
```
|
|
|
|
## Realtime subscriptions
|
|
```dart
|
|
StreamSubscription<List<Map<String, dynamic>>>? _sub;
|
|
|
|
void watchOrders(String userId) {
|
|
_sub = _supabase
|
|
.from('orders')
|
|
.stream(primaryKey: ['id'])
|
|
.eq('user_id', userId)
|
|
.listen(
|
|
(data) => _updateOrders(data),
|
|
onError: (e) => _handleError(e),
|
|
);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_sub?.cancel(); // ALWAYS cancel in dispose()
|
|
super.dispose();
|
|
}
|
|
```
|
|
|
|
## Auth session
|
|
- Use `supabase.auth.onAuthStateChange` stream — never poll auth state
|
|
- Persist session: `supabase-flutter` handles this automatically via secure storage
|
|
- `session.accessToken` expires — check `session.isExpired` before sensitive operations
|