wowsql 1.0.0 copy "wowsql: ^1.0.0" to clipboard
wowsql: ^1.0.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 (single and batch)
  • 📊 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(
  projectUrl: 'https://your-project.wowsql.com',
  apiKey: 'your-api-key',
);

// Upload file
final bytes = await file.readAsBytes();
final result = await storage.upload(
  bytes,
  'uploads/document.pdf',
  contentType: 'application/pdf',
);
print('Uploaded: ${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',
  publicApiKey: 'public-api-key',
);

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)');

// Update with conditions
final updated = await client.table('users')
    .eq('status', 'inactive')
    .update({'status': 'active'});

Delete Data #

// Delete by ID
final result = await client.table('users').deleteById(1);

print('Deleted ${result.affectedRows} row(s)');

// Delete with conditions
final deleted = await client.table('users')
    .eq('status', 'deleted')
    .delete();

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(
  projectUrl: 'https://your-project.wowsql.com',
  apiKey: 'your-api-key',
);

// Upload file from bytes
final bytes = await File('document.pdf').readAsBytes();
final uploadResult = await storage.upload(
  bytes,
  'uploads/2024/document.pdf',
  contentType: 'application/pdf',
);
print('Uploaded: ${uploadResult.url}');

// Check if file exists
if (await storage.fileExists('uploads/document.pdf')) {
  print('File exists!');
}

// Get file information
final info = await storage.getFileInfo('uploads/document.pdf');
print('Size: ${info.size} bytes');
print('Modified: ${info.lastModified}');

// List files with prefix
final files = await storage.listFiles(prefix: 'uploads/2024/');
for (final file in files) {
  print('${file.key}: ${file.size} bytes');
}

// Download file (get presigned URL)
final url = await storage.download('uploads/document.pdf');
print('Download URL: $url');
// URL is valid for 1 hour by default

// Delete single file
await storage.deleteFile('uploads/old-file.pdf');

// Delete multiple files
await storage.deleteFiles([
  'uploads/file1.pdf',
  'uploads/file2.pdf',
  'uploads/file3.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(
      projectUrl: 'https://your-project.wowsql.com',
      apiKey: 'your-api-key',
    );

    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(
  projectUrl: 'https://your-project.wowsql.com',
  apiKey: 'your-api-key',
  timeout: Duration(minutes: 2), // 2 minutes for large files
);

Auto Quota Check #

// Disable automatic quota checking
final storage = WOWSQLStorage(
  projectUrl: 'https://your-project.wowsql.com',
  apiKey: 'your-api-key',
  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);
}

🔑 API Keys #

WOWSQL uses different API keys for different operations. Understanding which key to use is crucial for proper authentication.

Key Types Overview #

Operation Type Recommended Key Alternative Key Used By
Database Operations (CRUD) Service Role Key (wowbase_service_...) Anonymous Key (wowbase_anon_...) WOWSQLClient
Authentication Operations (OAuth, sign-in) Public API Key (wowbase_auth_...) Service Role Key (wowbase_service_...) ProjectAuthClient

Where to Find Your Keys #

All keys are found in: WOWSQL Dashboard → Authentication → PROJECT KEYS

  1. Service Role Key (wowbase_service_...)

    • Location: "Service Role Key (keep secret)"
    • Used for: Database CRUD operations (recommended for server-side)
    • Can also be used for authentication operations (fallback)
    • Important: Click the eye icon to reveal this key
  2. Public API Key (wowbase_auth_...)

    • Location: "Public API Key"
    • Used for: OAuth, sign-in, sign-up, user management
    • Recommended for client-side/public authentication flows
  3. Anonymous Key (wowbase_anon_...)

    • Location: "Anonymous Key"
    • Used for: Public/client-side database operations with limited permissions
    • Optional: Use when exposing database access to frontend/client

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 Public API Key (recommended for OAuth, sign-in, sign-up)
final auth = ProjectAuthClient(
  projectUrl: 'https://your-project.wowsql.com',
  publicApiKey: 'wowbase_auth_your-public-key-here',  // Public API Key
);

// Using Service Role Key (can be used for auth operations too)
final auth = ProjectAuthClient(
  projectUrl: 'https://your-project.wowsql.com',
  publicApiKey: 'wowbase_service_your-service-key-here',  // Service Role Key
);

// 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 - Public API Key
final authClient = ProjectAuthClient(
  projectUrl: dotenv.env['WOWSQL_PROJECT_URL']!,
  publicApiKey: dotenv.env['WOWSQL_PUBLIC_API_KEY']!,
);

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 Public API Key for client-side authentication flows
  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 Public API Key or Service Role Key
  • Verify the key is copied correctly (no extra spaces)

Error: "Authentication failed"

  • Check that you're using Public API Key (not Anonymous Key) for auth operations
  • 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
0
points
139
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

License

unknown (license)

Dependencies

http

More

Packages that depend on wowsql