orbitnest_studio_flutter 1.5.0 copy "orbitnest_studio_flutter: ^1.5.0" to clipboard
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 #

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 #

  1. Copy .env.example to .env in your project root:
cp .env.example .env
  1. 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 #

  1. Never commit .env files: Always use .env.example as template
  2. Use secure storage: Sensitive tokens are automatically encrypted
  3. Enable RLS: Always enable Row Level Security for production tables
  4. Validate inputs: Use form validation and server-side validation
  5. 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: #

  1. Separation of Concerns: Each layer has a single responsibility
  2. Dependency Injection: Lower layers don't depend on higher layers
  3. Reactive Architecture: UI reacts to state changes through BLoC pattern
  4. Type Safety: Freezed models ensure null-safety throughout
  5. 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.

1
likes
0
points
132
downloads

Publisher

unverified uploader

Weekly Downloads

Official Flutter SDK for OrbitNest Studio — auth (MFA, passkeys, SMS), Postgres queries, realtime, storage, and vector search for your backend.

Homepage
Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

crypto, dio, flutter, flutter_bloc, flutter_dotenv, flutter_secure_storage, jwt_decoder, passkeys, uuid, web_socket_channel

More

Packages that depend on orbitnest_studio_flutter