orbitnest_studio_flutter 1.5.0
orbitnest_studio_flutter: ^1.5.0 copied to clipboard
Official Flutter SDK for OrbitNest Studio — auth (MFA, passkeys, SMS), Postgres queries, realtime, storage, and vector search for your backend.
OrbitNest Studio Flutter #
A secure, comprehensive Flutter client for OrbitNest Studio - A powerful Supabase-compatible backend as a service. This package provides a drop-in replacement for Supabase with direct method access for seamless developer experience, powered by the BLoC pattern behind the scenes for reactive state management.
🚀 Features #
- 🔐 Authentication: Complete authentication system with email/password, OTP, and session management
- 🗄️ Database Operations: CRUD operations with Supabase-compatible query builder
- ⚡ Edge Functions: Function invocation with support for GET, POST, PUT, DELETE methods
- 🎯 Direct Method Access: Supabase-style API with simple, intuitive method calls
- 🔄 Supabase Compatibility: Drop-in replacement with identical APIs for easy migration
- 🔑 Token Management: Secure JWT token storage with automatic refresh and expiration handling
- 🛡️ Type Safety: Full null-safety with Freezed models and comprehensive error handling
- 🌐 HTTP Client: Robust Dio-based HTTP client with interceptors for auth, errors, and logging
- 🌍 Environment Configuration: Secure configuration management with .env support
- 📱 Production Ready: Comprehensive error handling and monitoring capabilities
- 🔒 Enterprise Security: Advanced token validation, secure storage, data sanitization, and security headers
- 🎛️ Advanced BLoC Access: Optional reactive state management for complex use cases
📋 Table of Contents #
- Installation
- Environment Setup
- Quick Start
- Direct Method API
- Authentication Guide
- Database Operations
- Edge Functions
- Advanced BLoC Usage
- Error Handling
- Security Features
- Migration from Supabase
- Best Practices
- API Reference
Current Implementation Status #
✅ Completed:
- Project structure and dependencies
- HTTP client with interceptors (auth, error, logging)
- Authentication models (User, Session, AuthResponse)
- Authentication BLoC with events and states
- Token management with secure storage
- Authentication service and repository
- Database models (PostgrestResponse)
- Database BLoC with events and states
- Supabase-compatible PostgrestQueryBuilder with full filter support
- Database service and repository with CRUD operations
- Edge functions models (FunctionResponse)
- Edge functions BLoC with events and states
- Edge functions service and repository
- Function invocation capabilities (no management operations)
- Direct Method API with Supabase-style method calls
- Environment variable management
- Main OrbitNestClient with database and functions integration
- Constants and error codes
- Type definitions and JSON serialization
🎉 Package is now feature-complete for core functionality
📦 Installation #
Add this to your package's pubspec.yaml:
dependencies:
orbitnest_studio_flutter: ^1.0.0
Environment Configuration #
- Copy
.env.exampleto.envin your project root:
cp .env.example .env
- Fill in your OrbitNest Studio project details in
.env:
# Only the public, RLS-protected anon key belongs in a client app.
# The project slug and API base URL are decoded from the anon key at runtime.
ORBITNEST_ANON_KEY=your-anon-key
ORBITNEST_DEBUG=true
⚠️ Security: This is a client SDK. Only the public, RLS-protected anon key belongs in a mobile app. Never put a service-role (admin) key in a client build or in any file that gets bundled into the app — that credential is server-side only. Anything shipped as a Flutter asset (including a bundled
.env) is packaged verbatim inside the APK/IPA and is trivially extractable, so it must never contain a secret beyond the anon key.
🚀 Quick Start #
Initialize the Client #
import 'package:orbitnest_studio_flutter/orbitnest_studio_flutter.dart';
// Initialize environment configuration first
await EnvConfig.initialize();
// Create client using environment variables
final orbitnest = OrbitNestClient.create();
// Or pass the anon key explicitly (the base URL and project slug are
// decoded from the anon key JWT automatically).
final orbitnest = OrbitNestClient.create(
anonKey: 'your-anon-key',
);
🎯 Direct Method API #
OrbitNest provides a Supabase-style direct method API that makes it incredibly easy to work with your backend. No need to understand BLoCs - just call methods directly!
🔐 Authentication #
// Sign up a new user
final authData = await orbitnest.signUp('user@example.com', 'password123');
print('User: ${authData['user']}');
// Sign in existing user
final authData = await orbitnest.signIn('user@example.com', 'password123');
print('Session: ${authData['session']}');
// Sign out
await orbitnest.signOut();
// Get current user
final user = orbitnest.currentUser();
print('Current user: $user');
🗄️ Database Operations #
// Select data from a table
final users = await orbitnest.select('users',
filters: {'status': 'active'},
orderBy: ['created_at'],
limit: 10,
);
// Insert new record
final newUser = await orbitnest.insert('users', {
'name': 'John Doe',
'email': 'john@example.com',
'status': 'active',
});
// Update records
final updatedUsers = await orbitnest.update('users',
{'status': 'inactive'},
filters: {'id': 123},
);
// Delete records
final deletedUsers = await orbitnest.delete('users',
filters: {'status': 'deleted'},
);
// Execute raw SQL
final result = await orbitnest.sql(
'SELECT * FROM users WHERE created_at > ?',
parameters: ['2024-01-01'],
);
⚡ Edge Functions #
// Invoke a function
final result = await orbitnest.function('send-email', params: {
'to': 'user@example.com',
'subject': 'Welcome!',
'message': 'Hello World',
});
// Access the functions API directly for more options
final response = await orbitnest.functions.invoke('my-function',
method: 'POST',
body: {'data': 'value'},
headers: {'Custom-Header': 'value'},
);
🎛️ Advanced Usage Examples #
Using the Query Builder (Supabase-compatible)
// Complex queries using the familiar Supabase syntax
final users = await orbitnest
.from('users')
.select('id, name, profiles(*)')
.eq('status', 'active')
.gte('age', 18)
.order('created_at', ascending: false)
.limit(50)
.execute();
🎛️ Advanced BLoC Usage #
For complex applications that need reactive state management, you can access the underlying BLoCs directly:
When to Use BLoCs vs Direct Methods #
Use Direct Methods When:
- Simple CRUD operations
- One-time function calls
- Straightforward authentication flows
- Quick prototyping
Use BLoCs When:
- Building reactive UIs that respond to state changes
- Complex state management across multiple widgets
- Real-time updates and streaming
- Advanced error handling and loading states
Accessing BLoCs Directly #
import 'package:flutter_bloc/flutter_bloc.dart';
// Listen to state changes through the simplified APIs
orbitnest.auth.onAuthStateChange.listen((state) {
state.when(
authenticated: (user, session) => print('User signed in: ${user.email}'),
unauthenticated: () => print('User signed out'),
error: (message, code, details) => print('Auth error: $message'),
initial: () => print('Initial state'),
loading: () => print('Authentication in progress...'),
otpSent: (email, message, type) => print('OTP sent to $email'),
passwordResetSent: (email, message) => print('Password reset sent to $email'),
userUpdated: (user, message) => print('User updated: $message'),
);
});
orbitnest.database.onStateChange.listen((state) {
state.when(
dataSelected: (table, response) => print('Data selected from $table'),
dataInserted: (table, response) => print('Data inserted into $table'),
error: (message, code, table, query, hint, details) => print('DB error: $message'),
loading: () => print('Database operation in progress...'),
initial: () => {},
// Handle other states...
);
});
orbitnest.functions.onStateChange.listen((state) {
state.when(
invoked: (functionName, response) => print('Function $functionName executed'),
error: (message, code, functionName) => print('Function error: $message'),
initial: () => {},
loading: () => print('Function executing...'),
);
});
BlocBuilder Integration #
class UserList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<DatabaseBloc, DatabaseState>(
bloc: orbitnest.database.databaseBloc,
builder: (context, state) {
return state.when(
initial: () => Text('Ready to load data'),
loading: () => CircularProgressIndicator(),
dataSelected: (table, response) {
return ListView.builder(
itemCount: response.data.length,
itemBuilder: (context, index) {
final user = response.data[index];
return ListTile(
title: Text(user['name']),
subtitle: Text(user['email']),
);
},
);
},
error: (message, code, table, query, hint, details) {
return Text('Error: $message');
},
// Handle other states...
);
},
);
}
}
```dart
// Function invocation
final response = await orbitnest.functions.invoke(
'my-function',
body: {'param1': 'value1'},
);
// Database operations
final users = await orbitnest.database.select('users');
// Supabase-compatible query builder
final response = await orbitnest
.from('users')
.select('id, name, email')
.eq('status', 'active')
.execute();
🔐 Authentication Guide #
OrbitNest Studio provides comprehensive authentication with both traditional email/password and modern OTP-based flows. All authentication is handled through the BLoC pattern for reactive state management.
Authentication Architecture #
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ UI Layer │ │ BLoC Layer │ │ Service Layer │
│ │ │ │ │ │
│ ▶ AuthForm │───▶│ ▶ AuthBloc │───▶│ ▶ AuthService │
│ ▶ AuthListener │◀───│ ▶ AuthEvent │◀───│ ▶ TokenManager │
│ ▶ AuthBuilder │ │ ▶ AuthState │ │ ▶ SecureStorage │
└─────────────────┘ └──────────────────┘ └─────────────────┘
Basic Authentication Setup #
1. Set up BLoC Listener
class AuthWrapper extends StatelessWidget {
final Widget child;
const AuthWrapper({Key? key, required this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocListener<AuthBloc, AuthState>(
bloc: orbitnest.auth,
listener: (context, state) {
state.when(
// User successfully authenticated
authenticated: (user, session) {
Navigator.pushReplacementNamed(context, '/home');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Welcome ${user.email}!')),
);
},
// OTP sent to user's email
otpSent: (email, message, type) {
Navigator.pushNamed(context, '/verify-otp', arguments: {
'email': email,
'type': type,
});
},
// Authentication error occurred
error: (message, code, details) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
),
);
},
// User logged out
unauthenticated: () {
Navigator.pushReplacementNamed(context, '/login');
},
// Other states...
initial: () {},
loading: () {},
passwordResetSent: (email, message) {},
userUpdated: (user, message) {},
);
},
child: child,
);
}
}
Quick Authentication Example #
// Simple async/await API (recommended)
try {
// Sign up with email (OTP-based)
final otpResult = await orbitnest.auth.signUpWithEmail('user@example.com');
print('OTP sent to ${otpResult['email']}');
// Verify OTP
final authResult = await orbitnest.auth.verifySignUp(
email: 'user@example.com',
otp: '123456',
password: 'secure-password',
);
final user = authResult['user'] as User;
print('Welcome ${user.email}!');
} catch (e) {
print('Auth error: $e');
}
// Traditional email/password sign in
try {
final result = await orbitnest.auth.signInWithPassword(
email: 'user@example.com',
password: 'password123',
);
final user = result['user'] as User;
print('Signed in as ${user.email}');
} catch (e) {
print('Sign in error: $e');
}
// Check authentication status
if (orbitnest.auth.isAuthenticated) {
final user = orbitnest.auth.currentUser;
print('Current user: ${user?.email}');
}
// Listen to auth state changes (optional, for reactive UI)
orbitnest.auth.onAuthStateChange.listen((state) {
state.when(
authenticated: (user, session) => print('User logged in'),
unauthenticated: () => print('User logged out'),
// ... other states
orElse: () {},
);
});
Database Operations #
// Simple async/await API (recommended)
try {
// Insert data
final insertResult = await orbitnest.database.insert('users', {
'name': 'John Doe',
'email': 'john@example.com',
'age': 30,
});
print('User created: ${insertResult.data}');
// Select data
final users = await orbitnest.database.select('users',
columns: 'id, name, email',
filters: {'status': 'active'},
limit: 10,
);
print('Found ${users.data?.length} users');
// Update data
final updateResult = await orbitnest.database.update('users',
{'age': 31},
filters: {'id': 1},
);
print('Updated ${updateResult.count} records');
// Delete data
await orbitnest.database.delete('users', filters: {'id': 1});
print('User deleted');
} catch (e) {
print('Database error: $e');
}
Supabase-Compatible Query Builder #
// Simple select query
final response = await orbitnest
.from('users')
.select('id, name, email')
.eq('status', 'active')
.order('created_at', ascending: false)
.limit(10)
.execute();
// Insert with query builder
await orbitnest
.from('users')
.insert({
'name': 'Jane Doe',
'email': 'jane@example.com',
});
// Complex query with multiple filters
final result = await orbitnest
.from('posts')
.select('*, author:users(name)')
.eq('published', true)
.gt('created_at', '2024-01-01')
.like('title', '%flutter%')
.order('created_at', ascending: false)
.range(0, 49)
.execute();
Edge Functions #
// Simple async/await API (recommended)
try {
// Invoke a function
final response = await orbitnest.functions.invoke('send-email', body: {
'to': 'user@example.com',
'subject': 'Welcome!',
'message': 'Welcome to our app!',
});
print('Email sent: ${response.data}');
// Alternative calling methods
final result1 = await orbitnest.functions.call('my-function', params: {'key': 'value'});
final result2 = await orbitnest.functions.post('api-endpoint', body: {'data': 'value'});
final result3 = await orbitnest.functions.get('health-check');
final result4 = await orbitnest.functions.put('update-endpoint', body: {'update': 'data'});
final result5 = await orbitnest.functions.delete('remove-endpoint');
} catch (e) {
print('Function error: $e');
}
// Direct BLoC usage for reactive UI
orbitnest.functionsBloc.add(
FunctionsEvent.invoke(
functionName: 'process-payment',
method: 'POST',
body: {'amount': 100, 'currency': 'USD'},
),
);
Note: This package only supports function invocation. Function management operations (create, update, delete) are not supported and should be done through the OrbitNest Studio dashboard or admin APIs. final newFunction = await orbitnest.functions.create( name: 'process-payment', sourceCode: ''' export default async function(req) { const body = await req.json(); // Process payment logic here return new Response(JSON.stringify({success: true})); } ''', environmentVariables: { 'STRIPE_KEY': 'sk_test_...', }, ); print('Function created: ${newFunction.name}');
// List all functions final functions = await orbitnest.functions.list(); print('Available functions: ${functions.map((f) => f.name).join(', ')}');
// Set environment variables await orbitnest.functions.setEnvironmentVariable( name: 'API_URL', value: 'https://api.example.com', );
} catch (e) { print('Function management error: $e'); }
### Cleanup
```dart
@override
void dispose() {
orbitnest.dispose();
super.dispose();
}
🗄️ Database Operations #
OrbitNest Studio provides a powerful, Supabase-compatible database interface with CRUD operations only (Create, Read, Update, Delete). This package does not support database management operations such as creating tables, managing schemas, or RLS policies.
Database Architecture #
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ UI Layer │ │ BLoC Layer │ │ Service Layer │
│ │ │ │ │ │
│ ▶ DataWidget │───▶│ ▶ DatabaseBloc │───▶│ ▶ DatabaseSvc │
│ ▶ DataListener │◀───│ ▶ DatabaseEvent │◀───│ ▶ QueryBuilder │
│ ▶ DataBuilder │ │ ▶ DatabaseState │ │ ▶ HttpClient │
└─────────────────┘ └──────────────────┘ └─────────────────┘
Query Builder (Supabase Compatible) #
The package provides a PostgrestQueryBuilder that's 100% compatible with Supabase's query builder:
// Simple select query
final response = await orbitnest
.from('users')
.select('id, name, email, created_at')
.execute();
// With filtering and ordering
final activeUsers = await orbitnest
.from('users')
.select('id, name, email')
.eq('status', 'active')
.gt('created_at', '2024-01-01')
.order('created_at', ascending: false)
.limit(50)
.execute();
// Complex query with joins
final postsWithAuthors = await orbitnest
.from('posts')
.select('*, author:users(name, email)')
.eq('published', true)
.like('title', '%flutter%')
.range(0, 19) // Pagination
.execute();
// Full-text search
final searchResults = await orbitnest
.from('articles')
.select('id, title, content')
.textSearch('title', 'flutter OR dart')
.execute();
CRUD Operations with BLoC #
Setup Database BLoC Listener
class DatabaseWrapper extends StatelessWidget {
final Widget child;
const DatabaseWrapper({Key? key, required this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocListener<DatabaseBloc, DatabaseState>(
bloc: orbitnest.database,
listener: (context, state) {
state.when(
// Operation completed successfully
success: (result) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Operation completed: ${result.count} rows affected')),
);
},
// Database error occurred
error: (message, code, table) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Database Error: $message'),
backgroundColor: Colors.red,
),
);
},
// Loading and initial states
loading: () {},
initial: () {},
);
},
child: child,
);
}
}
Create Operations
class CreateUserScreen extends StatefulWidget {
@override
_CreateUserScreenState createState() => _CreateUserScreenState();
}
class _CreateUserScreenState extends State<CreateUserScreen> {
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Create User')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _nameController,
decoration: const InputDecoration(labelText: 'Name'),
validator: (value) => value?.isEmpty ?? true ? 'Name is required' : null,
),
TextFormField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
validator: (value) => value?.isEmpty ?? true ? 'Email is required' : null,
),
const SizedBox(height: 24),
// Submit button with loading state
BlocBuilder<DatabaseBloc, DatabaseState>(
bloc: orbitnest.database,
builder: (context, state) {
final isLoading = state.maybeWhen(
loading: () => true,
orElse: () => false,
);
return ElevatedButton(
onPressed: isLoading ? null : _createUser,
child: isLoading
? const CircularProgressIndicator()
: const Text('Create User'),
);
},
),
],
),
),
),
);
}
void _createUser() {
if (!_formKey.currentState!.validate()) return;
// Using BLoC
orbitnest.database.add(DatabaseEvent.insert(
table: 'users',
values: {
'name': _nameController.text.trim(),
'email': _emailController.text.trim(),
'status': 'active',
'created_at': DateTime.now().toIso8601String(),
},
));
// Or using Query Builder directly
// orbitnest.from('users').insert({
// 'name': _nameController.text.trim(),
// 'email': _emailController.text.trim(),
// 'status': 'active',
// });
}
}
Read Operations with Real-time Updates
class UsersListScreen extends StatefulWidget {
@override
_UsersListScreenState createState() => _UsersListScreenState();
}
class _UsersListScreenState extends State<UsersListScreen> {
List<Map<String, dynamic>> users = [];
String searchQuery = '';
@override
void initState() {
super.initState();
_loadUsers();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Users'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _loadUsers,
),
],
),
body: Column(
children: [
// Search bar
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
decoration: const InputDecoration(
labelText: 'Search users',
prefixIcon: Icon(Icons.search),
),
onChanged: (value) {
setState(() => searchQuery = value);
_searchUsers(value);
},
),
),
// Users list with BLoC builder
Expanded(
child: BlocBuilder<DatabaseBloc, DatabaseState>(
bloc: orbitnest.database,
builder: (context, state) {
return state.when(
// Loading state
loading: () => const Center(child: CircularProgressIndicator()),
// Success state
success: (result) {
users = List<Map<String, dynamic>>.from(result.data ?? []);
if (users.isEmpty) {
return const Center(
child: Text('No users found'),
);
}
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return UserTile(
user: user,
onEdit: () => _editUser(user),
onDelete: () => _deleteUser(user['id']),
);
},
);
},
// Error state
error: (message, code, table) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error, size: 64, color: Colors.red),
const SizedBox(height: 16),
Text(message),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _loadUsers,
child: const Text('Retry'),
),
],
),
),
// Initial state
initial: () => const Center(child: Text('Tap refresh to load users')),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.pushNamed(context, '/create-user'),
child: const Icon(Icons.add),
),
);
}
void _loadUsers() {
orbitnest.database.add(const DatabaseEvent.select(
table: 'users',
columns: 'id, name, email, status, created_at',
orderBy: [{'column': 'created_at', 'ascending': false}],
));
}
void _searchUsers(String query) {
if (query.isEmpty) {
_loadUsers();
return;
}
orbitnest.database.add(DatabaseEvent.select(
table: 'users',
columns: 'id, name, email, status, created_at',
filters: [
{'column': 'name', 'operator': 'ilike', 'value': '%$query%'},
],
));
}
void _editUser(Map<String, dynamic> user) {
Navigator.pushNamed(context, '/edit-user', arguments: user);
}
void _deleteUser(String userId) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Delete User'),
content: const Text('Are you sure you want to delete this user?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
orbitnest.database.add(DatabaseEvent.delete(
table: 'users',
filters: [{'column': 'id', 'operator': 'eq', 'value': userId}],
));
},
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
child: const Text('Delete'),
),
],
),
);
}
}
class UserTile extends StatelessWidget {
final Map<String, dynamic> user;
final VoidCallback onEdit;
final VoidCallback onDelete;
const UserTile({
Key? key,
required this.user,
required this.onEdit,
required this.onDelete,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: CircleAvatar(
child: Text(user['name']?[0]?.toUpperCase() ?? '?'),
),
title: Text(user['name'] ?? 'Unknown'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(user['email'] ?? 'No email'),
Text(
'Status: ${user['status']} • Created: ${DateTime.parse(user['created_at']).toLocal().toString().split(' ')[0]}',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
trailing: PopupMenuButton(
itemBuilder: (context) => [
const PopupMenuItem(
value: 'edit',
child: Row(
children: [Icon(Icons.edit), SizedBox(width: 8), Text('Edit')],
),
),
const PopupMenuItem(
value: 'delete',
child: Row(
children: [Icon(Icons.delete, color: Colors.red), SizedBox(width: 8), Text('Delete')],
),
),
],
onSelected: (value) {
if (value == 'edit') onEdit();
if (value == 'delete') onDelete();
},
),
),
);
}
}
Advanced Database Features #
Row Level Security (RLS)
// Enable RLS on a table
void enableRLS() {
orbitnest.database.add(const DatabaseEvent.enableRLS(table: 'users'));
}
// Create RLS policy
void createRLSPolicy() {
orbitnest.database.add(DatabaseEvent.createRLSPolicy(
table: 'users',
policyName: 'users_select_own',
operation: 'SELECT',
definition: 'auth.uid() = id',
));
}
Bulk Operations
// Bulk insert
void bulkInsertUsers(List<Map<String, dynamic>> users) {
orbitnest.database.add(DatabaseEvent.bulkInsert(
table: 'users',
values: users,
));
}
// Bulk update
void bulkUpdateStatus(List<String> userIds, String status) {
orbitnest.database.add(DatabaseEvent.bulkUpdate(
table: 'users',
values: {'status': status, 'updated_at': DateTime.now().toIso8601String()},
filters: [{'column': 'id', 'operator': 'in', 'value': userIds}],
));
}
Raw SQL Execution
// Execute custom SQL
void executeCustomQuery() {
orbitnest.database.add(const DatabaseEvent.executeSql(
sql: '''
SELECT u.name, u.email, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON u.id = p.author_id
WHERE u.status = 'active'
GROUP BY u.id, u.name, u.email
ORDER BY post_count DESC
LIMIT 10
''',
));
}
Database Best Practices #
1. Error Handling
void handleDatabaseErrors() {
orbitnest.database.stream.listen((state) {
state.whenOrNull(
error: (message, code, table) {
switch (code) {
case 'TABLE_NOT_FOUND':
// Handle table not found
showError('Table $table does not exist');
break;
case 'RLS_VIOLATION':
// Handle RLS policy violation
showError('Access denied: insufficient permissions');
break;
case 'CONSTRAINT_VIOLATION':
// Handle constraint violations
showError('Data validation failed');
break;
default:
showError('Database error: $message');
}
},
);
});
}
2. Performance Optimization
// Use pagination for large datasets
final pagedData = await orbitnest
.from('large_table')
.select('*')
.range(0, 49) // First 50 records
.execute();
// Use indexes for filtering
final indexedQuery = await orbitnest
.from('users')
.select('*')
.eq('email', email) // Assuming email is indexed
.execute();
// Limit columns to reduce payload
final essentialData = await orbitnest
.from('users')
.select('id, name, email') // Only necessary columns
.execute();
⚡ Edge Functions #
Edge Functions provide serverless compute capabilities with invocation-only support through the BLoC pattern. This package does not support function management operations (create, update, delete) - only invocation.
Edge Functions BLoC Implementation #
class FunctionInvoker extends StatefulWidget {
@override
_FunctionInvokerState createState() => _FunctionInvokerState();
}
class _FunctionInvokerState extends State<FunctionInvoker> {
@override
Widget build(BuildContext context) {
return BlocListener<FunctionsBloc, FunctionsState>(
bloc: orbitnest.functionsBloc,
listener: (context, state) {
state.when(
// Function invoked successfully
invoked: (functionName, response) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Function $functionName executed successfully')),
);
},
// Error occurred
error: (message, code, functionName) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Function Error: $message'),
backgroundColor: Colors.red,
),
);
},
// Other states...
initial: () {},
loading: () {},
deleted: (functionName) {},
logsLoaded: (functionName, logs) {},
environmentVariablesListed: (variables) {},
environmentVariableSet: (name, value) {},
environmentVariableDeleted: (name) {},
bulkEnvironmentVariablesSet: (count) {},
);
},
child: YourFunctionWidget(),
);
}
}
Function Invocation #
// Simple function invocation
void invokeFunction() {
orbitnest.functions.add(FunctionsEvent.invoke(
functionName: 'send-email',
body: {
'to': 'user@example.com',
'subject': 'Welcome!',
'message': 'Welcome to our app!',
},
));
}
// Function with custom headers
void invokeFunctionWithHeaders() {
orbitnest.functions.add(FunctionsEvent.invoke(
functionName: 'api-proxy',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'custom-value',
},
body: {'data': 'payload'},
));
}
🎯 BLoC Pattern Implementation #
OrbitNest Studio uses the BLoC pattern throughout for predictable state management. All BLoCs are exposed through the main client, allowing you to access them directly for reactive state management:
// Access BLoCs directly
final authBloc = orbitnest.authBloc;
final databaseBloc = orbitnest.databaseBloc;
final functionsBloc = orbitnest.functionsBloc;
// Use simplified APIs (recommended for most cases)
final auth = orbitnest.auth;
final database = orbitnest.database;
final functions = orbitnest.functions;
1. Direct BLoC Usage #
// Direct BLoC operations for maximum control
orbitnest.functionsBloc.add(
FunctionsEvent.invoke(
functionName: 'my-function',
body: {'param': 'value'},
),
);
orbitnest.databaseBloc.add(
DatabaseEvent.select(
table: 'users',
columns: 'id, name, email',
),
);
2. Provider Setup #
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider.value(value: orbitnest.authBloc),
BlocProvider.value(value: orbitnest.databaseBloc),
BlocProvider.value(value: orbitnest.functionsBloc),
],
child: MaterialApp(
home: AuthStateBuilder(),
),
);
}
}
3. State Management Patterns #
Listening to Multiple BLoCs
class MultiStateListener extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocListener(
listeners: [
BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
// Handle auth state changes
},
),
BlocListener<DatabaseBloc, DatabaseState>(
listener: (context, state) {
// Handle database state changes
},
),
BlocListener<FunctionsBloc, FunctionsState>(
listener: (context, state) {
// Handle functions state changes
},
),
],
child: YourWidget(),
);
}
}
Building UI from Multiple States
class CombinedStateBuilder extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<AuthBloc, AuthState>(
builder: (context, authState) {
return authState.when(
authenticated: (user, session) => BlocBuilder<DatabaseBloc, DatabaseState>(
builder: (context, dbState) {
return dbState.when(
success: (result) => UserDataWidget(
user: user,
data: result.data,
),
loading: () => const LoadingWidget(),
error: (message, code, table) => ErrorWidget(message),
initial: () => const EmptyStateWidget(),
);
},
),
loading: () => const AuthLoadingWidget(),
unauthenticated: () => const LoginWidget(),
error: (message, code, details) => ErrorWidget(message),
initial: () => const SplashWidget(),
otpSent: (email, message, type) => OtpWidget(email: email),
passwordResetSent: (email, message) => const PasswordResetSentWidget(),
userUpdated: (user, message) => UserUpdatedWidget(user: user),
);
},
);
}
}
🛡️ Error Handling & Best Practices #
Comprehensive Error Handling #
class ErrorHandler {
static void handleAuthError(String message, String? code, Map<String, dynamic>? details) {
switch (code) {
case 'INVALID_CREDENTIALS':
showError('Invalid email or password');
break;
case 'EMAIL_NOT_CONFIRMED':
showError('Please check your email and confirm your account');
break;
case 'TOKEN_EXPIRED':
// Automatically try to refresh
orbitnest.auth.add(const AuthEvent.refreshSession());
break;
default:
showError(message);
}
}
static void handleDatabaseError(String message, String? code, String? table) {
switch (code) {
case 'TABLE_NOT_FOUND':
showError('Resource not found');
break;
case 'RLS_VIOLATION':
showError('Access denied');
break;
case 'CONSTRAINT_VIOLATION':
showError('Invalid data provided');
break;
default:
showError('Database error: $message');
}
}
static void showError(String message) {
// Implement your error display logic
Get.snackbar('Error', message, backgroundColor: Colors.red);
}
}
Best Practices #
1. State Management
// ✅ DO: Use BlocBuilder for UI that depends on state
BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) => state.when(
authenticated: (user, session) => HomeScreen(user: user),
unauthenticated: () => const LoginScreen(),
loading: () => const LoadingScreen(),
error: (message, code, details) => ErrorScreen(message: message),
initial: () => const SplashScreen(),
otpSent: (email, message, type) => OtpScreen(email: email),
passwordResetSent: (email, message) => const ResetSentScreen(),
userUpdated: (user, message) => HomeScreen(user: user),
),
)
// ❌ DON'T: Access state directly in build method
class BadExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
final authBloc = orbitnest.auth;
final currentState = authBloc.state; // DON'T DO THIS
// UI won't rebuild when state changes
return Container();
}
}
2. Resource Management
class ProperResourceManagement extends StatefulWidget {
@override
_ProperResourceManagementState createState() => _ProperResourceManagementState();
}
class _ProperResourceManagementState extends State<ProperResourceManagement> {
late StreamSubscription _authSubscription;
@override
void initState() {
super.initState();
// ✅ DO: Subscribe to streams in initState
_authSubscription = orbitnest.auth.stream.listen((state) {
// Handle state changes
});
}
@override
void dispose() {
// ✅ DO: Always dispose of subscriptions
_authSubscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container();
}
}
3. Performance Optimization
// ✅ DO: Use buildWhen to control rebuilds
BlocBuilder<DatabaseBloc, DatabaseState>(
buildWhen: (previous, current) {
// Only rebuild when data actually changes
return current.whenOrNull(
success: (result) => true,
error: (_, __, ___) => true,
) ?? false;
},
builder: (context, state) => YourWidget(),
)
// ✅ DO: Use specific BlocSelector for small parts
BlocSelector<AuthBloc, AuthState, User?>(
selector: (state) => state.whenOrNull(
authenticated: (user, session) => user,
),
builder: (context, user) => user != null
? Text('Welcome ${user.email}')
: const Text('Not logged in'),
)
🔒 Security Features #
OrbitNest Studio Flutter implements enterprise-grade security measures to protect your application and user data:
🛡️ Token Security #
Secure Token Storage
// Tokens are stored using FlutterSecureStorage with maximum encryption
static const FlutterSecureStorage _secureStorage = FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
keyCipherAlgorithm: KeyCipherAlgorithm.RSA_ECB_PKCS1Padding,
storageCipherAlgorithm: StorageCipherAlgorithm.AES_GCM_NoPadding,
),
iOptions: IOSOptions(
accessibility: KeychainAccessibility.first_unlock_this_device,
synchronizable: false, // Never sync tokens to iCloud
),
);
Token Validation & Integrity
// Automatic session validation with integrity checks
final session = await tokenManager.getStoredSession();
if (session != null) {
// ✅ Automatic validation includes:
// - Token expiration checking
// - JWT signature validation
// - Session integrity verification with checksums
// - Token age limits (max 24 hours)
// - Automatic cleanup of invalid sessions
}
Automatic Token Refresh
// Tokens are automatically refreshed before expiration
if (tokenManager.needsRefresh(accessToken)) {
// Automatically refreshes 5 minutes before expiration
await authBloc.add(const AuthEvent.refreshSession());
}
🚫 Data Sanitization #
Logging Protection
// All sensitive data is automatically redacted from logs
class LoggingInterceptor extends Interceptor {
// ✅ Automatically redacts:
// - JWT tokens, API keys, passwords
// - Credit card numbers, SSNs, PINs
// - Authorization headers and cookies
// - Any field containing 'password', 'token', 'secret', 'key'
Map<String, dynamic> _sanitizeData(dynamic data) {
// Recursively sanitizes nested objects and arrays
// Never logs sensitive information, even in debug mode
}
}
Request/Response Sanitization
// HTTP requests and responses are sanitized before logging
final sanitizedRequest = _sanitizeData(requestData);
final sanitizedHeaders = _sanitizeHeaders(headers);
OrbitNestLogger.logRequest(method, url, {
'headers': sanitizedHeaders, // Authorization: ***REDACTED***
'data': sanitizedRequest, // password: ***REDACTED***
});
🌐 Network Security #
Security Headers
// All HTTP requests include security headers
BaseOptions(
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-Client-Info': 'orbitnest_studio_flutter/1.0.0',
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
},
validateStatus: (status) => status >= 200 && status < 300,
followRedirects: false, // Prevent redirect attacks
maxRedirects: 0,
);
Request Validation
// All API keys and tokens are validated before use
bool isValidApiKey(String? apiKey) {
if (apiKey == null || apiKey.length < 32) return false;
return RegExp(r'^[a-zA-Z0-9\-_.]+$').hasMatch(apiKey);
}
🔐 Session Management #
Secure Session Lifecycle
// Sessions are managed with strict security controls
class TokenManager {
// ✅ Security features:
// - Automatic session expiration (24-hour max age)
// - Integrity verification with SHA-256 checksums
// - Secure memory cleanup on logout
// - Session invalidation on tampering detection
// - Automatic cleanup of expired sessions
Future<void> storeSession(Session session) async {
// Validates session before storage
if (!_isValidSession(session)) {
throw Exception('Invalid session data');
}
// Generates integrity checksum
final checksum = _generateChecksum(sessionJson);
await _secureStorage.write(key: 'session_checksum', value: checksum);
}
}
Session Validation
// Multi-layer session validation
bool _isValidSession(Session session) {
return session.accessToken.isNotEmpty &&
!isTokenExpired(session.accessToken) &&
!isTokenExpired(session.refreshToken) &&
_getTokenAge(session.accessToken)!.inHours <= 24;
}
🚨 Error Handling Security #
Safe Error Reporting
// Errors are sanitized before reporting, even in production
static void error(String message, [Object? error, StackTrace? stackTrace]) {
final sanitizedMessage = _sanitizeMessage(message);
final sanitizedError = _sanitizeError(error);
// Safe to log in production - no sensitive data exposed
developer.log(sanitizedMessage, error: sanitizedError);
}
Sensitive Data Detection
// Comprehensive sensitive data pattern detection
final sensitivePatterns = [
RegExp(r'eyJ[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*'), // JWT tokens
RegExp(r'[a-zA-Z0-9]{32,}'), // API keys
RegExp(r'password["\s]*[:=]["\s]*[^,}\s]+', caseSensitive: false), // Passwords
RegExp(r'token["\s]*[:=]["\s]*[^,}\s]+', caseSensitive: false), // Tokens
];
🔄 Production Security Checklist #
✅ Automatic Security Features
- Token Encryption: All tokens encrypted with device-specific keys
- Integrity Verification: SHA-256 checksums prevent token tampering
- Automatic Expiration: Sessions expire after 24 hours maximum
- Secure Headers: CSRF protection and cache control headers
- Data Sanitization: All logs sanitized automatically in debug AND production
- Redirect Prevention: Automatic redirects disabled to prevent attacks
- Input Validation: API keys and tokens validated before use
- Memory Cleanup: Secure cleanup of sensitive data on logout
🛡️ Security Best Practices Enforced
- No Hardcoded Secrets: All secrets must be in environment variables
- Secure Storage Only: Sensitive data never stored in regular preferences
- Production-Safe Logging: No sensitive data logged, even in debug mode
- Token Refresh: Automatic refresh prevents expired token usage
- Session Validation: Multi-layer validation prevents invalid sessions
- Network Security: Security headers and request validation by default
🔄 Migration from Supabase #
OrbitNest Studio is designed as a drop-in replacement for Supabase. Here's how to migrate:
1. Replace Dependencies #
# pubspec.yaml
# Before (Supabase)
dependencies:
supabase_flutter: ^2.0.0
# After (OrbitNest)
dependencies:
orbitnest_studio_flutter: ^1.0.0
2. Update Initialization #
// Before (Supabase)
await Supabase.initialize(
url: 'YOUR_SUPABASE_URL',
anonKey: 'YOUR_SUPABASE_ANON_KEY',
);
final supabase = Supabase.instance.client;
// After (OrbitNest)
await EnvConfig.initialize();
final orbitnest = OrbitNestClient.create();
3. Authentication Migration #
// Before (Supabase)
final response = await supabase.auth.signInWithPassword(
email: email,
password: password,
);
// After (OrbitNest)
orbitnest.auth.add(AuthEvent.signInWithPassword(
email: email,
password: password,
));
4. Database Query Migration #
// Before (Supabase) - Works the same!
final response = await supabase
.from('users')
.select('*')
.eq('status', 'active')
.execute();
// After (OrbitNest) - Identical syntax!
final response = await orbitnest
.from('users')
.select('*')
.eq('status', 'active')
.execute();
📱 Production Deployment #
Environment Configuration #
# Production .env — client apps ship ONLY the public anon key.
# The project slug and API base URL are decoded from the anon key at runtime.
ORBITNEST_ANON_KEY=your-production-anon-key
ORBITNEST_DEBUG=false
ORBITNEST_API_TIMEOUT=60000
⚠️ Do not add a service-role / admin key here. It is a server-side credential and would be extractable from the shipped binary.
Security Best Practices #
- Never commit .env files: Always use .env.example as template
- Use secure storage: Sensitive tokens are automatically encrypted
- Enable RLS: Always enable Row Level Security for production tables
- Validate inputs: Use form validation and server-side validation
- Monitor errors: Implement proper error tracking and monitoring
🎯 Architecture #
The package follows a clean architecture with clear separation of concerns:
┌─────────────────────────────────────────────────────────────┐
│ UI Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ AuthScreen │ │ DataScreen │ │ FuncScreen │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────┬───────────────────────────────────────┘
│
┌─────────────────────┴───────────────────────────────────────┐
│ BLoC Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ AuthBloc │ │ DatabaseBloc│ │FunctionsBloc│ │
│ │ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ │ │
│ │ │ Events │ │ │ │ Events │ │ │ │ Events │ │ │
│ │ │ States │ │ │ │ States │ │ │ │ States │ │ │
│ │ └─────────┘ │ │ └─────────┘ │ │ └─────────┘ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────┬───────────────────────────────────────┘
│
┌─────────────────────┴───────────────────────────────────────┐
│ Repository Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │AuthRepository│ │DbRepository │ │FuncRepository│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────┬───────────────────────────────────────┘
│
┌─────────────────────┴───────────────────────────────────────┐
│ Service Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ AuthService │ │DbService │ │FuncService │ │
│ │TokenManager │ │QueryBuilder │ │EnvVarMgr │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────┬───────────────────────────────────────┘
│
┌─────────────────────┴───────────────────────────────────────┐
│ Client Layer │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ OrbitNestHttpClient │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │AuthInterceptor│ │ErrorInterceptor│ │LogInterceptor│ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Architecture Layers: #
- Client Layer: Dio-based HTTP client with interceptors for authentication, error handling, and logging
- Service Layer: API interaction services that handle HTTP requests and responses
- Repository Layer: Data access abstraction layer that coordinates between services and BLoCs
- BLoC Layer: State management with events, states, and business logic
- UI Layer: Flutter widgets that react to state changes and dispatch events
Key Design Principles: #
- Separation of Concerns: Each layer has a single responsibility
- Dependency Injection: Lower layers don't depend on higher layers
- Reactive Architecture: UI reacts to state changes through BLoC pattern
- Type Safety: Freezed models ensure null-safety throughout
- Testability: Each layer can be tested independently
📚 API Reference #
OrbitNestClient #
Main client for interacting with OrbitNest Studio APIs.
class OrbitNestClient {
// Factory constructors — client apps pass only the public anon key.
factory OrbitNestClient.create({String? anonKey});
// Core services
AuthBloc get auth;
DatabaseBloc get database;
FunctionsBloc get functions;
// Query builder (Supabase compatible)
PostgrestQueryBuilder<T> from<T>(String table);
// Cleanup
void dispose();
}
Authentication Events #
@freezed
class AuthEvent with _$AuthEvent {
// Email-first registration (OTP-based)
const factory AuthEvent.signUpWithEmail({required String email, Map<String, dynamic>? metadata}) = AuthSignUpWithEmailEvent;
// Verify email registration OTP
const factory AuthEvent.verifySignUp({required String email, required String otp, String? password}) = AuthVerifySignUpEvent;
// Email-first signin (OTP-based)
const factory AuthEvent.signInWithEmail({required String email}) = AuthSignInWithEmailEvent;
// Verify email signin OTP
const factory AuthEvent.verifySignIn({required String email, required String otp}) = AuthVerifySignInEvent;
// Traditional email/password signup
const factory AuthEvent.signUp({required String email, required String password, Map<String, dynamic>? metadata}) = AuthSignUpEvent;
// Traditional email/password signin
const factory AuthEvent.signInWithPassword({required String email, required String password}) = AuthSignInWithPasswordEvent;
// Password recovery
const factory AuthEvent.recoverPassword({required String email}) = AuthRecoverPasswordEvent;
// Reset password with token
const factory AuthEvent.resetPassword({required String token, required String newPassword}) = AuthResetPasswordEvent;
// Update user profile
const factory AuthEvent.updateUser({String? email, String? password, Map<String, dynamic>? metadata}) = AuthUpdateUserEvent;
// Refresh session
const factory AuthEvent.refreshSession() = AuthRefreshSessionEvent;
// Sign out
const factory AuthEvent.signOut() = AuthSignOutEvent;
// Get current user
const factory AuthEvent.getCurrentUser() = AuthGetCurrentUserEvent;
}
Authentication States #
@freezed
class AuthState with _$AuthState {
const factory AuthState.initial() = AuthInitialState;
const factory AuthState.loading() = AuthLoadingState;
const factory AuthState.authenticated({required User user, required Session session}) = AuthAuthenticatedState;
const factory AuthState.unauthenticated() = AuthUnauthenticatedState;
const factory AuthState.otpSent({required String email, required String message, required String type}) = AuthOtpSentState;
const factory AuthState.passwordResetSent({required String email, required String message}) = AuthPasswordResetSentState;
const factory AuthState.userUpdated({required User user, required String message}) = AuthUserUpdatedState;
const factory AuthState.error({required String message, String? code, Map<String, dynamic>? details}) = AuthErrorState;
}
Database Events #
@freezed
class DatabaseEvent with _$DatabaseEvent {
// Select data
const factory DatabaseEvent.select({required String table, String? columns, List<Map<String, dynamic>>? filters, List<Map<String, dynamic>>? orderBy, int? limit, int? offset}) = DatabaseSelectEvent;
// Insert data
const factory DatabaseEvent.insert({required String table, required Map<String, dynamic> values, bool upsert = false}) = DatabaseInsertEvent;
// Update data
const factory DatabaseEvent.update({required String table, required Map<String, dynamic> values, required List<Map<String, dynamic>> filters}) = DatabaseUpdateEvent;
// Delete data
const factory DatabaseEvent.delete({required String table, required List<Map<String, dynamic>> filters}) = DatabaseDeleteEvent;
// Bulk operations
const factory DatabaseEvent.bulkInsert({required String table, required List<Map<String, dynamic>> values}) = DatabaseBulkInsertEvent;
const factory DatabaseEvent.bulkUpdate({required String table, required Map<String, dynamic> values, required List<Map<String, dynamic>> filters}) = DatabaseBulkUpdateEvent;
const factory DatabaseEvent.bulkDelete({required String table, required List<Map<String, dynamic>> filters}) = DatabaseBulkDeleteEvent;
// Raw SQL
const factory DatabaseEvent.executeSql({required String sql, List<dynamic>? parameters}) = DatabaseExecuteSqlEvent;
// RLS Management
const factory DatabaseEvent.enableRLS({required String table}) = DatabaseEnableRLSEvent;
const factory DatabaseEvent.disableRLS({required String table}) = DatabaseDisableRLSEvent;
const factory DatabaseEvent.createRLSPolicy({required String table, required String policyName, required String operation, required String definition}) = DatabaseCreateRLSPolicyEvent;
const factory DatabaseEvent.deleteRLSPolicy({required String table, required String policyName}) = DatabaseDeleteRLSPolicyEvent;
// Schema operations
const factory DatabaseEvent.getTableSchema({required String table}) = DatabaseGetTableSchemaEvent;
const factory DatabaseEvent.listTables() = DatabaseListTablesEvent;
}
Database States #
@freezed
class DatabaseState with _$DatabaseState {
const factory DatabaseState.initial() = DatabaseInitialState;
const factory DatabaseState.loading() = DatabaseLoadingState;
const factory DatabaseState.success({required PostgrestResponse<dynamic> result}) = DatabaseSuccessState;
const factory DatabaseState.error({required String message, String? code, String? table}) = DatabaseErrorState;
}
Functions Events #
@freezed
class FunctionsEvent with _$FunctionsEvent {
// Function invocation
const factory FunctionsEvent.invoke({required String functionName, String method = 'POST', dynamic body, Map<String, String>? headers}) = FunctionsInvokeEvent;
// Function management (admin only)
const factory FunctionsEvent.create({required String name, String? description, required String sourceCode, Map<String, String>? environmentVariables, Map<String, dynamic>? executionConfig}) = FunctionsCreateEvent;
const factory FunctionsEvent.list() = FunctionsListEvent;
const factory FunctionsEvent.get({required String name}) = FunctionsGetEvent;
const factory FunctionsEvent.update({required String name, String? description, String? sourceCode, Map<String, String>? environmentVariables, Map<String, dynamic>? executionConfig}) = FunctionsUpdateEvent;
const factory FunctionsEvent.delete({required String name}) = FunctionsDeleteEvent;
const factory FunctionsEvent.getLogs({required String name, int? limit, int? offset}) = FunctionsGetLogsEvent;
// Environment variables (admin only)
const factory FunctionsEvent.listEnvironmentVariables() = FunctionsListEnvironmentVariablesEvent;
const factory FunctionsEvent.setEnvironmentVariable({required String name, required String value, String? description, bool isSecret = false}) = FunctionsSetEnvironmentVariableEvent;
const factory FunctionsEvent.deleteEnvironmentVariable({required String name}) = FunctionsDeleteEnvironmentVariableEvent;
const factory FunctionsEvent.setBulkEnvironmentVariables({required Map<String, String> variables}) = FunctionsSetBulkEnvironmentVariablesEvent;
}
PostgrestQueryBuilder (Supabase Compatible) #
class PostgrestQueryBuilder<T> {
// Column selection
PostgrestQueryBuilder<T> select([String? columns]);
// Filtering methods
PostgrestQueryBuilder<T> eq(String column, dynamic value);
PostgrestQueryBuilder<T> neq(String column, dynamic value);
PostgrestQueryBuilder<T> gt(String column, dynamic value);
PostgrestQueryBuilder<T> gte(String column, dynamic value);
PostgrestQueryBuilder<T> lt(String column, dynamic value);
PostgrestQueryBuilder<T> lte(String column, dynamic value);
PostgrestQueryBuilder<T> like(String column, String pattern);
PostgrestQueryBuilder<T> ilike(String column, String pattern);
PostgrestQueryBuilder<T> isFilter(String column, dynamic value);
PostgrestQueryBuilder<T> inFilter(String column, List<dynamic> values);
PostgrestQueryBuilder<T> contains(String column, dynamic value);
PostgrestQueryBuilder<T> containedBy(String column, dynamic value);
PostgrestQueryBuilder<T> rangeLt(String column, String range);
PostgrestQueryBuilder<T> rangeGt(String column, String range);
PostgrestQueryBuilder<T> rangeGte(String column, String range);
PostgrestQueryBuilder<T> rangeLte(String column, String range);
PostgrestQueryBuilder<T> rangeAdjacent(String column, String range);
PostgrestQueryBuilder<T> overlaps(String column, List<dynamic> values);
PostgrestQueryBuilder<T> textSearch(String column, String query, {String? config, String? type});
PostgrestQueryBuilder<T> match(Map<String, dynamic> query);
PostgrestQueryBuilder<T> not(String column, String operator, dynamic value);
PostgrestQueryBuilder<T> or(String filters);
PostgrestQueryBuilder<T> filter(String column, String operator, dynamic value);
// Ordering and limiting
PostgrestQueryBuilder<T> order(String column, {bool ascending = true, bool nullsFirst = false});
PostgrestQueryBuilder<T> limit(int count, {String? foreignTable});
PostgrestQueryBuilder<T> range(int from, int to, {String? foreignTable});
// Execution
Future<PostgrestResponse<T>> execute();
// Modifications
Future<PostgrestResponse<T>> insert(Map<String, dynamic> values, {bool upsert = false});
Future<PostgrestResponse<T>> update(Map<String, dynamic> values);
Future<PostgrestResponse<T>> delete();
}
Environment Configuration #
class EnvConfig {
static Future<void> initialize();
static bool get isInitialized;
static String get baseUrl;
static String get projectSlug;
static String get anonKey;
static bool get isDebugMode;
static int get apiTimeout;
}
Error Types #
// Base exception
abstract class OrbitNestException implements Exception {
const OrbitNestException(this.message, {this.code, this.statusCode});
final String message;
final String? code;
final int? statusCode;
}
// Specific exceptions
class AuthException extends OrbitNestException;
class DatabaseException extends OrbitNestException;
class FunctionException extends OrbitNestException;
Development Status #
This package is now production-ready with enterprise-grade security:
✅ Authentication: Complete email/password and OTP-based authentication with secure session management ✅ Database: Full CRUD operations with Supabase-compatible query builder and RLS support ✅ Edge Functions: Function invocation, management, and environment variables with security validation ✅ BLoC Pattern: Reactive state management throughout with proper error handling ✅ Type Safety: Full null-safety with Freezed models and comprehensive validation ✅ Supabase Compatibility: Drop-in replacement API with identical syntax ✅ Enterprise Security: Token encryption, data sanitization, integrity checks, and secure storage ✅ Production Ready: Comprehensive error handling, logging, and monitoring with security-first approach
Security Compliance #
OrbitNest Studio Flutter meets enterprise security standards:
- 🔒 Data Protection: All sensitive data encrypted and sanitized
- 🛡️ Token Security: JWT tokens with integrity verification and automatic refresh
- 🚫 Zero Information Leakage: Comprehensive data sanitization in all logs
- 🔐 Secure Storage: Device-specific encryption for all sensitive data
- 🌐 Network Security: Security headers and request validation by default
- ⚡ Session Management: Automatic expiration and validation
- 🔄 Memory Safety: Secure cleanup of sensitive data on logout
Contributing #
This package is part of the OrbitNest Studio ecosystem. For issues and feature requests, please refer to the main OrbitNest Studio documentation.
Security Notice: This package has been audited for security vulnerabilities and implements industry-standard security practices. All authentication, session management, and data handling operations are designed to prevent common security issues including token tampering, data leakage, and session hijacking.