wowsql 1.4.0
wowsql: ^1.4.0 copied to clipboard
Official Flutter/Dart SDK for WowSQL - MySQL Backend-as-a-Service with S3 Storage support, type-safe queries and fluent API
🚀 WOWSQL Flutter SDK #
Official Flutter/Dart SDK for WOWSQL - MySQL Backend-as-a-Service with S3 Storage.
✨ Features #
Database Features #
- 🗄️ Full CRUD operations (Create, Read, Update, Delete)
- 🔍 Advanced filtering (eq, neq, gt, gte, lt, lte, like, isNull)
- 📄 Pagination (limit, offset)
- 📊 Sorting (orderBy)
- 🎯 Fluent query builder API
- 🔒 Type-safe queries
- ⚡ Async/await support
- 📝 Raw SQL queries
- 📋 Table schema introspection
Storage Features (NEW!) #
- 📦 S3-compatible storage for file management
- ⬆️ File upload with automatic quota validation
- ⬇️ File download (presigned URLs)
- 📂 File listing with metadata
- 🗑️ File deletion
- 📊 Storage quota management
- 🌍 Multi-region S3 support
- 🛡️ Client-side limit enforcement
📦 Installation #
Add to your pubspec.yaml:
dependencies:
WOWSQL: ^1.0.0
Then run:
flutter pub get
🚀 Quick Start #
Database Operations #
import 'package:WOWSQL/WOWSQL.dart';
// Initialize client
final client = WOWSQLClient(
projectUrl: 'https://your-project.wowsql.com',
apiKey: 'your-api-key',
);
// Query data
final users = await client.table('users')
.select(['id', 'name', 'email'])
.eq('status', 'active')
.limit(10)
.get();
print('Found ${users.count} users');
for (final user in users.data) {
print('${user['name']} - ${user['email']}');
}
// Clean up
client.dispose();
Storage Operations #
import 'dart:typed_data';
import 'package:WOWSQL/WOWSQL.dart';
// Initialize storage client
final storage = WOWSQLStorage(
projectSlug: 'your-project',
apiKey: 'your-api-key',
baseUrl: 'https://api.wowsql.com', // optional, defaults to api.wowsql.com
);
// Upload file
final bytes = await file.readAsBytes();
final result = await storage.upload(
bytes,
'document.pdf',
folder: 'uploads',
contentType: 'application/pdf',
);
print('Uploaded: ${result.key}');
print('File URL: ${result.url}');
// Check quota
final quota = await storage.getQuota();
print('Storage used: ${quota.storageUsedGb}GB / ${quota.storageQuotaGb}GB');
// Clean up
storage.dispose();
Project Authentication #
import 'package:WOWSQL/WOWSQL.dart';
final auth = ProjectAuthClient(
projectUrl: 'https://your-project.wowsql.com',
apiKey: 'your-anon-key', // Use anon key for client-side, service key for server-side
);
final result = await auth.signUp(
email: 'user@example.com',
password: 'SuperSecret123',
fullName: 'End User',
);
print('User ID: ${result.user?.id}');
print('Access token: ${result.session.accessToken}');
final current = await auth.getUser(accessToken: result.session.accessToken);
print('Email verified: ${current.emailVerified}');
// OAuth Authentication
final oauthUrl = await auth.getOAuthAuthorizationUrl(
provider: 'github',
redirectUri: 'https://app.example.com/auth/callback',
);
// Redirect user to oauthUrl.authorizationUrl
// After callback, exchange code for tokens
final oauthResult = await auth.exchangeOAuthCallback(
provider: 'github',
code: 'authorization_code',
redirectUri: 'https://app.example.com/auth/callback',
);
print('OAuth user: ${oauthResult.user?.email}');
// Password Reset
final forgotResult = await auth.forgotPassword(email: 'user@example.com');
print(forgotResult['message']);
final resetResult = await auth.resetPassword(
token: 'reset_token_from_email',
newPassword: 'newSecurePassword123',
);
print(resetResult['message']);
📚 Usage Examples #
Select Queries #
final client = WOWSQLClient(
projectUrl: 'https://your-project.wowsql.com',
apiKey: 'your-api-key',
);
// Select all columns
final all = await client.table('users').select(['*']).get();
// Select specific columns
final users = await client.table('users')
.select(['id', 'name', 'email'])
.get();
// With filters
final active = await client.table('users')
.select(['*'])
.eq('status', 'active')
.gt('age', 18)
.get();
// With ordering
final recent = await client.table('users')
.select(['*'])
.orderBy('created_at', SortDirection.desc)
.limit(10)
.get();
// With pagination
final page1 = await client.table('users')
.select(['*'])
.limit(20)
.offset(0)
.get();
// Pattern matching
final gmailUsers = await client.table('users')
.select(['*'])
.like('email', '%@gmail.com')
.get();
// Get first result
final user = await client.table('users')
.eq('email', 'john@example.com')
.first();
if (user != null) {
print('Found user: ${user['name']}');
}
Insert Data #
// Insert single record
final result = await client.table('users').insert({
'name': 'John Doe',
'email': 'john@example.com',
'age': 30,
'status': 'active',
});
print('New user ID: ${result.id}');
Update Data #
// Update by ID
final result = await client.table('users').updateById(1, {
'name': 'Jane Smith',
'age': 26,
});
print('Updated ${result.affectedRows} row(s)');
// Get record by ID first, then update
final user = await client.table('users').getById(1);
if (user != null) {
await client.table('users').updateById(1, {
'status': 'active',
});
}
Delete Data #
// Delete by ID
final result = await client.table('users').deleteById(1);
print('Deleted ${result.affectedRows} row(s)');
// Get records first, then delete by ID
final usersToDelete = await client.table('users')
.eq('status', 'deleted')
.get();
for (final user in usersToDelete.data) {
await client.table('users').deleteById(user['id']);
}
Filter Operators #
// Equal
.eq('status', 'active')
// Not equal
.neq('status', 'deleted')
// Greater than
.gt('age', 18)
// Greater than or equal
.gte('age', 18)
// Less than
.lt('age', 65)
// Less than or equal
.lte('age', 65)
// Pattern matching (SQL LIKE)
.like('email', '%@gmail.com')
// Is null
.isNull('deleted_at')
Storage Operations #
import 'dart:io';
import 'dart:typed_data';
final storage = WOWSQLStorage(
projectSlug: 'your-project',
apiKey: 'your-api-key',
baseUrl: 'https://api.wowsql.com',
);
// Upload file from bytes
final bytes = await File('document.pdf').readAsBytes();
final uploadResult = await storage.upload(
bytes,
'document.pdf',
folder: 'uploads/2024',
contentType: 'application/pdf',
);
print('Uploaded: ${uploadResult.fileKey}');
// List files with prefix
final files = await storage.listFiles(prefix: 'uploads/2024/');
for (final file in files) {
print('${file.key}: ${file.size} bytes');
}
// Get presigned URL for download
final downloadUrl = await storage.getPresignedUrl('uploads/document.pdf');
print('Download URL: $downloadUrl');
// URL is valid for 1 hour by default
// Get file URL with metadata
final urlData = await storage.getFileUrl('uploads/document.pdf');
print('File URL: ${urlData['file_url']}');
print('Bucket: ${urlData['bucket_name']}');
// Delete single file
await storage.deleteFile('uploads/old-file.pdf');
// Check quota
final quota = await storage.getQuota();
print('Used: ${quota.storageUsedGb.toStringAsFixed(2)} GB');
print('Available: ${quota.storageAvailableGb.toStringAsFixed(2)} GB');
print('Usage: ${quota.usagePercentage.toStringAsFixed(1)}%');
// Check if enough storage before upload
if (quota.storageAvailableBytes < fileBytes.length) {
print('Not enough storage!');
} else {
await storage.upload(fileBytes, 'uploads/large-file.zip');
}
Error Handling #
try {
final users = await client.table('users').select(['*']).get();
print('Success: ${users.count} users');
} on AuthenticationException catch (e) {
print('Authentication error: ${e.message}');
} on NotFoundException catch (e) {
print('Not found: ${e.message}');
} on RateLimitException catch (e) {
print('Rate limit exceeded: ${e.message}');
} on WOWSQLException catch (e) {
print('Database error (${e.statusCode}): ${e.message}');
} catch (e) {
print('Unexpected error: $e');
}
try {
await storage.upload(fileBytes, 'uploads/file.pdf');
} on StorageLimitExceededException catch (e) {
print('Storage full: ${e.message}');
print('Required: ${e.requiredBytes} bytes');
print('Available: ${e.availableBytes} bytes');
} on StorageException catch (e) {
print('Storage error: ${e.message}');
}
Utility Methods #
// List all tables
final tables = await client.listTables();
print('Tables: $tables');
// Get table schema
final schema = await client.getTableSchema('users');
print('Columns: ${schema.columns.length}');
for (final column in schema.columns) {
print(' - ${column.name} (${column.type})');
}
// Raw SQL query
final results = await client.query<Map<String, dynamic>>(
'SELECT COUNT(*) as count FROM users WHERE age > 18'
);
print('Adult users: ${results.first['count']}');
// Check API health
final health = await client.health();
print('Status: ${health['status']}');
🎨 Flutter Integration #
With StatefulWidget #
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late WOWSQLClient client;
List<Map<String, dynamic>> users = [];
bool loading = true;
@override
void initState() {
super.initState();
client = WOWSQLClient(
projectUrl: 'https://your-project.wowsql.com',
apiKey: 'your-api-key',
);
_loadUsers();
}
Future<void> _loadUsers() async {
try {
final response = await client.table('users')
.select(['*'])
.limit(10)
.get();
setState(() {
users = response.data;
loading = false;
});
} catch (e) {
print('Error: $e');
setState(() => loading = false);
}
}
@override
void dispose() {
client.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (loading) {
return Center(child: CircularProgressIndicator());
}
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
title: Text(user['name']),
subtitle: Text(user['email']),
);
},
);
}
}
With FutureBuilder #
class UsersPage extends StatelessWidget {
final client = WOWSQLClient(
projectUrl: 'https://your-project.wowsql.com',
apiKey: 'your-api-key',
);
@override
Widget build(BuildContext context) {
return FutureBuilder<QueryResponse<Map<String, dynamic>>>(
future: client.table('users').select(['*']).get(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
final users = snapshot.data!.data;
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
title: Text(user['name']),
subtitle: Text(user['email']),
);
},
);
},
);
}
}
File Upload Example #
import 'package:file_picker/file_picker.dart';
import 'package:WOWSQL/WOWSQL.dart';
Future<void> uploadFile() async {
// Pick file
FilePickerResult? result = await FilePicker.platform.pickFiles();
if (result != null) {
final bytes = result.files.first.bytes!;
final fileName = result.files.first.name;
final storage = WOWSQLStorage(
projectSlug: 'your-project',
apiKey: 'your-api-key',
baseUrl: 'https://api.wowsql.com',
);
try {
final uploadResult = await storage.upload(
bytes,
'uploads/$fileName',
);
print('File uploaded: ${uploadResult.url}');
} on StorageLimitExceededException catch (e) {
print('Storage full: ${e.message}');
} finally {
storage.dispose();
}
}
}
🔧 Configuration #
Custom Timeout #
// Database client
final client = WOWSQLClient(
projectUrl: 'https://your-project.wowsql.com',
apiKey: 'your-api-key',
timeout: Duration(seconds: 60), // 60 seconds
);
// Storage client
final storage = WOWSQLStorage(
projectSlug: 'your-project',
apiKey: 'your-api-key',
baseUrl: 'https://api.wowsql.com',
timeout: Duration(minutes: 2), // 2 minutes for large files
);
Auto Quota Check #
// Disable automatic quota checking
final storage = WOWSQLStorage(
projectSlug: 'your-project',
apiKey: 'your-api-key',
baseUrl: 'https://api.wowsql.com',
autoCheckQuota: false, // Disable auto-check
);
// Manually check quota
final quota = await storage.getQuota();
if (quota.storageAvailableBytes > fileSize) {
await storage.upload(bytes, 'uploads/file.pdf', checkQuota: false);
}
🔑 Unified Authentication #
✨ One Project = One Set of Keys for ALL Operations
WOWSQL uses unified authentication - the same API keys work for both database operations AND authentication operations.
| Operation Type | Recommended Key | Alternative Key | Used By |
|---|---|---|---|
| Database Operations (CRUD) | Service Role Key (wowsql_service_...) |
Anonymous Key (wowsql_anon_...) |
WOWSQLClient |
| Authentication Operations (OAuth, sign-in) | Anonymous Key (wowsql_anon_...) |
Service Role Key (wowsql_service_...) |
ProjectAuthClient |
Where to Find Your Keys #
All keys are found in: WOWSQL Dashboard → Settings → API Keys or Authentication → PROJECT KEYS
-
Anonymous Key (
wowsql_anon_...) ✨ Unified Key- Location: "Anonymous Key (Public)"
- Used for:
- ✅ Client-side auth operations (signup, login, OAuth)
- ✅ Public/client-side database operations with limited permissions
- Safe to expose in frontend code (browser, mobile apps)
-
Service Role Key (
wowsql_service_...) ✨ Unified Key- Location: "Service Role Key (keep secret)"
- Used for:
- ✅ Server-side auth operations (admin, full access)
- ✅ Server-side database operations (full access, bypass RLS)
- NEVER expose in frontend code - server-side only!
Database Operations #
Use Service Role Key or Anonymous Key for database operations:
import 'package:WOWSQL/WOWSQL.dart';
// Using Service Role Key (recommended for server-side, full access)
final client = WOWSQLClient(
projectUrl: 'https://your-project.wowsql.com',
apiKey: 'wowbase_service_your-service-key-here', // Service Role Key
);
// Using Anonymous Key (for public/client-side access with limited permissions)
final client = WOWSQLClient(
projectUrl: 'https://your-project.wowsql.com',
apiKey: 'wowbase_anon_your-anon-key-here', // Anonymous Key
);
// Query data
final users = await client.table('users').get();
Authentication Operations #
Use Public API Key or Service Role Key for authentication:
import 'package:WOWSQL/WOWSQL.dart';
// Using Anonymous Key (recommended for client-side auth operations)
final auth = ProjectAuthClient(
projectUrl: 'https://your-project.wowsql.com',
apiKey: 'wowsql_anon_your-anon-key-here', // Same key as database operations!
);
// Using Service Role Key (for server-side auth operations)
final auth = ProjectAuthClient(
projectUrl: 'https://your-project.wowsql.com',
apiKey: 'wowsql_service_your-service-key-here', // Same key as database operations!
);
// OAuth authentication
final oauthUrl = await auth.getOAuthAuthorizationUrl(
provider: 'github',
redirectUri: 'https://app.example.com/auth/callback',
);
Environment Variables #
Best practice: Use environment variables for API keys:
import 'package:flutter_dotenv/flutter_dotenv.dart';
// Database operations - Service Role Key
final dbClient = WOWSQLClient(
projectUrl: dotenv.env['WOWSQL_PROJECT_URL']!,
apiKey: dotenv.env['WOWSQL_SERVICE_ROLE_KEY']!, // or WOWSQL_ANON_KEY
);
// Authentication operations - Use the SAME key!
final authClient = ProjectAuthClient(
projectUrl: dotenv.env['WOWSQL_PROJECT_URL']!,
apiKey: dotenv.env['WOWSQL_ANON_KEY']!, // Same key for client-side auth
// Or use WOWSQL_SERVICE_ROLE_KEY for server-side auth
);
Key Usage Summary #
WOWSQLClient→ Uses Service Role Key or Anonymous Key for database operationsProjectAuthClient→ Uses Public API Key or Service Role Key for authentication operations- Service Role Key can be used for both database AND authentication operations
- Public API Key is specifically for authentication operations only
- Anonymous Key is optional and provides limited permissions for public database access
Security Best Practices #
- Never expose Service Role Key in client-side code or public repositories
- Use Anonymous Key for client-side authentication flows (same key as database operations)
- Use Anonymous Key for public database access with limited permissions
- Store keys in environment variables, never hardcode them
- Rotate keys regularly if compromised
🔧 Schema Management #
Programmatically manage your database schema with the WOWSQLSchema client.
⚠️ IMPORTANT: Schema operations require a Service Role Key (
service_*). Anonymous keys will return a 403 Forbidden error.
Quick Start #
import 'package:WOWSQL/WOWSQL.dart';
// Initialize schema client with SERVICE ROLE KEY
final schema = WOWSQLSchema(
projectUrl: 'https://your-project.wowsql.com',
serviceKey: 'service_xyz789...', // ⚠️ Backend only! Never expose!
);
Create Table #
// Create a new table
await schema.createTable(
tableName: 'products',
columns: [
ColumnDefinition(name: 'id', type: 'INT', autoIncrement: true),
ColumnDefinition(name: 'name', type: 'VARCHAR(255)', notNull: true),
ColumnDefinition(name: 'price', type: 'DECIMAL(10,2)', notNull: true),
ColumnDefinition(name: 'category', type: 'VARCHAR(100)'),
ColumnDefinition(
name: 'created_at',
type: 'TIMESTAMP',
defaultValue: 'CURRENT_TIMESTAMP'
),
],
primaryKey: 'id',
indexes: [
IndexDefinition(name: 'idx_category', columns: ['category']),
IndexDefinition(name: 'idx_price', columns: ['price']),
],
);
print('Table created successfully!');
Alter Table #
// Add a new column
await schema.alterTable(
tableName: 'products',
addColumns: [
ColumnDefinition(name: 'stock_quantity', type: 'INT', defaultValue: '0'),
],
);
// Modify an existing column
await schema.alterTable(
tableName: 'products',
modifyColumns: [
ColumnDefinition(name: 'price', type: 'DECIMAL(12,2)'), // Increase precision
],
);
// Drop a column
await schema.alterTable(
tableName: 'products',
dropColumns: ['category'],
);
// Rename a column
await schema.alterTable(
tableName: 'products',
renameColumns: [
RenameColumn(oldName: 'name', newName: 'product_name'),
],
);
Drop Table #
// Drop a table
await schema.dropTable('old_table');
// Drop with CASCADE (removes dependent objects)
await schema.dropTable('products', cascade: true);
Execute Raw SQL #
// Execute custom schema SQL
await schema.executeSQL('''
CREATE INDEX idx_product_name
ON products(product_name);
''');
// Add a foreign key constraint
await schema.executeSQL('''
ALTER TABLE orders
ADD CONSTRAINT fk_product
FOREIGN KEY (product_id)
REFERENCES products(id);
''');
Security & Best Practices #
✅ DO:
- Use service role keys only in backend/server code (never in mobile apps)
- Store service keys in environment variables or secure configuration
- Use anonymous keys for client-side data operations
- Test schema changes in development first
❌ DON'T:
- Never expose service role keys in mobile app code
- Never commit service keys to version control
- Don't use anonymous keys for schema operations (will fail)
Example: Backend Migration Script #
import 'dart:io';
import 'package:WOWSQL/WOWSQL.dart';
Future<void> runMigration() async {
final schema = WOWSQLSchema(
projectUrl: Platform.environment['WOWSQL_PROJECT_URL']!,
serviceKey: Platform.environment['WOWSQL_SERVICE_KEY']!, // From env var
);
try {
// Create users table
await schema.createTable(
tableName: 'users',
columns: [
ColumnDefinition(name: 'id', type: 'INT', autoIncrement: true),
ColumnDefinition(
name: 'email',
type: 'VARCHAR(255)',
unique: true,
notNull: true,
),
ColumnDefinition(name: 'name', type: 'VARCHAR(255)', notNull: true),
ColumnDefinition(
name: 'created_at',
type: 'TIMESTAMP',
defaultValue: 'CURRENT_TIMESTAMP',
),
],
primaryKey: 'id',
indexes: [
IndexDefinition(name: 'idx_email', columns: ['email']),
],
);
print('Migration completed!');
} catch (e) {
print('Migration failed: $e');
rethrow;
}
}
void main() async {
await runMigration();
}
Error Handling #
import 'package:WOWSQL/WOWSQL.dart';
try {
final schema = WOWSQLSchema(
projectUrl: 'https://your-project.wowsql.com',
serviceKey: 'service_xyz...',
);
await schema.createTable(
tableName: 'test',
columns: [ColumnDefinition(name: 'id', type: 'INT')],
);
} on PermissionException catch (e) {
print('Permission denied: ${e.message}');
print('Make sure you\'re using a SERVICE ROLE KEY, not an anonymous key!');
} on WOWSQLException catch (e) {
print('Schema error: ${e.message}');
} catch (e) {
print('Unexpected error: $e');
}
Troubleshooting #
Error: "Invalid API key for project"
- Ensure you're using the correct key type for the operation
- Database operations require Service Role Key or Anonymous Key
- Authentication operations require Anonymous Key (client-side) or Service Role Key (server-side)
- Verify the key is copied correctly (no extra spaces)
Error: "Authentication failed"
- Check that you're using the correct key: Anonymous Key for client-side, Service Role Key for server-side
- Verify the project URL matches your dashboard
- Ensure the key hasn't been revoked or expired
📋 Requirements #
- Dart SDK:
>=3.0.0 <4.0.0 - Flutter:
>=3.0.0
🔗 Links #
- 📚 Documentation
- 🌐 Website
- 💬 Discord
- 🐛 Issues
📄 License #
MIT License - see LICENSE file for details.
🤝 Contributing #
Contributions are welcome! Please open an issue or submit a pull request.
📞 Support #
- Email: support@wowsql.com
- Discord: https://discord.gg/WOWSQL
- Documentation: https://wowsql.com/docs
Made with ❤️ by the WOWSQL Team