wowsql 1.4.0 copy "wowsql: ^1.4.0" to clipboard
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.

pub package License: MIT

✨ 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

  1. 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)
  2. 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 operations
  • ProjectAuthClient → 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 #

  1. Never expose Service Role Key in client-side code or public repositories
  2. Use Anonymous Key for client-side authentication flows (same key as database operations)
  3. Use Anonymous Key for public database access with limited permissions
  4. Store keys in environment variables, never hardcode them
  5. 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

📄 License #

MIT License - see LICENSE file for details.

🤝 Contributing #

Contributions are welcome! Please open an issue or submit a pull request.

📞 Support #


Made with ❤️ by the WOWSQL Team

0
likes
155
points
140
downloads

Publisher

unverified uploader

Weekly Downloads

Official Flutter/Dart SDK for WowSQL - MySQL Backend-as-a-Service with S3 Storage support, type-safe queries and fluent API

Homepage
Repository (GitHub)
View/report issues

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

http

More

Packages that depend on wowsql